2026-02-21 21:30:39 +00:00
|
|
|
#include "DefaultFloatingAlgorithm.hpp"
|
|
|
|
|
|
|
|
|
|
#include "../../Algorithm.hpp"
|
|
|
|
|
|
|
|
|
|
#include "../../../target/WindowTarget.hpp"
|
|
|
|
|
#include "../../../space/Space.hpp"
|
|
|
|
|
|
|
|
|
|
#include "../../../../Compositor.hpp"
|
|
|
|
|
#include "../../../../helpers/Monitor.hpp"
|
|
|
|
|
|
|
|
|
|
using namespace Layout;
|
|
|
|
|
using namespace Layout::Floating;
|
|
|
|
|
|
|
|
|
|
constexpr const Vector2D DEFAULT_SIZE = {640, 400};
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
void CDefaultFloatingAlgorithm::newTarget(SP<ITarget> target) {
|
|
|
|
|
const auto WORK_AREA = m_parent->space()->workArea(true);
|
|
|
|
|
const auto DESIRED_GEOM = target->desiredGeometry();
|
|
|
|
|
const auto MONITOR_POS = m_parent->space()->workspace()->m_monitor->logicalBox().pos();
|
|
|
|
|
|
|
|
|
|
CBox windowGeometry;
|
|
|
|
|
|
|
|
|
|
if (!DESIRED_GEOM) {
|
|
|
|
|
switch (DESIRED_GEOM.error()) {
|
|
|
|
|
case GEOMETRY_INVALID_DESIRED: {
|
|
|
|
|
// if the desired is invalid, we hide the window.
|
|
|
|
|
if (target->type() == TARGET_TYPE_WINDOW)
|
|
|
|
|
dynamicPointerCast<CWindowTarget>(target)->window()->setHidden(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case GEOMETRY_NO_DESIRED: {
|
|
|
|
|
// add a default geom
|
|
|
|
|
windowGeometry = CBox{WORK_AREA.middle() - DEFAULT_SIZE / 2.F, DEFAULT_SIZE};
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (DESIRED_GEOM->pos)
|
|
|
|
|
windowGeometry = CBox{DESIRED_GEOM->pos.value(), DESIRED_GEOM->size};
|
|
|
|
|
else
|
|
|
|
|
windowGeometry = CBox{WORK_AREA.middle() - DESIRED_GEOM->size / 2.F, DESIRED_GEOM->size};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool posOverridden = false;
|
|
|
|
|
|
|
|
|
|
if (target->window() && target->window()->m_firstMap) {
|
|
|
|
|
const auto WINDOW = target->window();
|
|
|
|
|
|
|
|
|
|
// set this here so that expressions can use it. This could be wrong of course.
|
|
|
|
|
WINDOW->m_realSize->setValueAndWarp(DESIRED_GEOM ? DESIRED_GEOM->size : DEFAULT_SIZE);
|
|
|
|
|
|
|
|
|
|
if (!WINDOW->m_ruleApplicator->static_.size.empty()) {
|
|
|
|
|
const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.size);
|
|
|
|
|
if (!COMPUTED)
|
|
|
|
|
Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.size);
|
|
|
|
|
else {
|
|
|
|
|
windowGeometry.w = COMPUTED->x;
|
|
|
|
|
windowGeometry.h = COMPUTED->y;
|
|
|
|
|
|
|
|
|
|
// update for pos to work with size.
|
|
|
|
|
WINDOW->m_realPosition->setValueAndWarp(*COMPUTED);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!WINDOW->m_ruleApplicator->static_.position.empty()) {
|
|
|
|
|
const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.position);
|
|
|
|
|
if (!COMPUTED)
|
|
|
|
|
Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.position);
|
|
|
|
|
else {
|
|
|
|
|
windowGeometry.x = COMPUTED->x + MONITOR_POS.x;
|
|
|
|
|
windowGeometry.y = COMPUTED->y + MONITOR_POS.y;
|
|
|
|
|
posOverridden = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (WINDOW->m_ruleApplicator->static_.center.value_or(false)) {
|
|
|
|
|
const auto POS = WORK_AREA.middle() - windowGeometry.size() / 2.f;
|
|
|
|
|
windowGeometry.x = POS.x;
|
|
|
|
|
windowGeometry.y = POS.y;
|
|
|
|
|
posOverridden = true;
|
|
|
|
|
}
|
|
|
|
|
} else if (target->lastFloatingSize().x > 5 && target->lastFloatingSize().y > 5) {
|
|
|
|
|
windowGeometry.w = target->lastFloatingSize().x;
|
|
|
|
|
windowGeometry.h = target->lastFloatingSize().y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!posOverridden && (!DESIRED_GEOM || !DESIRED_GEOM->pos))
|
|
|
|
|
windowGeometry = CBox{WORK_AREA.middle() - windowGeometry.size() / 2.F, windowGeometry.size()};
|
|
|
|
|
|
2026-02-24 19:01:51 +00:00
|
|
|
if (posOverridden // pos is overridden by a rule
|
|
|
|
|
|| (DESIRED_GEOM && DESIRED_GEOM->pos && target->window() && target->window()->m_isX11) // X11 window with a geom
|
|
|
|
|
|| WORK_AREA.containsPoint(windowGeometry.middle())) // geometry within work area
|
2026-02-21 21:30:39 +00:00
|
|
|
target->setPositionGlobal(windowGeometry);
|
|
|
|
|
else {
|
|
|
|
|
const auto POS = WORK_AREA.middle() - windowGeometry.size() / 2.f;
|
|
|
|
|
windowGeometry.x = POS.x;
|
|
|
|
|
windowGeometry.y = POS.y;
|
|
|
|
|
|
|
|
|
|
target->setPositionGlobal(windowGeometry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: not very OOP, is it?
|
|
|
|
|
if (const auto WTARGET = dynamicPointerCast<CWindowTarget>(target); WTARGET) {
|
2026-02-25 13:29:12 +01:00
|
|
|
const auto PWINDOW = WTARGET->window();
|
2026-02-21 21:30:39 +00:00
|
|
|
|
|
|
|
|
if (PWINDOW->m_X11DoesntWantBorders || (PWINDOW->m_isX11 && PWINDOW->isX11OverrideRedirect())) {
|
|
|
|
|
PWINDOW->m_realPosition->warp();
|
|
|
|
|
PWINDOW->m_realSize->warp();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!PWINDOW->isX11OverrideRedirect())
|
|
|
|
|
g_pCompositor->changeWindowZOrder(PWINDOW, true);
|
|
|
|
|
else {
|
|
|
|
|
PWINDOW->m_pendingReportedSize = PWINDOW->m_realSize->goal();
|
|
|
|
|
PWINDOW->m_reportedSize = PWINDOW->m_pendingReportedSize;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-02 18:57:09 +00:00
|
|
|
|
|
|
|
|
updateTarget(target);
|
2026-02-21 21:30:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CDefaultFloatingAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {
|
|
|
|
|
auto LAST_SIZE = target->lastFloatingSize();
|
|
|
|
|
const auto CURRENT_SIZE = target->position().size();
|
|
|
|
|
|
|
|
|
|
// ignore positioning a dragged target
|
|
|
|
|
if (g_layoutManager->dragController()->target() == target)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (LAST_SIZE.x < 5 || LAST_SIZE.y < 5) {
|
|
|
|
|
const auto DESIRED = target->desiredGeometry();
|
|
|
|
|
LAST_SIZE = DESIRED ? DESIRED->size : DEFAULT_SIZE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (target->wasTiling()) {
|
|
|
|
|
// Avoid floating toggles that don't change size, they aren't easily visible to the user
|
|
|
|
|
if (std::abs(LAST_SIZE.x - CURRENT_SIZE.x) < 5 && std::abs(LAST_SIZE.y - CURRENT_SIZE.y) < 5)
|
|
|
|
|
LAST_SIZE += Vector2D{10, 10};
|
|
|
|
|
|
|
|
|
|
// calculate new position
|
|
|
|
|
const auto OLD_CENTER = target->position().middle();
|
|
|
|
|
|
|
|
|
|
// put around the current center, fit in workArea
|
|
|
|
|
target->setPositionGlobal(fitBoxInWorkArea(CBox{OLD_CENTER - LAST_SIZE / 2.F, LAST_SIZE}, target));
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// calculate new position
|
|
|
|
|
const auto THIS_MON_POS = m_parent->space()->workspace()->m_monitor->m_position;
|
|
|
|
|
const auto OLD_POS = target->position().pos();
|
|
|
|
|
const auto MON_FROM_OLD = g_pCompositor->getMonitorFromVector(OLD_POS);
|
|
|
|
|
const auto NEW_POS = MON_FROM_OLD ? OLD_POS - MON_FROM_OLD->m_position + THIS_MON_POS : OLD_POS;
|
|
|
|
|
|
|
|
|
|
// put around the current center, fit in workArea
|
|
|
|
|
target->setPositionGlobal(fitBoxInWorkArea(CBox{NEW_POS, LAST_SIZE}, target));
|
|
|
|
|
}
|
2026-03-02 18:57:09 +00:00
|
|
|
|
|
|
|
|
updateTarget(target);
|
2026-02-21 21:30:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP<ITarget> t) {
|
|
|
|
|
const auto WORK_AREA = m_parent->space()->workArea(true);
|
|
|
|
|
const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS) : SBoxExtents{};
|
|
|
|
|
CBox targetBox = box.copy().addExtents(EXTENTS);
|
|
|
|
|
|
|
|
|
|
targetBox.x = std::max(targetBox.x, WORK_AREA.x);
|
|
|
|
|
targetBox.y = std::max(targetBox.y, WORK_AREA.y);
|
|
|
|
|
|
|
|
|
|
if (targetBox.x + targetBox.w > WORK_AREA.x + WORK_AREA.w)
|
|
|
|
|
targetBox.x = WORK_AREA.x + WORK_AREA.w - targetBox.w;
|
|
|
|
|
|
|
|
|
|
if (targetBox.y + targetBox.h > WORK_AREA.y + WORK_AREA.h)
|
|
|
|
|
targetBox.y = WORK_AREA.y + WORK_AREA.h - targetBox.h;
|
|
|
|
|
|
|
|
|
|
return targetBox.addExtents(SBoxExtents{.topLeft = -EXTENTS.topLeft, .bottomRight = -EXTENTS.bottomRight});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CDefaultFloatingAlgorithm::removeTarget(SP<ITarget> target) {
|
|
|
|
|
target->rememberFloatingSize(target->position().size());
|
2026-03-02 18:57:09 +00:00
|
|
|
m_datas.erase(target);
|
2026-02-21 21:30:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
|
|
|
|
|
auto pos = target->position();
|
|
|
|
|
pos.w += Δ.x;
|
|
|
|
|
pos.h += Δ.y;
|
|
|
|
|
pos.translate(-Δ / 2.F);
|
|
|
|
|
target->setPositionGlobal(pos);
|
|
|
|
|
|
|
|
|
|
if (g_layoutManager->dragController()->target() == target)
|
|
|
|
|
target->warpPositionSize();
|
2026-03-02 18:57:09 +00:00
|
|
|
|
|
|
|
|
updateTarget(target);
|
2026-02-21 21:30:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> target) {
|
|
|
|
|
auto pos = target->position();
|
|
|
|
|
pos.translate(Δ);
|
|
|
|
|
target->setPositionGlobal(pos);
|
|
|
|
|
|
|
|
|
|
if (g_layoutManager->dragController()->target() == target)
|
|
|
|
|
target->warpPositionSize();
|
2026-03-02 18:57:09 +00:00
|
|
|
|
|
|
|
|
updateTarget(target);
|
2026-02-21 21:30:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CDefaultFloatingAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
|
|
|
|
|
auto posABackup = a->position();
|
|
|
|
|
a->setPositionGlobal(b->position());
|
|
|
|
|
b->setPositionGlobal(posABackup);
|
2026-03-02 18:57:09 +00:00
|
|
|
|
|
|
|
|
updateTarget(a);
|
|
|
|
|
updateTarget(b);
|
2026-02-21 21:30:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CDefaultFloatingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
|
|
|
|
|
auto pos = t->position();
|
|
|
|
|
auto work = m_parent->space()->workArea(true);
|
|
|
|
|
|
|
|
|
|
const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS) : SBoxExtents{};
|
|
|
|
|
|
|
|
|
|
switch (dir) {
|
|
|
|
|
case Math::DIRECTION_LEFT: pos.x = work.x + EXTENTS.topLeft.x; break;
|
|
|
|
|
case Math::DIRECTION_RIGHT: pos.x = work.x + work.w - pos.w - EXTENTS.bottomRight.x; break;
|
|
|
|
|
case Math::DIRECTION_UP: pos.y = work.y + EXTENTS.topLeft.y; break;
|
|
|
|
|
case Math::DIRECTION_DOWN: pos.y = work.y + work.h - pos.h - EXTENTS.bottomRight.y; break;
|
|
|
|
|
default: Log::logger->log(Log::ERR, "Invalid direction in CDefaultFloatingAlgorithm::moveTargetInDirection"); break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t->setPositionGlobal(pos);
|
2026-03-02 18:57:09 +00:00
|
|
|
|
|
|
|
|
updateTarget(t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CDefaultFloatingAlgorithm::recenter(SP<ITarget> t) {
|
|
|
|
|
if (!m_datas.contains(t)) {
|
|
|
|
|
IFloatingAlgorithm::recenter(t);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t->setPositionGlobal(m_datas.at(t).lastBox);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CDefaultFloatingAlgorithm::setTargetGeom(const CBox& geom, SP<ITarget> target) {
|
|
|
|
|
target->setPositionGlobal(geom);
|
|
|
|
|
|
|
|
|
|
updateTarget(target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CDefaultFloatingAlgorithm::updateTarget(SP<ITarget> t) {
|
|
|
|
|
m_datas[t] = {.lastBox = t->position()};
|
2026-02-21 21:30:39 +00:00
|
|
|
}
|