hyprland: dwindle resize only moves the dragged edge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-20 10:25:17 +01:00
parent 81b8cd173d
commit fa0a654f5f
2 changed files with 99 additions and 1 deletions

View file

@ -0,0 +1,91 @@
--- a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp
+++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp
@@ -297,6 +297,56 @@
recalculate();
}
+// Moving a split only changes that node's ratio, so every split nested on
+// either side of it gets rescaled and edges that weren't dragged slide along.
+// Record the size of each region that doesn't touch the moved edge, so it can
+// be pinned afterwards and only the windows along the edge absorb the resize.
+struct SFarSide {
+ SP<SDwindleNodeData> node;
+ bool farIsSecond = false;
+ float size = 0;
+};
+using FarSides = std::vector<SFarSide>;
+
+static void collectFarSides(const SP<SDwindleNodeData>& node, bool vertical, bool edgeAtStart, FarSides& out) {
+ if (!node || !node->children[0] || !node->children[1])
+ return;
+
+ const auto C0 = node->children[0].lock();
+ const auto C1 = node->children[1].lock();
+
+ if (node->splitTop == vertical) {
+ // split along the resize axis: pin the far child, follow the near one
+ const auto FARCHILD = edgeAtStart ? C1 : C0;
+ out.push_back({node, edgeAtStart, (float)(vertical ? FARCHILD->box.h : FARCHILD->box.w)});
+ collectFarSides(edgeAtStart ? C0 : C1, vertical, edgeAtStart, out);
+ } else {
+ // perpendicular split: both children touch the edge
+ collectFarSides(C0, vertical, edgeAtStart, out);
+ collectFarSides(C1, vertical, edgeAtStart, out);
+ }
+}
+
+static FarSides collectFarSides(const SP<SDwindleNodeData>& split, bool vertical) {
+ FarSides out;
+ collectFarSides(split->children[0].lock(), vertical, false, out);
+ collectFarSides(split->children[1].lock(), vertical, true, out);
+ return out;
+}
+
+// parents come before their children in `far`, so each node's box is already
+// final by the time its own ratio is recomputed
+static void restoreFarSides(const FarSides& far, bool vertical, bool force) {
+ for (const auto& [node, farIsSecond, size] : far) {
+ const float TOTAL = vertical ? node->box.h : node->box.w;
+ if (TOTAL <= 0)
+ continue;
+
+ node->splitRatio = std::clamp(farIsSecond ? 2.f - size / TOTAL * 2.f : size / TOTAL * 2.f, 0.1F, 1.9F);
+ node->recalcSizePosRecursive(force);
+ }
+}
+
void CDwindleAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
if (!validMapped(target->window()))
return;
@@ -356,6 +406,8 @@
}
if (PHOUTER) {
+ const auto HFAR = collectFarSides(PHOUTER->pParent.lock(), false);
+
PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9);
if (PHINNER) {
@@ -368,9 +420,13 @@
PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
} else
PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
+
+ restoreFarSides(HFAR, false, *PANIMATE == 0);
}
if (PVOUTER) {
+ const auto VFAR = collectFarSides(PVOUTER->pParent.lock(), true);
+
PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9);
if (PVINNER) {
@@ -383,6 +439,8 @@
PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
} else
PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
+
+ restoreFarSides(VFAR, true, *PANIMATE == 0);
}
} else {
// get the correct containers to apply splitratio to

View file

@ -11,6 +11,11 @@ in
programs.hyprland = { programs.hyprland = {
enable = true; enable = true;
xwayland.enable = true; xwayland.enable = true;
# Dragging a tiled edge only resizes the windows touching it; stock
# dwindle rescales the whole subtree beyond it, so untouched edges slid.
package = pkgs.hyprland.overrideAttrs (old: {
patches = (old.patches or [ ]) ++ [ ../patches/hyprland-dwindle-pin-far-sides.patch ];
});
# Closing a window that's being shared ends the share (Discord stops the # Closing a window that's being shared ends the share (Discord stops the
# stream) instead of streaming its last frame forever. # stream) instead of streaming its last frame forever.
portalPackage = pkgs.xdg-desktop-portal-hyprland.overrideAttrs (old: { portalPackage = pkgs.xdg-desktop-portal-hyprland.overrideAttrs (old: {
@ -72,7 +77,7 @@ in
libcanberra-gtk3 libcanberra-gtk3
]; ];
home-manager.users.fred = { config, lib, pkgs, inputs, ... }: home-manager.users.fred = { config, osConfig, lib, pkgs, inputs, ... }:
let let
c = config.lib.stylix.colors; c = config.lib.stylix.colors;
rgb = hex: "rgb(${hex})"; rgb = hex: "rgb(${hex})";
@ -218,6 +223,8 @@ in
wayland.windowManager.hyprland = { wayland.windowManager.hyprland = {
enable = true; enable = true;
configType = "lua"; configType = "lua";
# same patched build as programs.hyprland, not a second stock copy
package = osConfig.programs.hyprland.package;
systemd.variables = [ "--all" ]; systemd.variables = [ "--all" ];
settings = { settings = {