Watch
1
0
Fork
You've already forked souveraine
0

shell: the overview arrives as one surface, and mission control finally lands

Everything drove off a visible/not-visible flag, so the cards appeared
rather than the overview opening. One shared `progress` now: the strip
slides 24px, each card takes its own share of it staggered by index and
capped at the fifth, and scale runs 0.86 to 1.0 — the reference shell's
numbers, driven from the same clock instead of a timer per card. The cap
is what stops the tenth card starting half a second after the first,
which reads as loading rather than opening.

This is also SHELL-ECOSYSTEM's stated model, taken from Phosh: state
gates visibility, never the reverse.

`missionControlOpen` has been set by the pill's second-stage swipe since
July and consumed by NOTHING — TASK-14 records the Auxo-like card
surface it was meant to raise as never built. WindowOverview is that
surface. The state reaches something now instead of being set and
dropped.

Mission control is the cards alone: no search, and no keyboard focus,
because it is "switch to what is running" and not "find something" — and
taking the keyboard would summon the OSK over a surface with no field.
This commit is contained in:
Fimeg 2026-08-03 22:18:43 -04:00
commit cc4bb83573
2 changed files with 57 additions and 17 deletions

View file

@ -40,17 +40,27 @@ Scope {
property string searchingText: "" property string searchingText: ""
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)
visible: GlobalStates.overviewOpen // Two ways in, one surface. The pill's second-stage swipe has set
// `missionControlOpen` since 2026-07, and TASK-14 records that NO
// SURFACE CONSUMES IT — the Auxo-like card surface it was meant to
// raise was never built. WindowOverview is that surface, so the state
// finally reaches something instead of being set and dropped.
//
// Mission control is the cards alone: no search, because it is a
// "switch to what is running" gesture, not a "find something" one.
visible: GlobalStates.overviewOpen || GlobalStates.missionControlOpen
WlrLayershell.namespace: "quickshell:overview" WlrLayershell.namespace: "quickshell:overview"
// Overlay, not Top: the second-stage edge swipe must bring the // Overlay, not Top: the second-stage edge swipe must bring the
// overview up over a fullscreen app (fullscreen renders above Top). // overview up over a fullscreen app (fullscreen renders above Top).
WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.layer: WlrLayer.Overlay
// Only the search half wants the keyboard. Mission control taking it
// would summon the OSK over the cards for a surface with no text field.
WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
color: "transparent" color: "transparent"
mask: Region { mask: Region {
item: GlobalStates.overviewOpen ? columnLayout : null item: (GlobalStates.overviewOpen || GlobalStates.missionControlOpen) ? columnLayout : null
} }
anchors { anchors {
@ -160,7 +170,8 @@ Scope {
Loader { Loader {
id: overviewLoader id: overviewLoader
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
active: GlobalStates.overviewOpen && (Config?.options.overview.enable ?? true) active: (GlobalStates.overviewOpen || GlobalStates.missionControlOpen)
&& (Config?.options.overview.enable ?? true)
// WindowOverview, not OverviewWidget. The latter draws a grid // WindowOverview, not OverviewWidget. The latter draws a grid
// of workspaces from HyprlandData; viewtop has neither, so it // of workspaces from HyprlandData; viewtop has neither, so it
// rendered an empty frame and read as the overview being // rendered an empty frame and read as the overview being

View file

@ -44,6 +44,26 @@ Item {
signal activated(var toplevel) signal activated(var toplevel)
signal closed(var toplevel) signal closed(var toplevel)
// How far open, 0..1. The reference shell drives its whole overview from
// one `progress` — cards read it, offset their own entry from it, and the
// strip slides on it — rather than each piece animating independently on a
// visible/not-visible flag. That is what makes it read as one surface
// arriving instead of several things appearing at once.
//
// It is also Phosh's model, which SHELL-ECOSYSTEM names as the one we
// study: state gates visibility, and the canonical line is
// `use_top_layer = !locked`. Visibility follows state; state is never set
// from a visibility handler.
property real progress: (GlobalStates.overviewOpen || GlobalStates.missionControlOpen) ? 1 : 0
Behavior on progress {
NumberAnimation {
duration: 260
// Standard-curve: quick to commit, slow to settle. A symmetric
// ease makes an overview feel like it is deciding.
easing.type: Easing.OutCubic
}
}
implicitWidth: parent ? parent.width : 540 implicitWidth: parent ? parent.width : 540
implicitHeight: parent ? parent.height : 800 implicitHeight: parent ? parent.height : 800
@ -61,6 +81,10 @@ Item {
id: list id: list
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.margins: 12
// The whole strip slides up as it opens, which is what ties the cards
// together into one surface. 24px is small on purpose — a long throw
// reads as a modal, and this is a place you pass through.
transform: Translate { y: (1 - root.progress) * 24 }
visible: root.count > 0 visible: root.count > 0
model: root.windows model: root.windows
// Horizontal, per TASK-14 ("horizontally swiped live app cards") and // Horizontal, per TASK-14 ("horizontally swiped live app cards") and
@ -104,20 +128,25 @@ Item {
property real dismissY: 0 property real dismissY: 0
property bool dismissed: false property bool dismissed: false
readonly property real dismissDistance: height * 0.32 readonly property real dismissDistance: height * 0.32
readonly property real introDelay: Math.min(index, 4) * 45
opacity: dismissed ? 0 : 1 - Math.min(1, Math.abs(dismissY) / (height * 0.8)) // Each card's own share of the opening, staggered by index and
// capped at the fifth — `interval(progress, i*0.045, 1)` in the
// reference. The cap is the part that matters: uncapped, the tenth
// card starts half a second after the first and the overview reads
// as loading rather than opening.
readonly property real intro: {
const begin = Math.min(index, 4) * 0.045;
if (root.progress <= begin) return 0;
return Math.min(1, (root.progress - begin) / (1 - begin));
}
opacity: dismissed
? 0
: root.progress * (1 - Math.min(1, Math.abs(dismissY) / (height * 0.8)))
transform: Translate { y: card.dismissY } transform: Translate { y: card.dismissY }
scale: 0.86 // 0.86 -> 1.0, the reference's numbers, driven by the shared
Component.onCompleted: introTimer.start() // progress rather than a timer per card.
Timer { scale: 0.86 + 0.14 * card.intro
id: introTimer
interval: card.introDelay
onTriggered: card.scale = 1.0
}
Behavior on scale {
NumberAnimation { duration: 220; easing.type: Easing.OutCubic }
}
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: 160 } NumberAnimation { duration: 160 }
} }
@ -167,8 +196,8 @@ Item {
// a render of that window every frame, and leaving them running // a render of that window every frame, and leaving them running
// behind a closed overview is the battery cost of drawing // behind a closed overview is the battery cost of drawing
// things nobody can see. // things nobody can see.
captureSource: GlobalStates.overviewOpen ? card.modelData : null captureSource: root.progress > 0 ? card.modelData : null
live: GlobalStates.overviewOpen live: root.progress > 0
// The preview is the target: tapping the picture of a window is // The preview is the target: tapping the picture of a window is
// how you go to it. // how you go to it.