Watch
1
0
Fork
You've already forked souveraine
0

move is a mode: grab, drag, done

This commit is contained in:
Fimeg 2026-08-05 15:35:40 -04:00
commit c3f998f17a
3 changed files with 112 additions and 15 deletions

View file

@ -241,18 +241,41 @@ PanelWindow {
}
// Commit an upward gesture. `stage` is the stage the drag settled on
// (0/1/2). Higher stages imply the lower one, so Mission Control also
// reveals the dock beneath it.
// (0/1/2).
//
// Casey, 2026-08-05: "a proper toggle for the multitasking sections,
// that was the short swipe up deal, and long swipe up was to zone one
// which was to be clear and have widget space."
//
// So the two swipes are two different destinations rather than degrees
// of the same one: **short = multitasking, long = square one**. This
// used to be short = dock, long = multitasking, which left Home
// reachable only by tap and the tap was `hyprctl`, so under viewtop
// there was no way up and out at all.
//
// Short *toggles*. Reaching multitasking with a gesture that cannot
// also leave it means the way out is a different gesture than the way
// in, which is the thing that makes a phone feel stuck.
function commitUp(stage: int): void {
singleTapTimer.stop()
lastTapAt = -1
if (stage >= 1) {
GlobalStates.dockSuppressed = false
GlobalStates.dockRevealed = true
}
if (stage >= 2) {
GlobalStates.missionControlOpen = true
if (!Persistent.states.navigation.missionControlDiscovered)
// Square one. `goHome` clears multitasking, the overview, the
// dock and the OSK before moving, so the zone is not arrived at
// with the last screen's furniture still up.
goHome()
shortSwipes = 0
return
}
if (stage >= 1) {
GlobalStates.missionControlOpen = !GlobalStates.missionControlOpen
// The dock rides with it, as it did when multitasking lived on
// the long swipe: the two were always shown together and
// splitting them now would be a second change nobody asked for.
GlobalStates.dockSuppressed = false
GlobalStates.dockRevealed = GlobalStates.missionControlOpen
if (GlobalStates.missionControlOpen
&& !Persistent.states.navigation.missionControlDiscovered)
Persistent.states.navigation.missionControlDiscovered = true
shortSwipes = 0
}

View file

@ -50,6 +50,10 @@ Scope {
property bool sheetOpen: false
// Split has pinned a window and is waiting for the other half to be filled.
property bool choosing: false
// A window is grabbed and following the finger. The sheet is closed while
// this is true it would be covering the thing being moved so the only
// surface up is the Done bar.
property bool moving: false
// How long a hold on Close means kill. Longer than a tap could ever be,
// short enough that a stuck app does not feel like a negotiation.
@ -67,6 +71,14 @@ Scope {
function dismiss() {
scope.sheetOpen = false;
scope.choosing = false;
// A grab outlives the sheet on purpose Move closes the sheet to get
// out of the way so dismissing must not orphan one. Anything else
// would leave the compositor holding a window for a finger that has
// nothing left on screen telling it how to let go.
if (scope.moving) {
ViewtopControl.drop();
scope.moving = false;
}
scope.target = 0;
}
@ -112,6 +124,55 @@ Scope {
}
}
// The way out of move mode. A separate, small surface rather than part of
// the sheet: while moving, the whole screen has to stay reachable by the
// finger that is dragging the window, so a full-screen scrim would eat the
// gesture it exists to support.
Variants {
model: Quickshell.screens
PanelWindow {
id: moveBar
required property var modelData
screen: moveBar.modelData
anchors { bottom: true; left: true; right: true }
implicitHeight: 86
color: "transparent"
visible: scope.moving
WlrLayershell.namespace: "souveraine:windowmove"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
Rectangle {
anchors.centerIn: parent
implicitWidth: 220
implicitHeight: 58
radius: 29
color: Appearance.colors.colLayer1
border.width: 1
border.color: Appearance.colors.colLayer1Active
StyledText {
anchors.centerIn: parent
text: "Done moving"
color: Appearance.colors.colOnLayer1
font.pixelSize: Appearance.font.pixelSize.normal
}
MouseArea {
anchors.fill: parent
onClicked: {
ViewtopControl.drop();
scope.moving = false;
scope.target = 0;
}
}
}
}
}
Variants {
model: Quickshell.screens
@ -213,15 +274,16 @@ Scope {
spacing: 28
SheetButton {
icon: "open_in_full"
label: "Resize"
icon: "drag_pan"
label: "Move"
onTapped: {
// Half height, full width, parked at the top the
// `place` path, and the one geometry that is always
// legal on a phone. A drag-to-size handle is the real
// answer and it needs a gesture of its own; this is the
// verb reaching the window, which is what was missing.
ViewtopControl.place(scope.target, 0, 0, win.width, win.height / 2);
// Hands the window to the finger and gets out of the
// way: the sheet closes, one-finger drags move the
// window, and `drop` ends it. A one-shot `place` here
// would be the compositor guessing where you wanted it.
ViewtopControl.grab(scope.target);
scope.moving = true;
scope.sheetOpen = false;
}
}

View file

@ -121,6 +121,18 @@ Singleton {
});
}
// Hand a window to the finger. While grabbed, one-finger drags move it;
// `drop` ends the mode. This is Move as a *mode* rather than a computed
// geometry the three-finger carry that used to do it lost a race with
// the three-finger tap every time, so it is entered on purpose now.
function grab(id) {
root.scene("grab", { id: id });
}
function drop() {
root.scene("drop", {});
}
function raise(id) {
root.scene("raise", { id: id });
}