feat: improve popouts

Fix bg tracking, use slow spatial for detach, default spatial for
everything else, adjust deform, move instead of width scale
This commit is contained in:
2 * r + 2 * t 2026-04-08 17:06:37 +10:00
parent 293b6f4b2b
commit df27c93224
8 changed files with 101 additions and 79 deletions

View file

@ -0,0 +1,55 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.components
import qs.config
Item {
id: root
required property ShellScreen screen
readonly property alias content: content
property real offsetScale: x > 0 || content.hasCurrent ? 0 : 1
visible: width > 0 && height > 0
clip: true
implicitWidth: content.implicitWidth * (1 - offsetScale)
implicitHeight: content.implicitHeight
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on x {
Anim {
duration: content.animLength
easing.bezierCurve: content.animCurve
}
}
Behavior on y {
enabled: root.offsetScale < 1
Anim {
duration: content.animLength
easing.bezierCurve: content.animCurve
}
}
Wrapper {
id: content
screen: root.screen
offsetScale: root.offsetScale
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: (-implicitWidth - 5) * root.offsetScale
}
}

View file

@ -14,46 +14,50 @@ Item {
id: root id: root
required property ShellScreen screen required property ShellScreen screen
required property real offsetScale
readonly property real shownWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth readonly property alias content: content
readonly property real nonAnimWidth: x > 0 || hasCurrent ? shownWidth : 0 readonly property alias winfo: winfo
readonly property alias controlCenter: controlCenter
readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth
readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight
readonly property Item current: (content.item as Content)?.current ?? null readonly property Item current: (content.item as Content)?.current ?? null
readonly property bool isDetached: detachedMode.length > 0
property alias currentName: popoutState.currentName property alias currentName: popoutState.currentName
property real currentCenter
property alias hasCurrent: popoutState.hasCurrent property alias hasCurrent: popoutState.hasCurrent
readonly property PopoutState popState: popoutState property real currentCenter
property string detachedMode property string detachedMode
property string queuedMode property string queuedMode
readonly property bool isDetached: detachedMode.length > 0
property int animLength: Appearance.anim.durations.normal property int animLength: Appearance.anim.durations.expressiveDefaultSpatial
property list<real> animCurve: Appearance.anim.curves.emphasized property list<real> animCurve: Appearance.anim.curves.expressiveDefaultSpatial
function setAnims(detach: bool): void {
const type = `expressive${detach ? "Slow" : "Default"}Spatial`;
animLength = Appearance.anim.durations[type];
animCurve = Appearance.anim.curves[type];
}
function detach(mode: string): void { function detach(mode: string): void {
animLength = Appearance.anim.durations.large; setAnims(true);
if (mode === "winfo") { if (mode === "winfo") {
detachedMode = mode; detachedMode = mode;
} else { } else {
queuedMode = mode; queuedMode = mode;
detachedMode = "any"; detachedMode = "any";
} }
setAnims(false);
focus = true; focus = true;
} }
function close(): void { function close(): void {
hasCurrent = false; hasCurrent = false;
animCurve = Appearance.anim.curves.emphasizedAccel;
animLength = Appearance.anim.durations.normal;
detachedMode = ""; detachedMode = "";
animCurve = Appearance.anim.curves.emphasized;
} }
visible: width > 0 && height > 0
clip: true
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
implicitHeight: nonAnimHeight implicitHeight: nonAnimHeight
@ -90,15 +94,7 @@ Item {
} }
Binding { Binding {
when: root.isDetached when: root.isDetached || (root.hasCurrent && root.currentName === "wirelesspassword")
target: QsWindow.window
property: "WlrLayershell.keyboardFocus"
value: WlrKeyboardFocus.OnDemand
}
Binding {
when: root.hasCurrent && root.currentName === "wirelesspassword"
target: QsWindow.window target: QsWindow.window
property: "WlrLayershell.keyboardFocus" property: "WlrLayershell.keyboardFocus"
@ -118,6 +114,8 @@ Item {
} }
Comp { Comp {
id: winfo
shouldBeActive: root.detachedMode === "winfo" shouldBeActive: root.detachedMode === "winfo"
anchors.centerIn: parent anchors.centerIn: parent
@ -128,32 +126,15 @@ Item {
} }
Comp { Comp {
id: controlCenter
shouldBeActive: root.detachedMode === "any" shouldBeActive: root.detachedMode === "any"
anchors.centerIn: parent anchors.centerIn: parent
sourceComponent: ControlCenter { sourceComponent: ControlCenter {
function close(): void {
root.close();
}
screen: root.screen screen: root.screen
active: root.queuedMode active: root.queuedMode
} onClose: root.close()
}
Behavior on x {
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
Behavior on y {
enabled: root.implicitWidth > 0
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
} }
} }
@ -165,7 +146,7 @@ Item {
} }
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: root.implicitWidth > 0 enabled: root.offsetScale < 1
Anim { Anim {
duration: root.animLength duration: root.animLength
@ -181,6 +162,7 @@ Item {
active: false active: false
opacity: 0 opacity: 0
// Makes the loader load on the same frame shouldBeActive becomes true, which ensures size is set
states: State { states: State {
name: "active" name: "active"
when: comp.shouldBeActive when: comp.shouldBeActive

View file

@ -25,8 +25,7 @@ Item {
root: root root: root
} }
function close(): void { signal close
}
implicitWidth: implicitHeight * Config.controlCenter.sizes.ratio implicitWidth: implicitHeight * Config.controlCenter.sizes.ratio
implicitHeight: screen.height * Config.controlCenter.sizes.heightMult implicitHeight: screen.height * Config.controlCenter.sizes.heightMult

View file

@ -45,12 +45,9 @@ Singleton {
ControlCenter { ControlCenter {
id: cc id: cc
function close(): void {
win.destroy();
}
anchors.fill: parent anchors.fill: parent
screen: win.screen screen: win.screen
onClose: win.destroy()
floating: true floating: true
} }

View file

@ -211,23 +211,10 @@ StyledWindow {
PanelBg { PanelBg {
id: popoutBg id: popoutBg
panel: panels.popouts panel: panels.popoutsWrapper
x: bar.implicitWidth - (panels.popouts.isDetached ? -(root.width - panels.popouts.shownWidth) / 2 : panels.popouts.hasCurrent ? 0 : panels.popouts.shownWidth + 5) deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1
implicitWidth: panels.popouts.shownWidth x: panels.popoutsWrapper.x + panels.popouts.x + bar.implicitWidth
implicitWidth: panels.popouts.width
Behavior on x {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on implicitWidth {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
} }
} }

View file

@ -211,7 +211,7 @@ CustomMouseArea {
// Show popouts on hover // Show popouts on hover
if (x < bar.implicitWidth) { if (x < bar.implicitWidth) {
bar.checkPopout(y); bar.checkPopout(y);
} else if ((!popouts.currentName.startsWith("traymenu") || ((popouts.current as StackView)?.depth ?? 0) <= 1) && !inLeftPanel(panels.popouts, x, y)) { } else if ((!popouts.currentName.startsWith("traymenu") || ((popouts.current as StackView)?.depth ?? 0) <= 1) && !inLeftPanel(panels.popoutsWrapper, x, y)) {
popouts.hasCurrent = false; popouts.hasCurrent = false;
bar.closeTray(); bar.closeTray();
} }

View file

@ -28,13 +28,14 @@ Item {
readonly property alias sessionWrapper: sessionWrapper readonly property alias sessionWrapper: sessionWrapper
readonly property alias launcher: launcher readonly property alias launcher: launcher
readonly property alias dashboard: dashboard readonly property alias dashboard: dashboard
readonly property alias popouts: popouts readonly property alias popouts: popoutsWrapper.content
readonly property alias popoutsWrapper: popoutsWrapper
readonly property alias utilities: utilities readonly property alias utilities: utilities
readonly property alias toasts: toasts readonly property alias toasts: toasts
readonly property alias sidebar: sidebar readonly property alias sidebar: sidebar
anchors.fill: parent anchors.fill: parent
anchors.margins: root.borderThickness anchors.margins: borderThickness
anchors.leftMargin: bar.implicitWidth anchors.leftMargin: bar.implicitWidth
Item { Item {
@ -114,18 +115,18 @@ Item {
anchors.top: parent.top anchors.top: parent.top
} }
BarPopouts.Wrapper { BarPopouts.ClipWrapper {
id: popouts id: popoutsWrapper
screen: root.screen screen: root.screen
x: isDetached ? (root.width - nonAnimWidth) / 2 : 0 x: content.isDetached ? (root.width - content.nonAnimWidth) / 2 : 0
y: { y: {
if (isDetached) if (content.isDetached)
return (root.height - nonAnimHeight) / 2; return (root.height - content.nonAnimHeight) / 2;
const off = currentCenter - root.borderThickness - nonAnimHeight / 2; const off = content.currentCenter - root.borderThickness - content.nonAnimHeight / 2;
const diff = root.height - Math.floor(off + nonAnimHeight); const diff = root.height - Math.floor(off + content.nonAnimHeight);
if (diff < 0) if (diff < 0)
return off + diff; return off + diff;
return Math.max(off, 0); return Math.max(off, 0);
@ -137,7 +138,7 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
sidebar: sidebar sidebar: sidebar
popouts: popouts popouts: popoutsWrapper.content
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right

View file

@ -65,7 +65,8 @@ Region {
} }
R { R {
panel: root.panels.popouts panel: root.panels.popoutsWrapper
width: panel.width * (1 - root.panels.popoutsWrapper.offsetScale)
} }
component R: Region { component R: Region {