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: ""
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
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"
// Overlay, not Top: the second-stage edge swipe must bring the
// overview up over a fullscreen app (fullscreen renders above Top).
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
color: "transparent"
mask: Region {
item: GlobalStates.overviewOpen ? columnLayout : null
item: (GlobalStates.overviewOpen || GlobalStates.missionControlOpen) ? columnLayout : null
}
anchors {
@ -160,7 +170,8 @@ Scope {
Loader {
id: overviewLoader
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
// of workspaces from HyprlandData; viewtop has neither, so it
// rendered an empty frame and read as the overview being

View file

@ -44,6 +44,26 @@ Item {
signal activated(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
implicitHeight: parent ? parent.height : 800
@ -61,6 +81,10 @@ Item {
id: list
anchors.fill: parent
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
model: root.windows
// Horizontal, per TASK-14 ("horizontally swiped live app cards") and
@ -104,20 +128,25 @@ Item {
property real dismissY: 0
property bool dismissed: false
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 }
scale: 0.86
Component.onCompleted: introTimer.start()
Timer {
id: introTimer
interval: card.introDelay
onTriggered: card.scale = 1.0
}
Behavior on scale {
NumberAnimation { duration: 220; easing.type: Easing.OutCubic }
}
// 0.86 -> 1.0, the reference's numbers, driven by the shared
// progress rather than a timer per card.
scale: 0.86 + 0.14 * card.intro
Behavior on opacity {
NumberAnimation { duration: 160 }
}
@ -167,8 +196,8 @@ Item {
// a render of that window every frame, and leaving them running
// behind a closed overview is the battery cost of drawing
// things nobody can see.
captureSource: GlobalStates.overviewOpen ? card.modelData : null
live: GlobalStates.overviewOpen
captureSource: root.progress > 0 ? card.modelData : null
live: root.progress > 0
// The preview is the target: tapping the picture of a window is
// how you go to it.