mirror of
https://github.com/hyprwm/Hyprland
synced 2024-11-15 06:45:59 +01:00
input: add snapping to floating windows (#8088)
* add snapping to floating windows Works for both moving and resizing of windows. It comes with 3 options: `general:snap:enabled` - whether it's enabled, off by default `general:snap:window_gap` - minimum gap in pixels between windows before snapping. Setting to 0 effectively turns off this method of snapping. `general:snap:monitor_gap` - minimum gap in pixels between window and monitor edges before snapping. Again, setting it to 0 effectively turns it off. * snap: add more ignore criteria and change if clause into a guard * snap: refactor code * snap: new refactoring approach and account for border size * snap: do corner snapping after all edge snapping is done The approach of performing corner snaps after each individual edge snap results in far fewer scenarios where snapping can occur. After trying it out for a while, I found that I prefer an approach that's more prone to snapping. * snap: combine snapWindows and snapMonitor into a single function * snap: add forced aspect ratio functionality * snap: avoid directly referring to border_size config value * snap: address vaxerski feedback - add new line between functions - use std::function typedef for SnapFn and make snap functions static - avoid uninitialized variable declarations. - change ignore condition m_bIsX11 to isX11OverrideRedirect() - use braces for CBox and Vector2D declarations. - add SNAP_INVALID to eSnapEdge enum - use bitshift notation for eSnapEdge and eRectCorner - make performSnap a non-member function. * snap: add corner-snapping to forced aspect ratio mode
This commit is contained in:
parent
08cc063e17
commit
4093b993a2
4 changed files with 185 additions and 9 deletions
|
@ -104,6 +104,24 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
||||||
.type = CONFIG_OPTION_INT,
|
.type = CONFIG_OPTION_INT,
|
||||||
.data = SConfigOptionDescription::SRangeData{0, 0, 4},
|
.data = SConfigOptionDescription::SRangeData{0, 0, 4},
|
||||||
},
|
},
|
||||||
|
SConfigOptionDescription{
|
||||||
|
.value = "general:snap:enabled",
|
||||||
|
.description = "enable snapping for floating windows",
|
||||||
|
.type = CONFIG_OPTION_BOOL,
|
||||||
|
.data = SConfigOptionDescription::SBoolData{false},
|
||||||
|
},
|
||||||
|
SConfigOptionDescription{
|
||||||
|
.value = "general:snap:window_gap",
|
||||||
|
.description = "minimum gap in pixels between windows before snapping",
|
||||||
|
.type = CONFIG_OPTION_INT,
|
||||||
|
.data = SConfigOptionDescription::SRangeData{10, 0, 100},
|
||||||
|
},
|
||||||
|
SConfigOptionDescription{
|
||||||
|
.value = "general:snap:monitor_gap",
|
||||||
|
.description = "minimum gap in pixels between window and monitor edges before snapping",
|
||||||
|
.type = CONFIG_OPTION_INT,
|
||||||
|
.data = SConfigOptionDescription::SRangeData{10, 0, 100},
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* decoration:
|
* decoration:
|
||||||
|
|
|
@ -341,6 +341,9 @@ CConfigManager::CConfigManager() {
|
||||||
m_pConfig->addConfigValue("general:layout", {"dwindle"});
|
m_pConfig->addConfigValue("general:layout", {"dwindle"});
|
||||||
m_pConfig->addConfigValue("general:allow_tearing", Hyprlang::INT{0});
|
m_pConfig->addConfigValue("general:allow_tearing", Hyprlang::INT{0});
|
||||||
m_pConfig->addConfigValue("general:resize_corner", Hyprlang::INT{0});
|
m_pConfig->addConfigValue("general:resize_corner", Hyprlang::INT{0});
|
||||||
|
m_pConfig->addConfigValue("general:snap:enabled", Hyprlang::INT{0});
|
||||||
|
m_pConfig->addConfigValue("general:snap:window_gap", Hyprlang::INT{10});
|
||||||
|
m_pConfig->addConfigValue("general:snap:monitor_gap", Hyprlang::INT{10});
|
||||||
|
|
||||||
m_pConfig->addConfigValue("misc:disable_hyprland_logo", Hyprlang::INT{0});
|
m_pConfig->addConfigValue("misc:disable_hyprland_logo", Hyprlang::INT{0});
|
||||||
m_pConfig->addConfigValue("misc:disable_splash_rendering", Hyprlang::INT{0});
|
m_pConfig->addConfigValue("misc:disable_splash_rendering", Hyprlang::INT{0});
|
||||||
|
|
|
@ -396,6 +396,138 @@ void IHyprLayout::onEndDragWindow() {
|
||||||
g_pInputManager->m_bWasDraggingWindow = false;
|
g_pInputManager->m_bWasDraggingWindow = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool canSnap(const double sideA, const double sideB, const double gap) {
|
||||||
|
return std::abs(sideA - sideB) < gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void snapMoveLeft(double& pos, double& len, const double p) {
|
||||||
|
pos = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void snapMoveRight(double& pos, double& len, const double p) {
|
||||||
|
pos = p - len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void snapResizeLeft(double& pos, double& len, const double p) {
|
||||||
|
len += pos - p;
|
||||||
|
pos = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void snapResizeRight(double& pos, double& len, const double p) {
|
||||||
|
len = p - pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef std::function<void(double&, double&, const double)> SnapFn;
|
||||||
|
|
||||||
|
static void performSnap(Vector2D& pos, Vector2D& size, PHLWINDOW DRAGGINGWINDOW, const eMouseBindMode mode, const int corner, const Vector2D& beginSize) {
|
||||||
|
static auto SNAPWINDOWGAP = CConfigValue<Hyprlang::INT>("general:snap:window_gap");
|
||||||
|
static auto SNAPMONITORGAP = CConfigValue<Hyprlang::INT>("general:snap:monitor_gap");
|
||||||
|
|
||||||
|
const SnapFn snapRight = (mode == MBIND_MOVE) ? snapMoveRight : snapResizeRight;
|
||||||
|
const SnapFn snapLeft = (mode == MBIND_MOVE) ? snapMoveLeft : snapResizeLeft;
|
||||||
|
const SnapFn snapDown = snapRight;
|
||||||
|
const SnapFn snapUp = snapLeft;
|
||||||
|
int snaps = 0;
|
||||||
|
|
||||||
|
if (*SNAPWINDOWGAP) {
|
||||||
|
const auto PID = DRAGGINGWINDOW->getPID();
|
||||||
|
const auto WSID = DRAGGINGWINDOW->workspaceID();
|
||||||
|
const int BORD = DRAGGINGWINDOW->getRealBorderSize();
|
||||||
|
|
||||||
|
for (auto& other : g_pCompositor->m_vWindows) {
|
||||||
|
if (other->workspaceID() != WSID || other->getPID() == PID || !other->m_bIsMapped || other->m_bFadingOut || other->isX11OverrideRedirect())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const int bord = std::max(BORD, other->getRealBorderSize());
|
||||||
|
const double gap = *SNAPWINDOWGAP + bord;
|
||||||
|
|
||||||
|
const CBox box = other->getWindowMainSurfaceBox();
|
||||||
|
const CBox ob = {box.x, box.y, box.x + box.w, box.y + box.h};
|
||||||
|
const CBox bb = {ob.x - bord, ob.y - bord, ob.w + bord, ob.h + bord};
|
||||||
|
const Vector2D end = {pos.x + size.x, pos.y + size.y};
|
||||||
|
|
||||||
|
// only snap windows if their ranges intersect in the opposite axis
|
||||||
|
if (pos.y <= bb.h && bb.y <= end.y) {
|
||||||
|
if (corner & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && canSnap(pos.x, bb.w, gap)) {
|
||||||
|
snapLeft(pos.x, size.x, bb.w);
|
||||||
|
snaps |= SNAP_LEFT;
|
||||||
|
} else if (corner & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && canSnap(end.x, bb.x, gap)) {
|
||||||
|
snapRight(pos.x, size.x, bb.x);
|
||||||
|
snaps |= SNAP_RIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pos.x <= bb.w && bb.x <= end.x) {
|
||||||
|
if (corner & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && canSnap(pos.y, bb.h, gap)) {
|
||||||
|
snapUp(pos.y, size.y, bb.h);
|
||||||
|
snaps |= SNAP_UP;
|
||||||
|
} else if (corner & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && canSnap(end.y, bb.y, gap)) {
|
||||||
|
snapDown(pos.y, size.y, bb.y);
|
||||||
|
snaps |= SNAP_DOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// corner snapping
|
||||||
|
if (pos.x == bb.w || bb.x == pos.x + size.x) {
|
||||||
|
if (corner & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && canSnap(pos.y, ob.y, gap)) {
|
||||||
|
snapUp(pos.y, size.y, ob.y);
|
||||||
|
snaps |= SNAP_UP;
|
||||||
|
} else if (corner & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && canSnap(end.y, ob.h, gap)) {
|
||||||
|
snapDown(pos.y, size.y, ob.h);
|
||||||
|
snaps |= SNAP_DOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pos.y == bb.h || bb.y == pos.y + size.y) {
|
||||||
|
if (corner & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && canSnap(pos.x, ob.x, gap)) {
|
||||||
|
snapLeft(pos.x, size.x, ob.x);
|
||||||
|
snaps |= SNAP_LEFT;
|
||||||
|
} else if (corner & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && canSnap(end.x, ob.w, gap)) {
|
||||||
|
snapRight(pos.x, size.x, ob.w);
|
||||||
|
snaps |= SNAP_RIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*SNAPMONITORGAP) {
|
||||||
|
const auto MON = g_pCompositor->getMonitorFromID(DRAGGINGWINDOW->m_iMonitorID);
|
||||||
|
const CBox mon = {MON->vecPosition.x, MON->vecPosition.y, MON->vecPosition.x + MON->vecSize.x, MON->vecPosition.y + MON->vecSize.y};
|
||||||
|
const double gap = *SNAPMONITORGAP;
|
||||||
|
|
||||||
|
if (canSnap(pos.x, mon.x, gap)) {
|
||||||
|
snapLeft(pos.x, size.x, mon.x);
|
||||||
|
snaps |= SNAP_LEFT;
|
||||||
|
}
|
||||||
|
if (canSnap(pos.x + size.x, mon.w, gap)) {
|
||||||
|
snapRight(pos.x, size.x, mon.w);
|
||||||
|
snaps |= SNAP_RIGHT;
|
||||||
|
}
|
||||||
|
if (canSnap(pos.y, mon.y, gap)) {
|
||||||
|
snapUp(pos.y, size.y, mon.y);
|
||||||
|
snaps |= SNAP_UP;
|
||||||
|
}
|
||||||
|
if (canSnap(pos.y + size.y, mon.h, gap)) {
|
||||||
|
snapDown(pos.y, size.y, mon.h);
|
||||||
|
snaps |= SNAP_DOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == MBIND_RESIZE_FORCE_RATIO) {
|
||||||
|
const double RATIO = beginSize.y / beginSize.x;
|
||||||
|
|
||||||
|
if ((corner & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && snaps & SNAP_LEFT) || (corner & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && snaps & SNAP_RIGHT)) {
|
||||||
|
const double sizeY = size.x * RATIO;
|
||||||
|
if (corner & (CORNER_TOPLEFT | CORNER_TOPRIGHT))
|
||||||
|
pos.y += size.y - sizeY;
|
||||||
|
size.y = sizeY;
|
||||||
|
} else if ((corner & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && snaps & SNAP_UP) || (corner & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && snaps & SNAP_DOWN)) {
|
||||||
|
const double sizeX = size.y / RATIO;
|
||||||
|
if (corner & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT))
|
||||||
|
pos.x += size.x - sizeX;
|
||||||
|
size.x = sizeX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void IHyprLayout::onMouseMove(const Vector2D& mousePos) {
|
void IHyprLayout::onMouseMove(const Vector2D& mousePos) {
|
||||||
if (g_pInputManager->currentlyDraggedWindow.expired())
|
if (g_pInputManager->currentlyDraggedWindow.expired())
|
||||||
return;
|
return;
|
||||||
|
@ -418,6 +550,8 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) {
|
||||||
static auto PANIMATEMOUSE = CConfigValue<Hyprlang::INT>("misc:animate_mouse_windowdragging");
|
static auto PANIMATEMOUSE = CConfigValue<Hyprlang::INT>("misc:animate_mouse_windowdragging");
|
||||||
static auto PANIMATE = CConfigValue<Hyprlang::INT>("misc:animate_manual_resizes");
|
static auto PANIMATE = CConfigValue<Hyprlang::INT>("misc:animate_manual_resizes");
|
||||||
|
|
||||||
|
static auto SNAPENABLED = CConfigValue<Hyprlang::INT>("general:snap:enabled");
|
||||||
|
|
||||||
const auto TIMERDELTA = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - TIMER).count();
|
const auto TIMERDELTA = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - TIMER).count();
|
||||||
const auto MSDELTA = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - MSTIMER).count();
|
const auto MSDELTA = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - MSTIMER).count();
|
||||||
const auto MSMONITOR = 1000.0 / g_pHyprRenderer->m_pMostHzMonitor->refreshRate;
|
const auto MSMONITOR = 1000.0 / g_pHyprRenderer->m_pMostHzMonitor->refreshRate;
|
||||||
|
@ -448,7 +582,13 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) {
|
||||||
|
|
||||||
if (g_pInputManager->dragMode == MBIND_MOVE) {
|
if (g_pInputManager->dragMode == MBIND_MOVE) {
|
||||||
|
|
||||||
CBox wb = {m_vBeginDragPositionXY + DELTA, DRAGGINGWINDOW->m_vRealSize.goal()};
|
Vector2D newPos = m_vBeginDragPositionXY + DELTA;
|
||||||
|
Vector2D newSize = DRAGGINGWINDOW->m_vRealSize.goal();
|
||||||
|
|
||||||
|
if (*SNAPENABLED && !DRAGGINGWINDOW->m_bDraggingTiled)
|
||||||
|
performSnap(newPos, newSize, DRAGGINGWINDOW, MBIND_MOVE, -1, m_vBeginDragSizeXY);
|
||||||
|
|
||||||
|
CBox wb = {newPos, newSize};
|
||||||
wb.round();
|
wb.round();
|
||||||
|
|
||||||
if (*PANIMATEMOUSE)
|
if (*PANIMATEMOUSE)
|
||||||
|
@ -479,9 +619,11 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) {
|
||||||
else if (m_eGrabbedCorner == CORNER_BOTTOMLEFT)
|
else if (m_eGrabbedCorner == CORNER_BOTTOMLEFT)
|
||||||
newSize = newSize + Vector2D(-DELTA.x, DELTA.y);
|
newSize = newSize + Vector2D(-DELTA.x, DELTA.y);
|
||||||
|
|
||||||
if ((m_vBeginDragSizeXY.x >= 1 && m_vBeginDragSizeXY.y >= 1) &&
|
eMouseBindMode mode = g_pInputManager->dragMode;
|
||||||
(g_pInputManager->dragMode == MBIND_RESIZE_FORCE_RATIO ||
|
if (DRAGGINGWINDOW->m_sWindowData.keepAspectRatio.valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO)
|
||||||
(!(g_pInputManager->dragMode == MBIND_RESIZE_BLOCK_RATIO) && DRAGGINGWINDOW->m_sWindowData.keepAspectRatio.valueOrDefault()))) {
|
mode = MBIND_RESIZE_FORCE_RATIO;
|
||||||
|
|
||||||
|
if (m_vBeginDragSizeXY.x >= 1 && m_vBeginDragSizeXY.y >= 1 && mode == MBIND_RESIZE_FORCE_RATIO) {
|
||||||
|
|
||||||
const float RATIO = m_vBeginDragSizeXY.y / m_vBeginDragSizeXY.x;
|
const float RATIO = m_vBeginDragSizeXY.y / m_vBeginDragSizeXY.x;
|
||||||
|
|
||||||
|
@ -510,6 +652,11 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) {
|
||||||
else if (m_eGrabbedCorner == CORNER_BOTTOMLEFT)
|
else if (m_eGrabbedCorner == CORNER_BOTTOMLEFT)
|
||||||
newPos = newPos + Vector2D((m_vBeginDragSizeXY - newSize).x, 0.0);
|
newPos = newPos + Vector2D((m_vBeginDragSizeXY - newSize).x, 0.0);
|
||||||
|
|
||||||
|
if (*SNAPENABLED) {
|
||||||
|
performSnap(newPos, newSize, DRAGGINGWINDOW, mode, m_eGrabbedCorner, m_vBeginDragSizeXY);
|
||||||
|
newSize = newSize.clamp(MINSIZE, MAXSIZE);
|
||||||
|
}
|
||||||
|
|
||||||
CBox wb = {newPos, newSize};
|
CBox wb = {newPos, newSize};
|
||||||
wb.round();
|
wb.round();
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,19 @@ struct SLayoutMessageHeader {
|
||||||
enum eFullscreenMode : int8_t;
|
enum eFullscreenMode : int8_t;
|
||||||
|
|
||||||
enum eRectCorner {
|
enum eRectCorner {
|
||||||
CORNER_NONE = 0,
|
CORNER_NONE = 0,
|
||||||
CORNER_TOPLEFT,
|
CORNER_TOPLEFT = (1 << 0),
|
||||||
CORNER_TOPRIGHT,
|
CORNER_TOPRIGHT = (1 << 1),
|
||||||
CORNER_BOTTOMRIGHT,
|
CORNER_BOTTOMRIGHT = (1 << 2),
|
||||||
CORNER_BOTTOMLEFT
|
CORNER_BOTTOMLEFT = (1 << 3),
|
||||||
|
};
|
||||||
|
|
||||||
|
enum eSnapEdge {
|
||||||
|
SNAP_INVALID = 0,
|
||||||
|
SNAP_UP = (1 << 0),
|
||||||
|
SNAP_DOWN = (1 << 1),
|
||||||
|
SNAP_LEFT = (1 << 2),
|
||||||
|
SNAP_RIGHT = (1 << 3),
|
||||||
};
|
};
|
||||||
|
|
||||||
enum eDirection {
|
enum eDirection {
|
||||||
|
|
Loading…
Reference in a new issue