Give the revealed Dock back the pointer that revealed it
The bottom dock came up when the cursor reached the edge and hid again a quarter-second later with the cursor still sitting there. The input region has two shapes: a three-pixel strip along the whole edge while hidden, and a region over the body once revealed. Teaching the dock about left and right rewrote both, and the bottom case was folded into the branch that serves a left dock -- x 0. That is right for a dock that hugs the left edge and wrong for one that is centred on the bottom: the region landed on the left third of the screen while the pointer that summoned the dock was in the middle. Hover dropped on the very frame the dock arrived, and the hide timer did the rest. Approaching from the far left worked, which is the only reason it looked intermittent rather than broken. Bottom is centred, so the region starts where the body starts. Nothing measured the input region, which is why "bottom is unchanged" passed while bottom was broken -- the contract read the window and the window was fine. It now probes the mask in both states on all three edges and asserts the point a hand actually aims at, the middle of the edge the dock lives on, is still inside the region after the dock arrives. It fails on the old binding with the coordinates that were wrong. Claude-Session: https://claude.ai/code/session_01NvgBuSWB5sE43yWmg21ozj
This commit is contained in:
@@ -177,12 +177,21 @@ PanelWindow {
|
|||||||
x: {
|
x: {
|
||||||
if (!root.revealed)
|
if (!root.revealed)
|
||||||
return root.position === "right" ? surface.width - root.revealStripHeight : 0;
|
return root.position === "right" ? surface.width - root.revealStripHeight : 0;
|
||||||
|
// A horizontal dock is centred on its edge, so the region has
|
||||||
|
// to start where the body starts -- starting at 0 puts it over
|
||||||
|
// the left of the screen while the pointer that summoned the
|
||||||
|
// dock is in the middle, and the hover drops on the very frame
|
||||||
|
// the dock arrives. A vertical dock reaches from the body out
|
||||||
|
// to its own edge, which is x 0 on the left and the body on
|
||||||
|
// the right.
|
||||||
|
if (!root.vertical)
|
||||||
|
return body.x;
|
||||||
return root.position === "right" ? body.x : 0;
|
return root.position === "right" ? body.x : 0;
|
||||||
}
|
}
|
||||||
y: {
|
y: {
|
||||||
if (!root.revealed)
|
if (!root.revealed)
|
||||||
return root.vertical ? 0 : surface.height - root.revealStripHeight;
|
return root.vertical ? 0 : surface.height - root.revealStripHeight;
|
||||||
return root.vertical ? body.y : body.y;
|
return body.y;
|
||||||
}
|
}
|
||||||
width: {
|
width: {
|
||||||
if (!root.revealed)
|
if (!root.revealed)
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
# 6. The drag grip sets preventStealing. Without it the settings page's own
|
# 6. The drag grip sets preventStealing. Without it the settings page's own
|
||||||
# Flickable claims the vertical gesture and the row never moves -- which is
|
# Flickable claims the vertical gesture and the row never moves -- which is
|
||||||
# the exact objection this feature was refused over for a long time.
|
# the exact objection this feature was refused over for a long time.
|
||||||
|
# 7. The point that reveals the dock is still inside the input region once the
|
||||||
|
# dock has revealed. The region follows the body, and a bottom dock is
|
||||||
|
# centred while its reveal strip spans the whole edge -- so a region that
|
||||||
|
# forgets the body's own offset lands somewhere the pointer is not, hover
|
||||||
|
# drops on the frame the dock arrives, and it hides under a still cursor.
|
||||||
#
|
#
|
||||||
# The geometry checks launch isolated shells against a temporary config. The
|
# The geometry checks launch isolated shells against a temporary config. The
|
||||||
# real settings are read to build them and never written.
|
# real settings are read to build them and never written.
|
||||||
@@ -81,7 +86,7 @@ for start, targets in [(0, [1, 2, 3, 4]), (4, [3, 2, 1, 0]), (2, [3]), (2, [1]),
|
|||||||
raise SystemExit(f'starting at {start} through {targets} produced {working}')
|
raise SystemExit(f'starting at {start} through {targets} produced {working}')
|
||||||
PY
|
PY
|
||||||
|
|
||||||
# ── 1, 2. Geometry, measured ────────────────────────────────────────────────
|
# ── 1, 2, 7. Geometry, measured ────────────────────────────────────────────────
|
||||||
|
|
||||||
cat >"$shell_dir/dock-position-probe.qml" <<'QML'
|
cat >"$shell_dir/dock-position-probe.qml" <<'QML'
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -90,6 +95,19 @@ import qs.modules.dock
|
|||||||
|
|
||||||
ShellRoot {
|
ShellRoot {
|
||||||
Dock { id: probe }
|
Dock { id: probe }
|
||||||
|
|
||||||
|
// The input region is measured in both states, because the bug it exists
|
||||||
|
// for lives in the transition between them: a region that stops covering
|
||||||
|
// the pointer the moment the dock arrives takes the hover away with it.
|
||||||
|
function report(state) {
|
||||||
|
const m = probe.mask.item;
|
||||||
|
console.warn("MASKGEOM " + probe.position + " " + state
|
||||||
|
+ " surfaceW=" + Math.round(probe.width)
|
||||||
|
+ " surfaceH=" + Math.round(probe.height)
|
||||||
|
+ " x=" + Math.round(m.x) + " y=" + Math.round(m.y)
|
||||||
|
+ " w=" + Math.round(m.width) + " h=" + Math.round(m.height));
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 1200; running: true
|
interval: 1200; running: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
@@ -97,6 +115,26 @@ ShellRoot {
|
|||||||
+ " vertical=" + probe.vertical
|
+ " vertical=" + probe.vertical
|
||||||
+ " w=" + Math.round(probe.width)
|
+ " w=" + Math.round(probe.width)
|
||||||
+ " h=" + Math.round(probe.height));
|
+ " h=" + Math.round(probe.height));
|
||||||
|
probe.revealed = false;
|
||||||
|
hidden.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Long enough for the slide to finish; the region follows the body, so
|
||||||
|
// measuring mid-animation measures nothing in particular.
|
||||||
|
Timer {
|
||||||
|
id: hidden
|
||||||
|
interval: 400
|
||||||
|
onTriggered: {
|
||||||
|
report("hidden");
|
||||||
|
probe.revealed = true;
|
||||||
|
shown.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timer {
|
||||||
|
id: shown
|
||||||
|
interval: 400
|
||||||
|
onTriggered: {
|
||||||
|
report("revealed");
|
||||||
Qt.quit();
|
Qt.quit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,15 +155,20 @@ out.parent.mkdir(parents=True, exist_ok=True)
|
|||||||
out.write_text(json.dumps(data))
|
out.write_text(json.dumps(data))
|
||||||
PY
|
PY
|
||||||
( cd "$shell_dir" && XDG_CONFIG_HOME="$work" timeout 40 qs -p ./dock-position-probe.qml 2>&1 ) \
|
( cd "$shell_dir" && XDG_CONFIG_HOME="$work" timeout 40 qs -p ./dock-position-probe.qml 2>&1 ) \
|
||||||
| grep -o 'DOCKGEOM .*' | head -1
|
| grep -oE '(DOCKGEOM|MASKGEOM) .*'
|
||||||
}
|
}
|
||||||
|
|
||||||
bottom="$(measure bottom)"
|
bottom="$(measure bottom)"
|
||||||
[[ -n "$bottom" ]] || fail 'the bottom dock produced no geometry at all'
|
[[ -n "$bottom" ]] || fail 'the bottom dock produced no geometry at all'
|
||||||
left="$(measure left)"
|
left="$(measure left)"
|
||||||
[[ -n "$left" ]] || fail 'the left dock produced no geometry at all'
|
[[ -n "$left" ]] || fail 'the left dock produced no geometry at all'
|
||||||
|
right="$(measure right)"
|
||||||
|
[[ -n "$right" ]] || fail 'the right dock produced no geometry at all'
|
||||||
|
|
||||||
python3 - "$bottom" "$left" <<'PY' || fail 'a dock does not span the edge it lives on'
|
# The span checks read the window; the region checks read every line.
|
||||||
|
first_dock() { grep -o 'DOCKGEOM .*' <<<"$1" | head -1; }
|
||||||
|
|
||||||
|
python3 - "$(first_dock "$bottom")" "$(first_dock "$left")" <<'PY' || fail 'a dock does not span the edge it lives on'
|
||||||
import re, sys
|
import re, sys
|
||||||
|
|
||||||
def read(line):
|
def read(line):
|
||||||
@@ -152,4 +195,44 @@ if left_w >= bottom_w or bottom_h >= left_h:
|
|||||||
raise SystemExit('the two orientations are not thin on opposite axes')
|
raise SystemExit('the two orientations are not thin on opposite axes')
|
||||||
PY
|
PY
|
||||||
|
|
||||||
|
# ── 7. The pointer that reveals the dock is still inside the region ─────────
|
||||||
|
|
||||||
|
python3 - "$bottom" "$left" "$right" <<'REGION' || fail 'revealing the dock moves the input region off the pointer that revealed it'
|
||||||
|
import re, sys
|
||||||
|
|
||||||
|
FIELDS = re.compile(
|
||||||
|
r'MASKGEOM (\w+) (\w+) surfaceW=(\d+) surfaceH=(\d+) '
|
||||||
|
r'x=(-?\d+) y=(-?\d+) w=(-?\d+) h=(-?\d+)')
|
||||||
|
|
||||||
|
def regions(blob):
|
||||||
|
found = {}
|
||||||
|
for line in blob.splitlines():
|
||||||
|
m = FIELDS.search(line)
|
||||||
|
if m:
|
||||||
|
found[m.group(2)] = (m.group(1),) + tuple(int(g) for g in m.groups()[2:])
|
||||||
|
return found
|
||||||
|
|
||||||
|
# Where a hand actually goes to summon the dock: the middle of the edge it
|
||||||
|
# lives on, a pixel in from that edge.
|
||||||
|
def aim(position, surface_w, surface_h):
|
||||||
|
if position == "left":
|
||||||
|
return 1, surface_h / 2
|
||||||
|
if position == "right":
|
||||||
|
return surface_w - 1, surface_h / 2
|
||||||
|
return surface_w / 2, surface_h - 1
|
||||||
|
|
||||||
|
for blob in sys.argv[1:]:
|
||||||
|
found = regions(blob)
|
||||||
|
for state in ("hidden", "revealed"):
|
||||||
|
if state not in found:
|
||||||
|
raise SystemExit(f'the probe reported no {state} input region')
|
||||||
|
position, surface_w, surface_h, x, y, w, h = found[state]
|
||||||
|
px, py = aim(position, surface_w, surface_h)
|
||||||
|
if not (x <= px <= x + w and y <= py <= y + h):
|
||||||
|
raise SystemExit(
|
||||||
|
f'the {position} dock {state} takes input over x {x}..{x + w}, '
|
||||||
|
f'y {y}..{y + h}, which does not contain the pointer at '
|
||||||
|
f'({px:.0f}, {py:.0f})')
|
||||||
|
REGION
|
||||||
|
|
||||||
printf 'dock position contract: ok\n'
|
printf 'dock position contract: ok\n'
|
||||||
|
|||||||
Reference in New Issue
Block a user