Watch
1
0
Fork
You've already forked souveraine
0

the app scales into multitasking; pill has two destinations

This commit is contained in:
Fimeg 2026-08-05 16:34:51 -04:00
commit 0e7dec53d1
3 changed files with 97 additions and 0 deletions

View file

@ -273,6 +273,12 @@ PanelWindow {
// Square one. `goHome` clears multitasking, the overview, the // Square one. `goHome` clears multitasking, the overview, the
// dock and the OSK before moving, so the zone is not arrived at // dock and the OSK before moving, so the zone is not arrived at
// with the last screen's furniture still up. // with the last screen's furniture still up.
//
// The pose is released here rather than on arrival: home is a
// different zone, and a window left at 0.6 on the zone behind
// you is one you would find shrunken next time you swiped back
// to it, with no gesture in flight to explain why.
ViewtopControl.clearPose()
goHome() goHome()
shortSwipes = 0 shortSwipes = 0
return return
@ -282,6 +288,11 @@ PanelWindow {
// belongs to zone one and is shown by being *there*, not by a // belongs to zone one and is shown by being *there*, not by a
// gesture that reveals it over whatever else is on screen. // gesture that reveals it over whatever else is on screen.
GlobalStates.missionControlOpen = !GlobalStates.missionControlOpen GlobalStates.missionControlOpen = !GlobalStates.missionControlOpen
// Multitasking draws its own cards over the scene, so the live
// windows go back to full size underneath it. Leaving them
// posed would mean two shrunken pictures of the same app
// the card and the window behind it.
ViewtopControl.clearPose()
if (GlobalStates.missionControlOpen if (GlobalStates.missionControlOpen
&& !Persistent.states.navigation.missionControlDiscovered) && !Persistent.states.navigation.missionControlDiscovered)
Persistent.states.navigation.missionControlDiscovered = true Persistent.states.navigation.missionControlDiscovered = true
@ -302,6 +313,24 @@ PanelWindow {
// Upward travel is positive; downward drags leave travel at 0 so // Upward travel is positive; downward drags leave travel at 0 so
// the handle doesn't chase a dismiss gesture. // the handle doesn't chase a dismiss gesture.
rail.dragTravel = Math.max(0, startY - mouse.y) rail.dragTravel = Math.max(0, startY - mouse.y)
// The app shrinks under the thumb. Casey, 2026-08-05: *"swipe up
// just a bit makes the app sorta scale and you fall into a multi
// tasking area"* and Phosh's home is the same shape, a drag
// surface travelling between two states rather than a button that
// teleports.
//
// Scaled against `missionAt`, the multitasking detent, so the app
// has visibly become a card by the moment it would commit. It
// keeps shrinking past that toward home, which is what makes the
// long pull feel like a further degree of the same motion instead
// of a second, unrelated gesture.
//
// Nothing is scaled below 0.6: past that the window is a thumbnail
// and its own inverse-mapped touch targets stop being findable if
// the drag is abandoned there.
const progress = Math.min(1.0, rail.dragTravel / Math.max(1, rail.missionAt))
ViewtopControl.poseActiveZone(1.0 - 0.4 * progress)
} }
onReleased: mouse => { onReleased: mouse => {
@ -347,6 +376,12 @@ PanelWindow {
return return
} }
// A climb that did not reach the first detent. The app was shrinking
// under the thumb and has to spring back, or an abandoned gesture
// leaves a permanently smaller window and no way to say so.
if (wasDragging)
ViewtopControl.clearPose()
// Downward: peel the nearest surface (keyboard, then dock). // Downward: peel the nearest surface (keyboard, then dock).
if (deltaY >= rail.revealAt) { if (deltaY >= rail.revealAt) {
singleTapTimer.stop() singleTapTimer.stop()
@ -412,6 +447,10 @@ PanelWindow {
rail.dragging = false rail.dragging = false
rail.dragTravel = 0 rail.dragTravel = 0
lastTapAt = -1 lastTapAt = -1
// A cancel is the compositor taking the sequence away mid-drag
// the FTS controller does exactly this after a wake. The scale must
// still come home; nothing else will do it.
ViewtopControl.clearPose()
} }
} }
} }

View file

@ -54,6 +54,11 @@ Scope {
// this is true it would be covering the thing being moved so the only // this is true it would be covering the thing being moved so the only
// surface up is the Done bar. // surface up is the Done bar.
property bool moving: false property bool moving: false
// Which size Resize last applied. Kept on the scope rather than the button
// so it survives the sheet closing and reopening on the same window
// tapping Resize twice in two visits should continue the cycle, not restart
// it and appear to do nothing.
property int sizeStep: 0
// How long a hold on Close means kill. Longer than a tap could ever be, // 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. // short enough that a stuck app does not feel like a negotiation.
@ -287,6 +292,27 @@ Scope {
} }
} }
SheetButton {
icon: "aspect_ratio"
label: "Resize"
// Cycles the window through sizes rather than opening a
// drag-handle mode: Move already owns "follow my finger",
// and a second finger-driven mode would need a way to tell
// them apart mid-gesture. Tapping through half two-thirds
// full back is reachable one-handed and needs no
// second gesture vocabulary.
//
// A real corner handle is still owed; this is the verb
// reaching the window, which is what was missing when the
// button said Move and there was no Resize at all.
onTapped: {
scope.sizeStep = (scope.sizeStep + 1) % 3;
const frac = [0.5, 0.67, 1.0][scope.sizeStep];
ViewtopControl.place(scope.target, 0, 0,
win.width, Math.round(win.height * frac));
}
}
SheetButton { SheetButton {
icon: "splitscreen" icon: "splitscreen"
label: "Split" label: "Split"

View file

@ -156,6 +156,38 @@ Singleton {
root.scene("drop", {}); root.scene("drop", {});
} }
// Scale every window on the zone in front, live, as a drag progresses.
//
// This is the iOS/Android "the app shrinks into a card while your thumb
// climbs" feel, and it is the first thing in the tree to actually drive
// `pose`. The verb and its inverse input-mapping have been implemented and
// tested in the compositor for weeks with nothing calling them, which is
// the whole reason the behaviour has never been seen on the device.
//
// Throttled on the value, not on a timer: a drag emits a motion event per
// frame and each one would otherwise be a socket round-trip. Anything
// smaller than a percent of scale is invisible and not worth a write.
property real _lastPose: 1.0
function poseActiveZone(scale) {
if (Math.abs(scale - root._lastPose) < 0.01)
return;
root._lastPose = scale;
for (const w of root.windows) {
if (w.workspace === root.activeZone)
root.pose(w.id, scale, 0.0, 0.5, 0.5);
}
}
// Put them back. Called on every path out of a drag commit, abandon and
// cancel because a window left posed by a gesture that ended is a window
// the user cannot restore without knowing a verb exists.
function clearPose() {
root._lastPose = 1.0;
for (const w of root.windows)
root.pose(w.id, 1.0, 0.0, 0.5, 0.5);
}
function raise(id) { function raise(id) {
root.scene("raise", { id: id }); root.scene("raise", { id: id });
} }