Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions docs/user/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,12 @@ The three-finger vertical swipe continues to switch workspaces. The
three-finger horizontal strip gesture is inert on vertical workspaces, so use
keyboard or wheel bindings to scroll the strip.

In the scrolling layout, Mod+Right-drag selects horizontal and vertical resize
edges from the outer thirds of a window. Dragging from a corner region resizes
both axes. Mod+Right-click in the center region starts no resize and instead
scrolls the focused window into view. When a tiled resize ends, the focused
scrolling column animates back into view.
Mod+Right-drag selects horizontal and vertical resize edges from the outer
thirds of both tiled and floating windows. Dragging from a corner region resizes
both axes. Mod+Right-click in the center region starts no resize and preserves
the window's maximize state. For tiled windows, a center click also scrolls the
focused window into view. When a tiled resize ends, the focused scrolling column
animates back into view.

When focus moves to a partially or fully hidden column, Umbriel scrolls by the
shortest distance needed to reveal it completely. A column entering from the
Expand Down
44 changes: 24 additions & 20 deletions src/input/cursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <linux/input-event-codes.h>
#include "wlr.h"
// clang-format on
#include "wlr/util/edges.h"
#include "workspace/scratchpad.h"
#include "workspace/workspace.h"

Expand Down Expand Up @@ -466,10 +467,6 @@ namespace umbriel {
tiled = false;
}

setActiveConstraint(nullptr);
if (view->maximizedToEdges()) {
view->setMaximizedToEdges(false);
}
if (tiled) {
Workspace* workspace = view->workspace();
if (workspace == nullptr || workspace->group() == nullptr || workspace->group()->output() == nullptr) {
Expand All @@ -486,6 +483,10 @@ namespace umbriel {
refreshInteractiveCursor();
return;
}
setActiveConstraint(nullptr);
if (view->maximizedToEdges()) {
view->setMaximizedToEdges(false);
}
const wlr_box usable = workspace->group()->output()->usableArea();
std::unique_ptr<ResizeGrab> session = layout.beginResize(view, resolvedEdges, usable);
if (session == nullptr) {
Expand All @@ -506,6 +507,14 @@ namespace umbriel {
updateInteractiveCursor(view);
return;
}
if (edges == 0) {
refreshInteractiveCursor();
return;
}
setActiveConstraint(nullptr);
if (view->maximizedToEdges()) {
view->setMaximizedToEdges(false);
}

const wlr_box& geometry = view->toplevel()->base->geometry;
const double borderX =
Expand Down Expand Up @@ -1689,24 +1698,19 @@ namespace umbriel {
const int y = view->sceneTree()->node.y + geo.y;
const double cx = m_cursor->x;
const double cy = m_cursor->y;
const double distLeft = std::abs(cx - x);
const double distRight = std::abs(cx - (x + geo.width));
const double distTop = std::abs(cy - y);
const double distBottom = std::abs(cy - (y + geo.height));
const double nearestH = std::min(distLeft, distRight);
const double nearestV = std::min(distTop, distBottom);
const double px = cx - x;
const double py = cy - y;

uint32_t edges = 0;
if (nearestH <= nearestV) {
edges |= distLeft <= distRight ? WLR_EDGE_LEFT : WLR_EDGE_RIGHT;
} else {
edges |= distTop <= distBottom ? WLR_EDGE_TOP : WLR_EDGE_BOTTOM;
}
// Prefer a corner when the cursor is near both axes.
constexpr double kCornerSlop = 32.0;
if (nearestH < kCornerSlop && nearestV < kCornerSlop) {
edges = (distLeft <= distRight ? WLR_EDGE_LEFT : WLR_EDGE_RIGHT)
| (distTop <= distBottom ? WLR_EDGE_TOP : WLR_EDGE_BOTTOM);
if (px < geo.width / 3.0) {
edges |= WLR_EDGE_LEFT;
} else if (px > 2.0 * geo.width / 3.0) {
edges |= WLR_EDGE_RIGHT;
}
if (py < geo.height / 3.0) {
edges |= WLR_EDGE_TOP;
} else if (py > 2.0 * geo.height / 3.0) {
edges |= WLR_EDGE_BOTTOM;
}
return edges;
}
Expand Down
90 changes: 90 additions & 0 deletions tests/harness/checks/515_resize_center.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# A center-region Mod+Right click proposes no resize edge. It must not begin an empty resize grab or clear maximize state.
set -euo pipefail

readonly BTN_RIGHT=273 # evdev BTN_RIGHT
readonly OUTPUT_W=1280
readonly OUTPUT_H=720
readonly POINTER="${UMBRIEL_POINTER_CLIENT:-./build-debug/pointer-client}"

spawn_client() {
foot sh -c 'sleep 120' > /dev/null 2>&1 &
}

pointer() {
"$POINTER" "$OUTPUT_W" "$OUTPUT_H" "$@"
}

wait_for_window() {
for _ in $(seq 60); do
[[ $("$UMBRIEL" windows --json | jq 'length') -eq 1 ]] && return 0
sleep 0.25
done
echo "timed out waiting for one window"
return 1
}

wait_for_floating() {
local want=$1
for _ in $(seq 40); do
[[ $("$UMBRIEL" windows --json | jq -r '.[0].floating') == "$want" ]] && return 0
sleep 0.1
done
echo "timed out waiting for floating=$want: $("$UMBRIEL" windows --json)"
return 1
}

wait_for_maximized_size() {
for _ in $(seq 40); do
local windows width height
windows=$("$UMBRIEL" windows --json)
width=$(jq -r '.[0].w' <<< "$windows")
height=$(jq -r '.[0].h' <<< "$windows")
(( width >= 1200 && height >= 700 )) && return 0
sleep 0.1
done
echo "timed out waiting for maximized geometry: $("$UMBRIEL" windows --json)"
return 1
}

center_resize_click() {
local windows x y
windows=$("$UMBRIEL" windows --json)
x=$(jq -r '.[0].x + (.[0].w / 2 | floor)' <<< "$windows")
y=$(jq -r '.[0].y + (.[0].h / 2 | floor)' <<< "$windows")
pointer move "$x" "$y" mod logo click "$BTN_RIGHT" mod none
sleep 0.8
}

check_maximized_size_unchanged() {
local kind=$1 before after before_size after_size
before=$("$UMBRIEL" windows --json)
before_size=$(jq -r '.[0] | "\(.w)x\(.h)"' <<< "$before")
center_resize_click
after=$("$UMBRIEL" windows --json)
after_size=$(jq -r '.[0] | "\(.w)x\(.h)"' <<< "$after")
if [[ $after_size != "$before_size" ]]; then
echo "$kind center resize click changed maximized geometry: $before_size to $after_size"
return 1
fi
}

spawn_client
wait_for_window

"$UMBRIEL" msg window-toggle-maximize-to-edges > /dev/null
wait_for_maximized_size
failed=0
check_maximized_size_unchanged tiled || failed=1

"$UMBRIEL" msg window-toggle-floating > /dev/null
wait_for_floating true
"$UMBRIEL" msg window-toggle-maximize-to-edges > /dev/null
wait_for_maximized_size
check_maximized_size_unchanged floating || failed=1

if (( failed != 0 )); then
exit 1
fi

echo "center resize clicks preserve tiled and floating maximize state"
8 changes: 4 additions & 4 deletions tests/unit/presentation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
#include "wlr.h"
// clang-format on

// A fullscreen client whose buffer does not match the output is centered rather than scaled, and that centering lives in
// the scene node's position. The wlroots xdg scene helper rewrites that position to (-geometry.x, -geometry.y) on every
// commit, so the offset has to be re-applied afterwards or an oversized fullscreen buffer drifts to the top left on the
// next frame the client draws.
// A fullscreen client whose buffer does not match the output is centered rather than scaled, and that centering lives
// in the scene node's position. The wlroots xdg scene helper rewrites that position to (-geometry.x, -geometry.y) on
// every commit, so the offset has to be re-applied afterwards or an oversized fullscreen buffer drifts to the top left
// on the next frame the client draws.
UMBRIEL_TEST(fullscreenCenteringSurvivesSceneReconfiguration) {
wlr_scene* scene = wlr_scene_create();
CHECK(scene != nullptr);
Expand Down