mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-16 16:24:00 -04:00
317 lines
11 KiB
QML
317 lines
11 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import qs.Common
|
|
import qs.Services
|
|
import "../Common/FluidGeometry.js" as FluidGeometry
|
|
|
|
PanelWindow {
|
|
id: root
|
|
|
|
required property var host
|
|
|
|
property bool opened: false
|
|
property bool closing: false
|
|
property real shownSheetHeight: sheetHeight
|
|
|
|
Behavior on shownSheetHeight {
|
|
enabled: root.opened && !root.closing && !morph.running && root.animationDuration > 0 && !SettingsData.reduceMotion
|
|
NumberAnimation {
|
|
duration: root.animationDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.variantPopoutResizeCurve
|
|
}
|
|
}
|
|
|
|
readonly property real dpr: host.dpr
|
|
readonly property real shadowBuffer: host.shadowBuffer
|
|
readonly property int alignX: host.alignX
|
|
readonly property int alignY: host.alignY
|
|
readonly property rect compact: Qt.rect(host.alignedX, host.alignedY, host.alignedWidth, host.alignedHeight)
|
|
readonly property real insetTop: host.edgeInset("top")
|
|
readonly property real insetBottom: host.edgeInset("bottom")
|
|
readonly property real insetLeft: host.edgeInset("left")
|
|
readonly property real insetRight: host.edgeInset("right")
|
|
|
|
readonly property real sheetWidth: Theme.px(Math.min(host.sheetWidth, host.screenWidth - insetLeft - insetRight), dpr)
|
|
readonly property real maxSheetHeight: {
|
|
switch (alignY) {
|
|
case -1:
|
|
return host.screenHeight - compact.y - insetBottom;
|
|
case 1:
|
|
return compact.y + compact.height - insetTop;
|
|
default:
|
|
return 2 * Math.min(compact.y + compact.height / 2 - insetTop, host.screenHeight - insetBottom - compact.y - compact.height / 2);
|
|
}
|
|
}
|
|
readonly property real sheetHeight: Theme.px(Math.min(maxSheetHeight, sheetBody.item?.implicitHeight ?? compact.height), dpr)
|
|
readonly property real sheetX: Math.max(insetLeft, Math.min(alongFrom(alignX, compact.x, compact.width, sheetWidth), host.screenWidth - insetRight - sheetWidth))
|
|
readonly property real sheetY: alongFrom(alignY, compact.y, compact.height, shownSheetHeight)
|
|
|
|
readonly property real bandHeight: Math.max(compact.height, maxSheetHeight)
|
|
readonly property real bandX: Math.min(compact.x, sheetX)
|
|
readonly property real bandY: alongFrom(alignY, compact.y, compact.height, bandHeight)
|
|
readonly property real bandRight: Math.max(compact.x + compact.width, sheetX + sheetWidth)
|
|
readonly property real originX: Math.max(0, Theme.snap(bandX - shadowBuffer, dpr))
|
|
readonly property real originY: Math.max(0, Theme.snap(bandY - shadowBuffer, dpr))
|
|
|
|
readonly property rect compactLocal: Qt.rect(compact.x - originX, compact.y - originY, compact.width, compact.height)
|
|
readonly property rect sheetLocal: Qt.rect(Theme.snap(sheetX - originX, dpr), Theme.snap(sheetY - originY, dpr), sheetWidth, shownSheetHeight)
|
|
|
|
readonly property int animationDuration: Theme.popoutAnimationDuration
|
|
readonly property var springParams: Theme.springPreset("default", animationDuration)
|
|
readonly property real morphTravelPx: Math.max(1, Math.abs(sheetLocal.x - compactLocal.x), Math.abs(sheetLocal.y - compactLocal.y), Math.abs(sheetLocal.width - compactLocal.width), Math.abs(sheetLocal.height - compactLocal.height))
|
|
readonly property bool morphSettled: !morph.running || Math.abs(morph.value - morph.target) < Theme.morphLayerEpsilon
|
|
// A fully transparent surface renders no frames, so the spring would never advance to settle.
|
|
readonly property bool closeFinished: closing && (morphSettled || FluidGeometry.chromeOpacity(morph.value) <= 0)
|
|
readonly property real morphProgress: morphSettled ? morph.target : morph.value
|
|
readonly property real progress: Math.max(0, Math.min(1, morphProgress))
|
|
readonly property rect body: FluidGeometry.limitOvershoot(morphRect(compactLocal, sheetLocal, morphProgress), sheetLocal, Theme.fluidOvershootLimit)
|
|
readonly property real surfaceRadius: host.surfaceRadius + (Theme.windowRadius - host.surfaceRadius) * progress
|
|
|
|
signal presented
|
|
signal dismissed
|
|
|
|
function alongFrom(align, start, size, target) {
|
|
switch (align) {
|
|
case -1:
|
|
return start;
|
|
case 1:
|
|
return start + size - target;
|
|
default:
|
|
return start + (size - target) / 2;
|
|
}
|
|
}
|
|
|
|
function alignIn(align, outer, inner) {
|
|
switch (align) {
|
|
case -1:
|
|
return 0;
|
|
case 1:
|
|
return outer - inner;
|
|
default:
|
|
return (outer - inner) / 2;
|
|
}
|
|
}
|
|
|
|
// FluidGeometry only bounds growth; a shrinking axis overshooting its target would crush the fixed-size content.
|
|
function morphRect(from, to, value) {
|
|
const grown = FluidGeometry.interpolate(from, to, value);
|
|
const held = FluidGeometry.interpolate(from, to, Math.min(1, value));
|
|
const holdX = to.width < from.width;
|
|
const holdY = to.height < from.height;
|
|
return Qt.rect(holdX ? held.x : grown.x, holdY ? held.y : grown.y, holdX ? held.width : grown.width, holdY ? held.height : grown.height);
|
|
}
|
|
|
|
function present() {
|
|
opened = true;
|
|
presented();
|
|
morph.retarget(1);
|
|
keyScope.forceActiveFocus();
|
|
}
|
|
|
|
function close() {
|
|
if (closing)
|
|
return;
|
|
morph.retarget(0);
|
|
closing = true;
|
|
}
|
|
|
|
onCloseFinishedChanged: {
|
|
if (closeFinished)
|
|
dismissed();
|
|
}
|
|
|
|
screen: host.screen
|
|
color: "transparent"
|
|
exclusiveZone: -1
|
|
WlrLayershell.namespace: host.blurNamespace
|
|
WlrLayershell.layer: host.WlrLayershell.layer
|
|
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(opened && !closing)
|
|
|
|
anchors {
|
|
top: true
|
|
left: true
|
|
}
|
|
|
|
WlrLayershell.margins {
|
|
left: root.originX
|
|
top: root.originY
|
|
}
|
|
|
|
implicitWidth: Math.min(host.screenWidth - originX, Theme.snap(bandRight + shadowBuffer, dpr) - originX)
|
|
implicitHeight: Math.min(host.screenHeight - originY, Theme.snap(bandY + bandHeight + shadowBuffer, dpr) - originY)
|
|
|
|
SpringMotion {
|
|
id: morph
|
|
enabled: !Theme.springMotionDisabled
|
|
reducedMotion: root.animationDuration <= 0 || SettingsData.reduceMotion
|
|
positionEpsilon: Math.max(0.001, 0.25 / root.dpr / root.morphTravelPx)
|
|
velocityEpsilon: positionEpsilon * damping / Math.max(0.001, 2 * mass)
|
|
stiffness: root.springParams.stiffness
|
|
damping: root.springParams.damping
|
|
|
|
Component.onCompleted: snapTo(0)
|
|
}
|
|
|
|
WindowBlur {
|
|
targetWindow: root
|
|
surfaceColor: Theme.withAlpha(root.host.surfaceColor, Theme.popupTransparency)
|
|
blurX: surface.x
|
|
blurY: surface.y
|
|
blurWidth: surface.opacity < 1 ? 0 : surface.width
|
|
blurHeight: surface.opacity < 1 ? 0 : surface.height
|
|
blurRadius: root.surfaceRadius
|
|
}
|
|
|
|
DFocusGrab {
|
|
windows: [root, dismissWindow].concat(KeyboardFocus.barWindows)
|
|
wanted: KeyboardFocus.wantsGrab(root.opened && !root.closing)
|
|
onCleared: root.close()
|
|
}
|
|
|
|
Connections {
|
|
target: surface.Window.window
|
|
enabled: !root.opened
|
|
|
|
function onFrameSwapped() {
|
|
root.present();
|
|
}
|
|
}
|
|
|
|
FocusScope {
|
|
id: keyScope
|
|
|
|
anchors.fill: parent
|
|
Keys.onEscapePressed: root.close()
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: root.opened
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
onClicked: root.close()
|
|
}
|
|
|
|
Item {
|
|
id: surface
|
|
|
|
x: root.body.x
|
|
y: root.body.y
|
|
width: root.body.width
|
|
height: root.body.height
|
|
opacity: root.closing ? FluidGeometry.chromeOpacity(morph.value) : 1
|
|
|
|
ElevationShadow {
|
|
anchors.fill: parent
|
|
z: -2
|
|
level: Theme.elevationLevel2
|
|
fallbackOffset: Theme.spacingXS
|
|
targetRadius: root.surfaceRadius
|
|
targetColor: Theme.withAlpha(root.host.surfaceColor, Theme.popupTransparency)
|
|
borderColor: Theme.outlineVariant
|
|
borderWidth: 0
|
|
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.AllButtons
|
|
onWheel: wheel => wheel.accepted = true
|
|
}
|
|
|
|
Item {
|
|
anchors.fill: parent
|
|
clip: true
|
|
|
|
Loader {
|
|
x: root.alignIn(root.alignX, parent.width, width)
|
|
y: root.alignIn(root.alignY, parent.height, height)
|
|
width: root.compact.width
|
|
height: root.compact.height
|
|
sourceComponent: root.host.content
|
|
opacity: root.closing ? 0 : 1 - FluidGeometry.chromeOpacity(morph.value)
|
|
visible: opacity > 0
|
|
enabled: !root.opened
|
|
}
|
|
|
|
Loader {
|
|
id: sheetBody
|
|
|
|
x: root.alignIn(root.alignX, parent.width, width)
|
|
y: root.alignIn(root.alignY, parent.height, height)
|
|
width: root.sheetWidth
|
|
height: root.sheetHeight
|
|
sourceComponent: root.host.sheet
|
|
opacity: FluidGeometry.contentOpacity(root.body, root.sheetLocal)
|
|
visible: opacity > 0
|
|
focus: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: sheetBody.item
|
|
ignoreUnknownSignals: true
|
|
|
|
function onCloseRequested() {
|
|
root.close();
|
|
}
|
|
}
|
|
|
|
PanelWindow {
|
|
id: dismissWindow
|
|
|
|
property bool mapped: false
|
|
|
|
screen: root.screen
|
|
visible: root.opened && !root.closing
|
|
color: "transparent"
|
|
exclusiveZone: -1
|
|
// Input only: after the first transparent frame maps it, repainting would damage the whole screen.
|
|
updatesEnabled: !mapped
|
|
WlrLayershell.namespace: root.host.blurNamespace + ":dismiss"
|
|
WlrLayershell.layer: root.WlrLayershell.layer
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
|
|
anchors {
|
|
top: true
|
|
bottom: true
|
|
left: true
|
|
right: true
|
|
}
|
|
|
|
mask: Region {
|
|
width: dismissWindow.width
|
|
height: dismissWindow.height
|
|
|
|
Region {
|
|
x: root.originX
|
|
y: root.originY
|
|
width: root.width
|
|
height: root.height
|
|
intersection: Intersection.Subtract
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: dismissSurface.Window.window
|
|
enabled: !dismissWindow.mapped
|
|
|
|
function onFrameSwapped() {
|
|
dismissWindow.mapped = true;
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: dismissSurface
|
|
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
onClicked: root.close()
|
|
}
|
|
}
|
|
}
|