mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-12 07:19:41 -04:00
feat(DMS FrameMode): A New Connected Unified Surface & Animation Overhaul
- Introduces Standalone & Connected Modes - Updated Animations & Motion effects for both modes - Numerous QOL tweaks and updates throughout the system - Highly inspired to the OG Caelestia Shell / @Soramanew
This commit is contained in:
151
quickshell/Widgets/ConnectedCorner.qml
Normal file
151
quickshell/Widgets/ConnectedCorner.qml
Normal file
@@ -0,0 +1,151 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import "../Common/ConnectorGeometry.js" as ConnectorGeometry
|
||||
|
||||
// Concave arc connector filling the gap between a bar corner and an adjacent surface.
|
||||
//
|
||||
// NOTE: FrameWindow now uses ConnectedShape.qml for frame-owned connected chrome
|
||||
// (unified single-path rendering). This component is still used by DankPopout's
|
||||
// own shadow source for non-frame-owned chrome (popouts on non-frame screens).
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string barSide: "top"
|
||||
property string placement: "left"
|
||||
property real spacing: 4
|
||||
property real connectorRadius: 12
|
||||
property color color: "transparent"
|
||||
property real edgeStrokeWidth: 0
|
||||
property color edgeStrokeColor: color
|
||||
property real dpr: 1
|
||||
|
||||
readonly property bool isHorizontalBar: barSide === "top" || barSide === "bottom"
|
||||
readonly property bool isPlacementLeft: placement === "left"
|
||||
readonly property real _edgeStrokeWidth: Math.max(0, edgeStrokeWidth)
|
||||
readonly property string arcCorner: ConnectorGeometry.arcCorner(barSide, placement)
|
||||
readonly property real pathStartX: {
|
||||
switch (arcCorner) {
|
||||
case "topLeft":
|
||||
return width;
|
||||
case "topRight":
|
||||
case "bottomLeft":
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
readonly property real pathStartY: {
|
||||
switch (arcCorner) {
|
||||
case "bottomRight":
|
||||
return height;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
readonly property real firstLineX: {
|
||||
switch (arcCorner) {
|
||||
case "topLeft":
|
||||
case "bottomLeft":
|
||||
return width;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
readonly property real firstLineY: {
|
||||
switch (arcCorner) {
|
||||
case "topLeft":
|
||||
case "topRight":
|
||||
return height;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
readonly property real secondLineX: {
|
||||
switch (arcCorner) {
|
||||
case "topRight":
|
||||
case "bottomLeft":
|
||||
case "bottomRight":
|
||||
return width;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
readonly property real secondLineY: {
|
||||
switch (arcCorner) {
|
||||
case "topLeft":
|
||||
case "topRight":
|
||||
case "bottomLeft":
|
||||
return height;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
readonly property real arcCenterX: arcCorner === "topRight" || arcCorner === "bottomRight" ? width : 0
|
||||
readonly property real arcCenterY: arcCorner === "bottomLeft" || arcCorner === "bottomRight" ? height : 0
|
||||
readonly property real arcStartAngle: {
|
||||
switch (arcCorner) {
|
||||
case "topLeft":
|
||||
case "topRight":
|
||||
return 90;
|
||||
case "bottomLeft":
|
||||
return 0;
|
||||
default:
|
||||
return -90;
|
||||
}
|
||||
}
|
||||
readonly property real arcSweepAngle: {
|
||||
switch (arcCorner) {
|
||||
case "topRight":
|
||||
return 90;
|
||||
default:
|
||||
return -90;
|
||||
}
|
||||
}
|
||||
|
||||
width: isHorizontalBar ? connectorRadius : (spacing + connectorRadius)
|
||||
height: isHorizontalBar ? (spacing + connectorRadius) : connectorRadius
|
||||
|
||||
Shape {
|
||||
x: -root._edgeStrokeWidth
|
||||
y: -root._edgeStrokeWidth
|
||||
width: root.width + root._edgeStrokeWidth * 2
|
||||
height: root.height + root._edgeStrokeWidth * 2
|
||||
asynchronous: false
|
||||
antialiasing: true
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
||||
|
||||
ShapePath {
|
||||
fillColor: root.color
|
||||
strokeColor: root._edgeStrokeWidth > 0 ? root.edgeStrokeColor : "transparent"
|
||||
strokeWidth: root._edgeStrokeWidth * 2
|
||||
joinStyle: ShapePath.RoundJoin
|
||||
capStyle: ShapePath.RoundCap
|
||||
fillRule: ShapePath.WindingFill
|
||||
startX: root.pathStartX + root._edgeStrokeWidth
|
||||
startY: root.pathStartY + root._edgeStrokeWidth
|
||||
|
||||
PathLine {
|
||||
x: root.firstLineX + root._edgeStrokeWidth
|
||||
y: root.firstLineY + root._edgeStrokeWidth
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: root.secondLineX + root._edgeStrokeWidth
|
||||
y: root.secondLineY + root._edgeStrokeWidth
|
||||
}
|
||||
|
||||
PathAngleArc {
|
||||
centerX: root.arcCenterX + root._edgeStrokeWidth
|
||||
centerY: root.arcCenterY + root._edgeStrokeWidth
|
||||
radiusX: root.connectorRadius
|
||||
radiusY: root.connectorRadius
|
||||
startAngle: root.arcStartAngle
|
||||
sweepAngle: root.arcSweepAngle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
414
quickshell/Widgets/ConnectedShape.qml
Normal file
414
quickshell/Widgets/ConnectedShape.qml
Normal file
@@ -0,0 +1,414 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import qs.Common
|
||||
|
||||
// Unified connected silhouette: body + near/far concave arcs as one ShapePath.
|
||||
// Keeping the connected chrome in one path avoids sibling alignment seams.
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string barSide: "top"
|
||||
|
||||
property real bodyWidth: 0
|
||||
property real bodyHeight: 0
|
||||
|
||||
property real connectorRadius: 12
|
||||
property real startConnectorRadius: connectorRadius
|
||||
property real endConnectorRadius: connectorRadius
|
||||
property real farStartConnectorRadius: 0
|
||||
property real farEndConnectorRadius: 0
|
||||
|
||||
property real surfaceRadius: 12
|
||||
|
||||
property color fillColor: "transparent"
|
||||
|
||||
readonly property bool _horiz: barSide === "top" || barSide === "bottom"
|
||||
readonly property real _sc: Math.max(0, startConnectorRadius)
|
||||
readonly property real _ec: Math.max(0, endConnectorRadius)
|
||||
readonly property real _fsc: Math.max(0, farStartConnectorRadius)
|
||||
readonly property real _fec: Math.max(0, farEndConnectorRadius)
|
||||
readonly property real _firstCr: barSide === "left" ? _sc : _ec
|
||||
readonly property real _secondCr: barSide === "left" ? _ec : _sc
|
||||
readonly property real _firstFarCr: barSide === "left" ? _fsc : _fec
|
||||
readonly property real _secondFarCr: barSide === "left" ? _fec : _fsc
|
||||
readonly property real _farExtent: Math.max(_fsc, _fec)
|
||||
readonly property real _sr: Math.max(0, Math.min(surfaceRadius, (_horiz ? bodyWidth : bodyHeight) / 2, (_horiz ? bodyHeight : bodyWidth) / 2))
|
||||
readonly property real _firstSr: _firstFarCr > 0 ? 0 : _sr
|
||||
readonly property real _secondSr: _secondFarCr > 0 ? 0 : _sr
|
||||
readonly property real _firstFarInset: _firstFarCr > 0 ? _firstFarCr : _firstSr
|
||||
readonly property real _secondFarInset: _secondFarCr > 0 ? _secondFarCr : _secondSr
|
||||
|
||||
// Root-level aliases — PathArc/PathLine elements can't use `parent`.
|
||||
readonly property real _bw: bodyWidth
|
||||
readonly property real _bh: bodyHeight
|
||||
readonly property real _bodyLeft: _horiz ? _sc : (barSide === "right" ? _farExtent : 0)
|
||||
readonly property real _bodyRight: _bodyLeft + _bw
|
||||
readonly property real _bodyTop: _horiz ? (barSide === "bottom" ? _farExtent : 0) : _sc
|
||||
readonly property real _bodyBottom: _bodyTop + _bh
|
||||
readonly property real _totalW: _horiz ? _bw + _sc + _ec : _bw + _farExtent
|
||||
readonly property real _totalH: _horiz ? _bh + _farExtent : _bh + _sc + _ec
|
||||
|
||||
width: _totalW
|
||||
height: _totalH
|
||||
|
||||
readonly property real bodyX: root._bodyLeft
|
||||
readonly property real bodyY: root._bodyTop
|
||||
|
||||
Shape {
|
||||
anchors.fill: parent
|
||||
asynchronous: false
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
antialiasing: true
|
||||
|
||||
ShapePath {
|
||||
fillColor: root.fillColor
|
||||
strokeWidth: -1
|
||||
fillRule: ShapePath.WindingFill
|
||||
|
||||
// CW path: bar edge → concave arc → body → convex arc → far edge → convex arc → body → concave arc
|
||||
|
||||
startX: root.barSide === "right" ? root._totalW : 0
|
||||
startY: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return root._totalH;
|
||||
case "left":
|
||||
return root._totalH;
|
||||
case "right":
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Bar edge
|
||||
PathLine {
|
||||
x: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return 0;
|
||||
case "right":
|
||||
return root._totalW;
|
||||
default:
|
||||
return root._totalW;
|
||||
}
|
||||
}
|
||||
y: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return root._totalH;
|
||||
case "left":
|
||||
return 0;
|
||||
case "right":
|
||||
return root._totalH;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Concave arc 1
|
||||
PathArc {
|
||||
relativeX: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return root._firstCr;
|
||||
case "right":
|
||||
return -root._firstCr;
|
||||
default:
|
||||
return -root._firstCr;
|
||||
}
|
||||
}
|
||||
relativeY: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return -root._firstCr;
|
||||
case "left":
|
||||
return root._firstCr;
|
||||
case "right":
|
||||
return -root._firstCr;
|
||||
default:
|
||||
return root._firstCr;
|
||||
}
|
||||
}
|
||||
radiusX: root._firstCr
|
||||
radiusY: root._firstCr
|
||||
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
|
||||
}
|
||||
|
||||
// Body edge to first convex corner
|
||||
PathLine {
|
||||
x: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return root._bodyRight - root._firstSr;
|
||||
case "right":
|
||||
return root._bodyLeft + root._firstSr;
|
||||
default:
|
||||
return root._bodyRight;
|
||||
}
|
||||
}
|
||||
y: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return root._bodyTop + root._firstSr;
|
||||
case "left":
|
||||
return root._bodyTop;
|
||||
case "right":
|
||||
return root._bodyBottom;
|
||||
default:
|
||||
return root._bodyBottom - root._firstSr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convex arc 1
|
||||
PathArc {
|
||||
relativeX: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return root._firstSr;
|
||||
case "right":
|
||||
return -root._firstSr;
|
||||
default:
|
||||
return -root._firstSr;
|
||||
}
|
||||
}
|
||||
relativeY: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return -root._firstSr;
|
||||
case "left":
|
||||
return root._firstSr;
|
||||
case "right":
|
||||
return -root._firstSr;
|
||||
default:
|
||||
return root._firstSr;
|
||||
}
|
||||
}
|
||||
radiusX: root._firstSr
|
||||
radiusY: root._firstSr
|
||||
direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise
|
||||
}
|
||||
|
||||
// Opposite-side connector 1
|
||||
PathLine {
|
||||
x: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return root._firstFarCr > 0 ? root._bodyRight + root._firstFarCr : root._bodyRight;
|
||||
case "right":
|
||||
return root._firstFarCr > 0 ? root._bodyLeft - root._firstFarCr : root._bodyLeft;
|
||||
default:
|
||||
return root._firstFarCr > 0 ? root._bodyRight : root._bodyRight - root._firstSr;
|
||||
}
|
||||
}
|
||||
y: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return root._firstFarCr > 0 ? root._bodyTop - root._firstFarCr : root._bodyTop;
|
||||
case "left":
|
||||
return root._firstFarCr > 0 ? root._bodyTop : root._bodyTop + root._firstSr;
|
||||
case "right":
|
||||
return root._firstFarCr > 0 ? root._bodyBottom : root._bodyBottom - root._firstSr;
|
||||
default:
|
||||
return root._firstFarCr > 0 ? root._bodyBottom + root._firstFarCr : root._bodyBottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PathArc {
|
||||
relativeX: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return -root._firstFarCr;
|
||||
case "right":
|
||||
return root._firstFarCr;
|
||||
default:
|
||||
return -root._firstFarCr;
|
||||
}
|
||||
}
|
||||
relativeY: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return root._firstFarCr;
|
||||
case "left":
|
||||
return root._firstFarCr;
|
||||
case "right":
|
||||
return -root._firstFarCr;
|
||||
default:
|
||||
return -root._firstFarCr;
|
||||
}
|
||||
}
|
||||
radiusX: root._firstFarCr
|
||||
radiusY: root._firstFarCr
|
||||
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
|
||||
}
|
||||
|
||||
// Far edge
|
||||
PathLine {
|
||||
x: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return root._bodyRight;
|
||||
case "right":
|
||||
return root._bodyLeft;
|
||||
default:
|
||||
return root._bodyLeft + root._secondFarInset;
|
||||
}
|
||||
}
|
||||
y: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return root._bodyTop;
|
||||
case "left":
|
||||
return root._bodyBottom - root._secondFarInset;
|
||||
case "right":
|
||||
return root._bodyTop + root._secondFarInset;
|
||||
default:
|
||||
return root._bodyBottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Opposite-side connector 2
|
||||
PathArc {
|
||||
relativeX: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return root._secondFarCr;
|
||||
case "right":
|
||||
return -root._secondFarCr;
|
||||
default:
|
||||
return -root._secondFarCr;
|
||||
}
|
||||
}
|
||||
relativeY: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return -root._secondFarCr;
|
||||
case "left":
|
||||
return root._secondFarCr;
|
||||
case "right":
|
||||
return -root._secondFarCr;
|
||||
default:
|
||||
return root._secondFarCr;
|
||||
}
|
||||
}
|
||||
radiusX: root._secondFarCr
|
||||
radiusY: root._secondFarCr
|
||||
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return root._secondFarCr > 0 ? root._bodyRight : root._bodyRight;
|
||||
case "right":
|
||||
return root._secondFarCr > 0 ? root._bodyLeft : root._bodyLeft;
|
||||
default:
|
||||
return root._secondFarCr > 0 ? root._bodyLeft : root._bodyLeft + root._secondSr;
|
||||
}
|
||||
}
|
||||
y: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return root._secondFarCr > 0 ? root._bodyTop : root._bodyTop;
|
||||
case "left":
|
||||
return root._secondFarCr > 0 ? root._bodyBottom : root._bodyBottom - root._secondSr;
|
||||
case "right":
|
||||
return root._secondFarCr > 0 ? root._bodyTop : root._bodyTop + root._secondSr;
|
||||
default:
|
||||
return root._secondFarCr > 0 ? root._bodyBottom : root._bodyBottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convex arc 2
|
||||
PathArc {
|
||||
relativeX: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return -root._secondSr;
|
||||
case "right":
|
||||
return root._secondSr;
|
||||
default:
|
||||
return -root._secondSr;
|
||||
}
|
||||
}
|
||||
relativeY: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return root._secondSr;
|
||||
case "left":
|
||||
return root._secondSr;
|
||||
case "right":
|
||||
return -root._secondSr;
|
||||
default:
|
||||
return -root._secondSr;
|
||||
}
|
||||
}
|
||||
radiusX: root._secondSr
|
||||
radiusY: root._secondSr
|
||||
direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise
|
||||
}
|
||||
|
||||
// Body edge to second concave arc
|
||||
PathLine {
|
||||
x: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return root._bodyLeft + root._ec;
|
||||
case "right":
|
||||
return root._bodyRight - root._sc;
|
||||
default:
|
||||
return root._bodyLeft;
|
||||
}
|
||||
}
|
||||
y: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return root._bodyBottom - root._sc;
|
||||
case "left":
|
||||
return root._bodyBottom;
|
||||
case "right":
|
||||
return root._bodyTop;
|
||||
default:
|
||||
return root._bodyTop + root._sc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Concave arc 2
|
||||
PathArc {
|
||||
relativeX: {
|
||||
switch (root.barSide) {
|
||||
case "left":
|
||||
return -root._secondCr;
|
||||
case "right":
|
||||
return root._secondCr;
|
||||
default:
|
||||
return -root._secondCr;
|
||||
}
|
||||
}
|
||||
relativeY: {
|
||||
switch (root.barSide) {
|
||||
case "bottom":
|
||||
return root._secondCr;
|
||||
case "left":
|
||||
return root._secondCr;
|
||||
case "right":
|
||||
return -root._secondCr;
|
||||
default:
|
||||
return -root._secondCr;
|
||||
}
|
||||
}
|
||||
radiusX: root._secondCr
|
||||
radiusY: root._secondCr
|
||||
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,8 @@ Rectangle {
|
||||
|
||||
Behavior on scale {
|
||||
enabled: enableScaleAnimation && Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None
|
||||
DankAnim {
|
||||
NumberAnimation {
|
||||
easing.type: Easing.BezierSpline
|
||||
duration: 100
|
||||
easing.bezierCurve: Theme.expressiveCurves.standard
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ ColumnLayout {
|
||||
|
||||
Behavior on rotation {
|
||||
enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None
|
||||
DankAnim {
|
||||
NumberAnimation {
|
||||
easing.type: Easing.BezierSpline
|
||||
duration: Theme.shortDuration
|
||||
easing.bezierCurve: Theme.expressiveCurves.standard
|
||||
}
|
||||
@@ -88,7 +89,8 @@ ColumnLayout {
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None
|
||||
DankAnim {
|
||||
NumberAnimation {
|
||||
easing.type: Easing.BezierSpline
|
||||
duration: Theme.shortDuration
|
||||
easing.bezierCurve: Theme.expressiveCurves.standard
|
||||
}
|
||||
@@ -108,7 +110,8 @@ ColumnLayout {
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None
|
||||
DankAnim {
|
||||
NumberAnimation {
|
||||
easing.type: Easing.BezierSpline
|
||||
duration: Theme.shortDuration
|
||||
easing.bezierCurve: Theme.expressiveCurves.standard
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -9,11 +7,8 @@ Item {
|
||||
readonly property var log: Log.scoped("DankPopout")
|
||||
|
||||
property string layerNamespace: "dms:popout"
|
||||
property alias content: contentLoader.sourceComponent
|
||||
property alias contentLoader: contentLoader
|
||||
property Component content: null
|
||||
property Component overlayContent: null
|
||||
property alias overlayLoader: overlayLoader
|
||||
readonly property alias backgroundWindow: backgroundWindow
|
||||
property real popupWidth: 400
|
||||
property real popupHeight: 300
|
||||
property real triggerX: 0
|
||||
@@ -22,10 +17,10 @@ Item {
|
||||
property string triggerSection: ""
|
||||
property string positioning: "center"
|
||||
property int animationDuration: Theme.popoutAnimationDuration
|
||||
property real animationScaleCollapsed: 0.96
|
||||
property real animationOffset: Theme.spacingL
|
||||
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
|
||||
property real animationScaleCollapsed: Theme.effectScaleCollapsed
|
||||
property real animationOffset: Theme.effectAnimOffset
|
||||
property list<real> animationEnterCurve: Theme.variantPopoutEnterCurve
|
||||
property list<real> animationExitCurve: Theme.variantPopoutExitCurve
|
||||
property bool suspendShadowWhileResizing: false
|
||||
property bool shouldBeVisible: false
|
||||
property var customKeyboardFocus: null
|
||||
@@ -33,9 +28,6 @@ Item {
|
||||
property bool contentHandlesKeys: false
|
||||
property bool fullHeightSurface: false
|
||||
property bool _primeContent: false
|
||||
property bool _resizeActive: false
|
||||
property real _surfaceMarginLeft: 0
|
||||
property real _surfaceW: 0
|
||||
|
||||
property real storedBarThickness: Theme.barHeight - 4
|
||||
property real storedBarSpacing: 4
|
||||
@@ -47,90 +39,118 @@ Item {
|
||||
"rightBar": 0
|
||||
})
|
||||
property var screen: null
|
||||
|
||||
readonly property real effectiveBarThickness: {
|
||||
const padding = storedBarConfig ? (storedBarConfig.innerPadding !== undefined ? storedBarConfig.innerPadding : 4) : 4;
|
||||
return Math.max(26 + padding * 0.6, Theme.barHeight - 4 - (8 - padding)) + storedBarSpacing;
|
||||
}
|
||||
|
||||
readonly property var barBounds: {
|
||||
if (!screen)
|
||||
return {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"wingSize": 0
|
||||
};
|
||||
return SettingsData.getBarBounds(screen, effectiveBarThickness, effectiveBarPosition, storedBarConfig);
|
||||
}
|
||||
|
||||
readonly property real barX: barBounds.x
|
||||
readonly property real barY: barBounds.y
|
||||
readonly property real barWidth: barBounds.width
|
||||
readonly property real barHeight: barBounds.height
|
||||
readonly property real barWingSize: barBounds.wingSize
|
||||
property int effectiveBarPosition: 0
|
||||
property real effectiveBarBottomGap: 0
|
||||
|
||||
signal opened
|
||||
signal popoutClosed
|
||||
signal backgroundClicked
|
||||
|
||||
property var _lastOpenedScreen: null
|
||||
readonly property var contentLoader: impl.item ? impl.item.contentLoader : _fallbackContentLoader
|
||||
readonly property var overlayLoader: impl.item ? impl.item.overlayLoader : _fallbackOverlayLoader
|
||||
readonly property var backgroundWindow: impl.item ? impl.item.backgroundWindow : null
|
||||
|
||||
property int effectiveBarPosition: 0
|
||||
property real effectiveBarBottomGap: 0
|
||||
readonly property string autoBarShadowDirection: {
|
||||
const section = triggerSection || "center";
|
||||
switch (effectiveBarPosition) {
|
||||
case SettingsData.Position.Top:
|
||||
if (section === "left")
|
||||
return "topLeft";
|
||||
if (section === "right")
|
||||
return "topRight";
|
||||
return "top";
|
||||
case SettingsData.Position.Bottom:
|
||||
if (section === "left")
|
||||
return "bottomLeft";
|
||||
if (section === "right")
|
||||
return "bottomRight";
|
||||
return "bottom";
|
||||
case SettingsData.Position.Left:
|
||||
if (section === "left")
|
||||
return "topLeft";
|
||||
if (section === "right")
|
||||
return "bottomLeft";
|
||||
return "left";
|
||||
case SettingsData.Position.Right:
|
||||
if (section === "left")
|
||||
return "topRight";
|
||||
if (section === "right")
|
||||
return "bottomRight";
|
||||
return "right";
|
||||
default:
|
||||
return "top";
|
||||
Loader {
|
||||
id: _fallbackContentLoader
|
||||
active: false
|
||||
}
|
||||
Loader {
|
||||
id: _fallbackOverlayLoader
|
||||
active: false
|
||||
}
|
||||
readonly property bool isClosing: impl.item ? (impl.item.isClosing ?? false) : false
|
||||
readonly property real dpr: impl.item ? impl.item.dpr : 1
|
||||
readonly property real screenWidth: impl.item ? impl.item.screenWidth : 0
|
||||
readonly property real screenHeight: impl.item ? impl.item.screenHeight : 0
|
||||
readonly property real alignedX: impl.item ? impl.item.alignedX : 0
|
||||
readonly property real alignedY: impl.item ? impl.item.alignedY : 0
|
||||
readonly property real alignedWidth: impl.item ? impl.item.alignedWidth : 0
|
||||
readonly property real alignedHeight: impl.item ? impl.item.alignedHeight : 0
|
||||
readonly property real maskX: impl.item ? impl.item.maskX : 0
|
||||
readonly property real maskY: impl.item ? impl.item.maskY : 0
|
||||
readonly property real maskWidth: impl.item ? impl.item.maskWidth : 0
|
||||
readonly property real maskHeight: impl.item ? impl.item.maskHeight : 0
|
||||
readonly property real barX: impl.item ? impl.item.barX : 0
|
||||
readonly property real barY: impl.item ? impl.item.barY : 0
|
||||
readonly property real barWidth: impl.item ? impl.item.barWidth : 0
|
||||
readonly property real barHeight: impl.item ? impl.item.barHeight : 0
|
||||
readonly property bool useConnectedBackend: _usesConnectedBackendForScreen(screen)
|
||||
property var _resolvedBackend: null
|
||||
property bool _pendingOpen: false
|
||||
|
||||
Timer {
|
||||
id: _pendingOpenTimer
|
||||
interval: 0
|
||||
onTriggered: {
|
||||
if (!root._pendingOpen || !impl.item)
|
||||
return;
|
||||
root._pendingOpen = false;
|
||||
impl.item.open();
|
||||
}
|
||||
}
|
||||
readonly property string effectiveShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
|
||||
|
||||
// Snapshot mask geometry to prevent background damage on bar updates
|
||||
property real _frozenMaskX: 0
|
||||
property real _frozenMaskY: 0
|
||||
property real _frozenMaskWidth: 0
|
||||
property real _frozenMaskHeight: 0
|
||||
onUseConnectedBackendChanged: _maybeResolveBackend()
|
||||
Component.onCompleted: _resolvedBackend = _backendForScreen(screen)
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onConnectedFrameModeActiveChanged() {
|
||||
root._maybeResolveBackend();
|
||||
}
|
||||
function onFrameScreenPreferencesChanged() {
|
||||
root._maybeResolveBackend();
|
||||
}
|
||||
}
|
||||
|
||||
function _usesConnectedBackendForScreen(targetScreen) {
|
||||
return SettingsData.connectedFrameModeActive && !!targetScreen && SettingsData.isScreenInPreferences(targetScreen, SettingsData.frameScreenPreferences);
|
||||
}
|
||||
|
||||
function _backendForScreen(targetScreen) {
|
||||
return _usesConnectedBackendForScreen(targetScreen) ? connectedComp : standaloneComp;
|
||||
}
|
||||
|
||||
// Defer Loader source-component swap until impl is fully closed; avoids
|
||||
// tearing down a popout mid-animation when frame mode is toggled.
|
||||
function _maybeResolveBackend() {
|
||||
_resolveBackendForScreen(screen);
|
||||
}
|
||||
|
||||
function _resolveBackendForScreen(targetScreen) {
|
||||
const backend = _backendForScreen(targetScreen);
|
||||
if (_resolvedBackend === backend)
|
||||
return;
|
||||
if (impl.item && (impl.item.shouldBeVisible || impl.item.isClosing))
|
||||
return;
|
||||
_resolvedBackend = backend;
|
||||
}
|
||||
|
||||
function open() {
|
||||
_maybeResolveBackend();
|
||||
if (impl.item) {
|
||||
_pendingOpen = false;
|
||||
impl.item.open();
|
||||
return;
|
||||
}
|
||||
_pendingOpen = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
_pendingOpen = false;
|
||||
_pendingOpenTimer.stop();
|
||||
if (impl.item)
|
||||
impl.item.close();
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
(shouldBeVisible || _pendingOpen) ? close() : open();
|
||||
}
|
||||
|
||||
function setBarContext(position, bottomGap) {
|
||||
effectiveBarPosition = position !== undefined ? position : 0;
|
||||
effectiveBarBottomGap = bottomGap !== undefined ? bottomGap : 0;
|
||||
}
|
||||
|
||||
function primeContent() {
|
||||
_primeContent = true;
|
||||
}
|
||||
|
||||
function clearPrimedContent() {
|
||||
_primeContent = false;
|
||||
}
|
||||
|
||||
function setTriggerPosition(x, y, width, section, targetScreen, barPosition, barThickness, barSpacing, barConfig) {
|
||||
triggerX = x;
|
||||
triggerY = y;
|
||||
@@ -147,477 +167,112 @@ Item {
|
||||
|
||||
adjacentBarInfo = SettingsData.getAdjacentBarInfo(targetScreen, pos, barConfig);
|
||||
setBarContext(pos, bottomGap);
|
||||
_resolveBackendForScreen(targetScreen);
|
||||
}
|
||||
|
||||
readonly property bool useBackgroundWindow: !CompositorService.isHyprland || CompositorService.useHyprlandFocusGrab
|
||||
|
||||
function updateSurfacePosition() {
|
||||
if (useBackgroundWindow && shouldBeVisible) {
|
||||
_surfaceMarginLeft = alignedX - shadowBuffer;
|
||||
_surfaceW = alignedWidth + shadowBuffer * 2;
|
||||
}
|
||||
if (impl.item && typeof impl.item.updateSurfacePosition === "function")
|
||||
impl.item.updateSurfacePosition();
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (!screen)
|
||||
Loader {
|
||||
id: impl
|
||||
active: root.screen !== null
|
||||
sourceComponent: root._resolvedBackend
|
||||
onItemChanged: if (item)
|
||||
root._wireBackend(item)
|
||||
}
|
||||
|
||||
Component {
|
||||
id: standaloneComp
|
||||
DankPopoutStandalone {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: connectedComp
|
||||
DankPopoutConnected {}
|
||||
}
|
||||
|
||||
function _wireBackend(it) {
|
||||
if (!it)
|
||||
return;
|
||||
closeTimer.stop();
|
||||
|
||||
// Snapshot mask geometry
|
||||
_frozenMaskX = maskX;
|
||||
_frozenMaskY = maskY;
|
||||
_frozenMaskWidth = maskWidth;
|
||||
_frozenMaskHeight = maskHeight;
|
||||
it.popoutHandle = root;
|
||||
it.layerNamespace = Qt.binding(() => root.layerNamespace);
|
||||
it.content = Qt.binding(() => root.content);
|
||||
it.overlayContent = Qt.binding(() => root.overlayContent);
|
||||
it.popupWidth = Qt.binding(() => root.popupWidth);
|
||||
it.popupHeight = Qt.binding(() => root.popupHeight);
|
||||
it.triggerX = Qt.binding(() => root.triggerX);
|
||||
it.triggerY = Qt.binding(() => root.triggerY);
|
||||
it.triggerWidth = Qt.binding(() => root.triggerWidth);
|
||||
it.triggerSection = Qt.binding(() => root.triggerSection);
|
||||
it.positioning = Qt.binding(() => root.positioning);
|
||||
it.animationDuration = Qt.binding(() => root.animationDuration);
|
||||
it.animationScaleCollapsed = Qt.binding(() => root.animationScaleCollapsed);
|
||||
it.animationOffset = Qt.binding(() => root.animationOffset);
|
||||
it.animationEnterCurve = Qt.binding(() => root.animationEnterCurve);
|
||||
it.animationExitCurve = Qt.binding(() => root.animationExitCurve);
|
||||
it.suspendShadowWhileResizing = Qt.binding(() => root.suspendShadowWhileResizing);
|
||||
it.customKeyboardFocus = Qt.binding(() => root.customKeyboardFocus);
|
||||
it.backgroundInteractive = Qt.binding(() => root.backgroundInteractive);
|
||||
it.contentHandlesKeys = Qt.binding(() => root.contentHandlesKeys);
|
||||
it.fullHeightSurface = Qt.binding(() => root.fullHeightSurface);
|
||||
it.storedBarThickness = Qt.binding(() => root.storedBarThickness);
|
||||
it.storedBarSpacing = Qt.binding(() => root.storedBarSpacing);
|
||||
it.storedBarConfig = Qt.binding(() => root.storedBarConfig);
|
||||
it.adjacentBarInfo = Qt.binding(() => root.adjacentBarInfo);
|
||||
it.screen = Qt.binding(() => root.screen);
|
||||
it.effectiveBarPosition = Qt.binding(() => root.effectiveBarPosition);
|
||||
it.effectiveBarBottomGap = Qt.binding(() => root.effectiveBarBottomGap);
|
||||
|
||||
if (_lastOpenedScreen !== null && _lastOpenedScreen !== screen) {
|
||||
contentWindow.visible = false;
|
||||
if (useBackgroundWindow)
|
||||
backgroundWindow.visible = false;
|
||||
}
|
||||
_lastOpenedScreen = screen;
|
||||
|
||||
shouldBeVisible = true;
|
||||
if (useBackgroundWindow) {
|
||||
_surfaceMarginLeft = alignedX - shadowBuffer;
|
||||
_surfaceW = alignedWidth + shadowBuffer * 2;
|
||||
}
|
||||
Qt.callLater(() => {
|
||||
if (shouldBeVisible && screen) {
|
||||
if (useBackgroundWindow)
|
||||
backgroundWindow.visible = true;
|
||||
contentWindow.visible = true;
|
||||
PopoutManager.showPopout(root);
|
||||
opened();
|
||||
}
|
||||
});
|
||||
it.shouldBeVisible = root.shouldBeVisible;
|
||||
if (root._primeContent && typeof it.primeContent === "function")
|
||||
it.primeContent();
|
||||
if (_pendingOpen)
|
||||
_pendingOpenTimer.restart();
|
||||
}
|
||||
|
||||
function close() {
|
||||
shouldBeVisible = false;
|
||||
function primeContent() {
|
||||
_primeContent = true;
|
||||
if (impl.item)
|
||||
impl.item.primeContent();
|
||||
}
|
||||
|
||||
function clearPrimedContent() {
|
||||
_primeContent = false;
|
||||
PopoutManager.popoutChanged();
|
||||
closeTimer.restart();
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
shouldBeVisible ? close() : open();
|
||||
if (impl.item)
|
||||
impl.item.clearPrimedContent();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onScreensChanged() {
|
||||
if (!shouldBeVisible || !screen)
|
||||
return;
|
||||
const currentScreenName = screen.name;
|
||||
let screenStillExists = false;
|
||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||
if (Quickshell.screens[i].name === currentScreenName) {
|
||||
screenStillExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!screenStillExists) {
|
||||
close();
|
||||
}
|
||||
target: root
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (impl.item && impl.item.shouldBeVisible !== root.shouldBeVisible)
|
||||
impl.item.shouldBeVisible = root.shouldBeVisible;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeTimer
|
||||
interval: animationDuration
|
||||
onTriggered: {
|
||||
if (!shouldBeVisible) {
|
||||
contentWindow.visible = false;
|
||||
if (useBackgroundWindow)
|
||||
backgroundWindow.visible = false;
|
||||
PopoutManager.hidePopout(root);
|
||||
popoutClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: impl.item
|
||||
ignoreUnknownSignals: true
|
||||
|
||||
readonly property real screenWidth: screen ? screen.width : 0
|
||||
readonly property real screenHeight: screen ? screen.height : 0
|
||||
readonly property real dpr: screen ? screen.devicePixelRatio : 1
|
||||
|
||||
readonly property var shadowLevel: Theme.elevationLevel3
|
||||
readonly property real shadowFallbackOffset: 6
|
||||
readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.popoutElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, effectiveShadowDirection, shadowFallbackOffset, 8, 16) : 0
|
||||
readonly property real shadowMotionPadding: Math.max(0, animationOffset)
|
||||
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
||||
readonly property real alignedWidth: Theme.px(popupWidth, dpr)
|
||||
readonly property real alignedHeight: Theme.px(popupHeight, dpr)
|
||||
|
||||
onAlignedHeightChanged: {
|
||||
if (!suspendShadowWhileResizing || !shouldBeVisible)
|
||||
return;
|
||||
_resizeActive = true;
|
||||
resizeSettleTimer.restart();
|
||||
}
|
||||
onShouldBeVisibleChanged: {
|
||||
if (!shouldBeVisible) {
|
||||
_resizeActive = false;
|
||||
resizeSettleTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: resizeSettleTimer
|
||||
interval: 80
|
||||
repeat: false
|
||||
onTriggered: root._resizeActive = false
|
||||
}
|
||||
|
||||
readonly property real alignedX: Theme.snap((() => {
|
||||
const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true;
|
||||
const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4;
|
||||
const popupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue;
|
||||
|
||||
switch (effectiveBarPosition) {
|
||||
case SettingsData.Position.Left:
|
||||
return Math.max(popupGap, Math.min(screenWidth - popupWidth - popupGap, triggerX));
|
||||
case SettingsData.Position.Right:
|
||||
return Math.max(popupGap, Math.min(screenWidth - popupWidth - popupGap, triggerX - popupWidth));
|
||||
default:
|
||||
const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2);
|
||||
const minX = adjacentBarInfo.leftBar > 0 ? adjacentBarInfo.leftBar : popupGap;
|
||||
const maxX = screenWidth - popupWidth - (adjacentBarInfo.rightBar > 0 ? adjacentBarInfo.rightBar : popupGap);
|
||||
return Math.max(minX, Math.min(maxX, rawX));
|
||||
}
|
||||
})(), dpr)
|
||||
|
||||
readonly property real alignedY: Theme.snap((() => {
|
||||
const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true;
|
||||
const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4;
|
||||
const popupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue;
|
||||
|
||||
switch (effectiveBarPosition) {
|
||||
case SettingsData.Position.Bottom:
|
||||
return Math.max(popupGap, Math.min(screenHeight - popupHeight - popupGap, triggerY - popupHeight));
|
||||
case SettingsData.Position.Top:
|
||||
return Math.max(popupGap, Math.min(screenHeight - popupHeight - popupGap, triggerY));
|
||||
default:
|
||||
const rawY = triggerY - (popupHeight / 2);
|
||||
const minY = adjacentBarInfo.topBar > 0 ? adjacentBarInfo.topBar : popupGap;
|
||||
const maxY = screenHeight - popupHeight - (adjacentBarInfo.bottomBar > 0 ? adjacentBarInfo.bottomBar : popupGap);
|
||||
return Math.max(minY, Math.min(maxY, rawY));
|
||||
}
|
||||
})(), dpr)
|
||||
|
||||
readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0
|
||||
readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0
|
||||
readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0
|
||||
readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0
|
||||
|
||||
readonly property real maskX: {
|
||||
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
|
||||
return Math.max(triggeringBarLeftExclusion, adjacentLeftBar);
|
||||
}
|
||||
|
||||
readonly property real maskY: {
|
||||
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
|
||||
return Math.max(triggeringBarTopExclusion, adjacentTopBar);
|
||||
}
|
||||
|
||||
readonly property real maskWidth: {
|
||||
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
|
||||
const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar);
|
||||
return Math.max(100, screenWidth - maskX - rightExclusion);
|
||||
}
|
||||
|
||||
readonly property real maskHeight: {
|
||||
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
|
||||
const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar);
|
||||
return Math.max(100, screenHeight - maskY - bottomExclusion);
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: backgroundWindow
|
||||
screen: root.screen
|
||||
visible: false
|
||||
color: "transparent"
|
||||
Component.onCompleted: {
|
||||
if (typeof updatesEnabled !== "undefined" && !root.overlayContent)
|
||||
updatesEnabled = false;
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (impl.item && root.shouldBeVisible !== impl.item.shouldBeVisible)
|
||||
root.shouldBeVisible = impl.item.shouldBeVisible;
|
||||
}
|
||||
|
||||
WlrLayershell.namespace: root.layerNamespace + ":background"
|
||||
WlrLayershell.layer: WlrLayershell.Top
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
function onOpened() {
|
||||
root.opened();
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: maskRect
|
||||
function onPopoutClosed() {
|
||||
root.popoutClosed();
|
||||
root._maybeResolveBackend();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: maskRect
|
||||
visible: false
|
||||
color: "transparent"
|
||||
x: root._frozenMaskX
|
||||
y: root._frozenMaskY
|
||||
width: (shouldBeVisible && backgroundInteractive) ? root._frozenMaskWidth : 0
|
||||
height: (shouldBeVisible && backgroundInteractive) ? root._frozenMaskHeight : 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
x: root._frozenMaskX
|
||||
y: root._frozenMaskY
|
||||
width: root._frozenMaskWidth
|
||||
height: root._frozenMaskHeight
|
||||
hoverEnabled: false
|
||||
enabled: shouldBeVisible && backgroundInteractive
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
const clickX = mouse.x + root._frozenMaskX;
|
||||
const clickY = mouse.y + root._frozenMaskY;
|
||||
const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight;
|
||||
|
||||
if (!outsideContent)
|
||||
return;
|
||||
backgroundClicked();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: overlayLoader
|
||||
anchors.fill: parent
|
||||
active: root.overlayContent !== null && backgroundWindow.visible
|
||||
sourceComponent: root.overlayContent
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: contentWindow
|
||||
screen: root.screen
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
WindowBlur {
|
||||
id: popoutBlur
|
||||
targetWindow: contentWindow
|
||||
readonly property real s: Math.min(1, contentContainer.scaleValue)
|
||||
blurX: contentContainer.x + contentContainer.width * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
|
||||
blurY: contentContainer.y + contentContainer.height * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
|
||||
blurWidth: (shouldBeVisible && contentWrapper.opacity > 0) ? contentContainer.width * s : 0
|
||||
blurHeight: (shouldBeVisible && contentWrapper.opacity > 0) ? contentContainer.height * s : 0
|
||||
blurRadius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
WlrLayershell.namespace: root.layerNamespace
|
||||
WlrLayershell.layer: {
|
||||
switch (Quickshell.env("DMS_POPOUT_LAYER")) {
|
||||
case "bottom":
|
||||
log.warn("'bottom' layer is not valid for popouts. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
log.warn("'background' layer is not valid for popouts. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (customKeyboardFocus !== null)
|
||||
return customKeyboardFocus;
|
||||
if (!shouldBeVisible)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
|
||||
readonly property bool _fullHeight: useBackgroundWindow && root.fullHeightSurface
|
||||
|
||||
anchors {
|
||||
left: true
|
||||
top: true
|
||||
right: !useBackgroundWindow
|
||||
bottom: _fullHeight || !useBackgroundWindow
|
||||
}
|
||||
|
||||
WlrLayershell.margins {
|
||||
left: useBackgroundWindow ? root._surfaceMarginLeft : 0
|
||||
top: (useBackgroundWindow && !_fullHeight) ? (root.alignedY - shadowBuffer) : 0
|
||||
}
|
||||
|
||||
implicitWidth: useBackgroundWindow ? root._surfaceW : 0
|
||||
implicitHeight: (useBackgroundWindow && !_fullHeight) ? (root.alignedHeight + shadowBuffer * 2) : 0
|
||||
|
||||
mask: useBackgroundWindow ? contentInputMask : null
|
||||
|
||||
Region {
|
||||
id: contentInputMask
|
||||
item: contentMaskRect
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentMaskRect
|
||||
visible: false
|
||||
x: contentContainer.x
|
||||
y: contentContainer.y
|
||||
width: shouldBeVisible ? root.alignedWidth : 0
|
||||
height: shouldBeVisible ? root.alignedHeight : 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: !useBackgroundWindow && shouldBeVisible && backgroundInteractive
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
z: -1
|
||||
onClicked: mouse => {
|
||||
const clickX = mouse.x;
|
||||
const clickY = mouse.y;
|
||||
const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight;
|
||||
if (!outsideContent)
|
||||
return;
|
||||
backgroundClicked();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentContainer
|
||||
x: useBackgroundWindow ? shadowBuffer : root.alignedX
|
||||
y: (useBackgroundWindow && !contentWindow._fullHeight) ? shadowBuffer : root.alignedY
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
|
||||
readonly property bool barTop: effectiveBarPosition === SettingsData.Position.Top
|
||||
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
|
||||
readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left
|
||||
readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right
|
||||
readonly property real offsetX: barLeft ? root.animationOffset : (barRight ? -root.animationOffset : 0)
|
||||
readonly property real offsetY: barBottom ? -root.animationOffset : (barTop ? root.animationOffset : 0)
|
||||
|
||||
property real animX: 0
|
||||
property real animY: 0
|
||||
property real scaleValue: root.animationScaleCollapsed
|
||||
|
||||
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
|
||||
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onShouldBeVisibleChanged() {
|
||||
contentContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetX, root.dpr);
|
||||
contentContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetY, root.dpr);
|
||||
contentContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on animX {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on animY {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scaleValue {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
ElevationShadow {
|
||||
id: shadowSource
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
opacity: contentWrapper.opacity
|
||||
scale: contentWrapper.scale
|
||||
x: contentWrapper.x
|
||||
y: contentWrapper.y
|
||||
level: root.shadowLevel
|
||||
direction: root.effectiveShadowDirection
|
||||
fallbackOffset: root.shadowFallbackOffset
|
||||
targetRadius: Theme.cornerRadius
|
||||
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) && !BlurService.enabled
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentWrapper
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
opacity: shouldBeVisible ? 1 : 0
|
||||
visible: opacity > 0
|
||||
scale: contentContainer.scaleValue
|
||||
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||
y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||
|
||||
layer.enabled: contentWrapper.opacity < 1
|
||||
layer.smooth: false
|
||||
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: animationDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
active: root._primeContent || shouldBeVisible || contentWindow.visible
|
||||
asynchronous: false
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
x: contentWrapper.x
|
||||
y: contentWrapper.y
|
||||
opacity: contentWrapper.opacity
|
||||
scale: contentWrapper.scale
|
||||
visible: contentWrapper.visible
|
||||
radius: Theme.cornerRadius
|
||||
antialiasing: true
|
||||
color: "transparent"
|
||||
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
|
||||
border.width: BlurService.enabled ? BlurService.borderWidth : 1
|
||||
z: 100
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: focusHelper
|
||||
parent: contentContainer
|
||||
anchors.fill: parent
|
||||
visible: !root.contentHandlesKeys
|
||||
enabled: !root.contentHandlesKeys
|
||||
focus: !root.contentHandlesKeys
|
||||
Keys.onPressed: event => {
|
||||
if (root.contentHandlesKeys)
|
||||
return;
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
close();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
function onBackgroundClicked() {
|
||||
root.backgroundClicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1142
quickshell/Widgets/DankPopoutConnected.qml
Normal file
1142
quickshell/Widgets/DankPopoutConnected.qml
Normal file
File diff suppressed because it is too large
Load Diff
893
quickshell/Widgets/DankPopoutStandalone.qml
Normal file
893
quickshell/Widgets/DankPopoutStandalone.qml
Normal file
@@ -0,0 +1,893 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property var log: Log.scoped("DankPopoutStandalone")
|
||||
|
||||
property var popoutHandle: root
|
||||
property string layerNamespace: "dms:popout"
|
||||
property alias content: contentLoader.sourceComponent
|
||||
property alias contentLoader: contentLoader
|
||||
property Component overlayContent: null
|
||||
property alias overlayLoader: overlayLoader
|
||||
readonly property alias backgroundWindow: backgroundWindow
|
||||
property real popupWidth: 400
|
||||
property real popupHeight: 300
|
||||
property real triggerX: 0
|
||||
property real triggerY: 0
|
||||
property real triggerWidth: 40
|
||||
property string triggerSection: ""
|
||||
property string positioning: "center"
|
||||
property int animationDuration: Theme.popoutAnimationDuration
|
||||
property real animationScaleCollapsed: Theme.effectScaleCollapsed
|
||||
property real animationOffset: Theme.effectAnimOffset
|
||||
property list<real> animationEnterCurve: Theme.variantPopoutEnterCurve
|
||||
property list<real> animationExitCurve: Theme.variantPopoutExitCurve
|
||||
property bool suspendShadowWhileResizing: false
|
||||
property bool shouldBeVisible: false
|
||||
property bool isClosing: false
|
||||
property bool animationsEnabled: true
|
||||
property var customKeyboardFocus: null
|
||||
property bool backgroundInteractive: true
|
||||
property bool contentHandlesKeys: false
|
||||
property bool fullHeightSurface: false
|
||||
property bool _primeContent: false
|
||||
property bool _resizeActive: false
|
||||
property real _surfaceMarginLeft: 0
|
||||
property real _surfaceMarginTop: 0
|
||||
property real _surfaceW: 0
|
||||
property real _surfaceH: 0
|
||||
property real _surfaceBodyX: 0
|
||||
property real _surfaceBodyY: 0
|
||||
property real _surfaceBodyW: 0
|
||||
property real _surfaceBodyH: 0
|
||||
|
||||
property real storedBarThickness: Theme.barHeight - 4
|
||||
property real storedBarSpacing: 4
|
||||
property var storedBarConfig: null
|
||||
property var adjacentBarInfo: ({
|
||||
"topBar": 0,
|
||||
"bottomBar": 0,
|
||||
"leftBar": 0,
|
||||
"rightBar": 0
|
||||
})
|
||||
property var screen: null
|
||||
readonly property bool frameOnlyNoConnected: SettingsData.frameEnabled && !!screen && SettingsData.isScreenInPreferences(screen, SettingsData.frameScreenPreferences)
|
||||
readonly property bool fluidStandaloneActive: Theme.isDirectionalEffect
|
||||
readonly property bool backgroundDismissWindowRequired: backgroundInteractive
|
||||
readonly property bool backgroundWindowRequired: backgroundDismissWindowRequired || root.overlayContent !== null
|
||||
readonly property bool _fullHeight: fullHeightSurface
|
||||
|
||||
function _frameEdgeInset(side) {
|
||||
if (!screen)
|
||||
return 0;
|
||||
return SettingsData.frameEdgeInsetForSide(screen, side);
|
||||
}
|
||||
|
||||
function _frameGapMargin(side) {
|
||||
return _frameEdgeInset(side) + Theme.popupDistance;
|
||||
}
|
||||
|
||||
function _edgeClearance(side, popupGap, adjacentInset) {
|
||||
if (frameOnlyNoConnected)
|
||||
return Math.max(adjacentInset, _frameGapMargin(side));
|
||||
return adjacentInset > 0 ? adjacentInset : popupGap;
|
||||
}
|
||||
|
||||
readonly property real effectiveBarThickness: {
|
||||
const padding = storedBarConfig ? (storedBarConfig.innerPadding !== undefined ? storedBarConfig.innerPadding : 4) : 4;
|
||||
return Math.max(26 + padding * 0.6, Theme.barHeight - 4 - (8 - padding)) + storedBarSpacing;
|
||||
}
|
||||
|
||||
readonly property var barBounds: {
|
||||
if (!screen)
|
||||
return {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"wingSize": 0
|
||||
};
|
||||
return SettingsData.getBarBounds(screen, effectiveBarThickness, effectiveBarPosition, storedBarConfig);
|
||||
}
|
||||
|
||||
readonly property real barX: barBounds.x
|
||||
readonly property real barY: barBounds.y
|
||||
readonly property real barWidth: barBounds.width
|
||||
readonly property real barHeight: barBounds.height
|
||||
readonly property real barWingSize: barBounds.wingSize
|
||||
|
||||
signal opened
|
||||
signal popoutClosed
|
||||
signal backgroundClicked
|
||||
|
||||
property var _lastOpenedScreen: null
|
||||
|
||||
property int effectiveBarPosition: 0
|
||||
property real effectiveBarBottomGap: 0
|
||||
readonly property string autoBarShadowDirection: {
|
||||
const section = triggerSection || "center";
|
||||
switch (effectiveBarPosition) {
|
||||
case SettingsData.Position.Top:
|
||||
if (section === "left")
|
||||
return "topLeft";
|
||||
if (section === "right")
|
||||
return "topRight";
|
||||
return "top";
|
||||
case SettingsData.Position.Bottom:
|
||||
if (section === "left")
|
||||
return "bottomLeft";
|
||||
if (section === "right")
|
||||
return "bottomRight";
|
||||
return "bottom";
|
||||
case SettingsData.Position.Left:
|
||||
if (section === "left")
|
||||
return "topLeft";
|
||||
if (section === "right")
|
||||
return "bottomLeft";
|
||||
return "left";
|
||||
case SettingsData.Position.Right:
|
||||
if (section === "left")
|
||||
return "topRight";
|
||||
if (section === "right")
|
||||
return "bottomRight";
|
||||
return "right";
|
||||
default:
|
||||
return "top";
|
||||
}
|
||||
}
|
||||
readonly property string effectiveShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
|
||||
|
||||
// Snapshot mask geometry to prevent background damage on bar updates
|
||||
property real _frozenMaskX: 0
|
||||
property real _frozenMaskY: 0
|
||||
property real _frozenMaskWidth: 0
|
||||
property real _frozenMaskHeight: 0
|
||||
|
||||
function setBarContext(position, bottomGap) {
|
||||
effectiveBarPosition = position !== undefined ? position : 0;
|
||||
effectiveBarBottomGap = bottomGap !== undefined ? bottomGap : 0;
|
||||
}
|
||||
|
||||
function primeContent() {
|
||||
_primeContent = true;
|
||||
}
|
||||
|
||||
function clearPrimedContent() {
|
||||
_primeContent = false;
|
||||
}
|
||||
|
||||
function setTriggerPosition(x, y, width, section, targetScreen, barPosition, barThickness, barSpacing, barConfig) {
|
||||
triggerX = x;
|
||||
triggerY = y;
|
||||
triggerWidth = width;
|
||||
triggerSection = section;
|
||||
screen = targetScreen;
|
||||
|
||||
storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4);
|
||||
storedBarSpacing = barSpacing !== undefined ? barSpacing : 4;
|
||||
storedBarConfig = barConfig;
|
||||
|
||||
const pos = barPosition !== undefined ? barPosition : 0;
|
||||
const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : 0) : 0;
|
||||
|
||||
adjacentBarInfo = SettingsData.getAdjacentBarInfo(targetScreen, pos, barConfig);
|
||||
setBarContext(pos, bottomGap);
|
||||
}
|
||||
|
||||
// Briefly forces backgroundWindow.updatesEnabled true while the surface
|
||||
// body changes, so the contentHoleRect mask carve-out commits to the
|
||||
// compositor — otherwise the input region stays stuck at the popup's
|
||||
// initial size and clicks in any newly-grown area dismiss the popup.
|
||||
// Cleared by the frameSwapped Connections below as soon as the dirty
|
||||
// frame ships, so the bg window goes back to skipping buffer updates.
|
||||
property bool _bgCommitWindow: false
|
||||
|
||||
function _setSurfaceGeometry(bodyX, bodyY, bodyW, bodyH) {
|
||||
const newX = Theme.snap(bodyX, dpr);
|
||||
const newY = Theme.snap(bodyY, dpr);
|
||||
const newW = Theme.snap(bodyW, dpr);
|
||||
const newH = Theme.snap(bodyH, dpr);
|
||||
const changed = newX !== _surfaceBodyX || newY !== _surfaceBodyY || newW !== _surfaceBodyW || newH !== _surfaceBodyH;
|
||||
_surfaceBodyX = newX;
|
||||
_surfaceBodyY = newY;
|
||||
_surfaceBodyW = newW;
|
||||
_surfaceBodyH = newH;
|
||||
_surfaceMarginLeft = _surfaceBodyX - shadowBuffer;
|
||||
_surfaceMarginTop = _surfaceBodyY - shadowBuffer;
|
||||
_surfaceW = _surfaceBodyW + shadowBuffer * 2;
|
||||
_surfaceH = _surfaceBodyH + shadowBuffer * 2;
|
||||
if (changed && backgroundWindow.visible) {
|
||||
_bgCommitWindow = true;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: backgroundWindow
|
||||
ignoreUnknownSignals: true
|
||||
function onFrameSwapped() {
|
||||
if (root._bgCommitWindow)
|
||||
root._bgCommitWindow = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Forces contentWindow to render a frame so Quickshell ships the updated
|
||||
// WindowBlur region to the compositor. WindowBlur's property updates
|
||||
// don't dirty the QML scene graph by themselves, so when the popup grows,
|
||||
// shrinks, or closes without an animation running, the blur state can
|
||||
// get stuck at its previous size. Called from the existing
|
||||
// onAligned*Changed / onShouldBeVisibleChanged handlers.
|
||||
function _kickBlurCommit() {
|
||||
if (typeof contentWindow.update === "function")
|
||||
contentWindow.update();
|
||||
}
|
||||
|
||||
function _setSettledSurfaceGeometry() {
|
||||
if (shouldBeVisible) {
|
||||
_setSurfaceGeometry(alignedX, alignedY, alignedWidth, alignedHeight);
|
||||
}
|
||||
}
|
||||
|
||||
function _setAnimatedSurfaceEnvelope() {
|
||||
if (!shouldBeVisible)
|
||||
return;
|
||||
if (_fullHeight) {
|
||||
_setSettledSurfaceGeometry();
|
||||
return;
|
||||
}
|
||||
|
||||
const currentY = renderedAlignedY;
|
||||
const currentBottom = renderedAlignedY + renderedAlignedHeight;
|
||||
const targetY = alignedY;
|
||||
const targetBottom = alignedY + alignedHeight;
|
||||
const existingY = _surfaceBodyH > 0 ? _surfaceBodyY : currentY;
|
||||
const existingBottom = _surfaceBodyH > 0 ? _surfaceBodyY + _surfaceBodyH : currentBottom;
|
||||
const envelopeY = Math.min(currentY, targetY, existingY);
|
||||
const envelopeBottom = Math.max(currentBottom, targetBottom, existingBottom);
|
||||
_setSurfaceGeometry(alignedX, envelopeY, alignedWidth, Math.max(0, envelopeBottom - envelopeY));
|
||||
surfaceSettleTimer.restart();
|
||||
}
|
||||
|
||||
function updateSurfacePosition() {
|
||||
_setSettledSurfaceGeometry();
|
||||
}
|
||||
|
||||
onAlignedXChanged: {
|
||||
if (shouldBeVisible)
|
||||
_setAnimatedSurfaceEnvelope();
|
||||
_kickBlurCommit();
|
||||
}
|
||||
|
||||
onAlignedYChanged: {
|
||||
if (shouldBeVisible)
|
||||
_setAnimatedSurfaceEnvelope();
|
||||
_kickBlurCommit();
|
||||
}
|
||||
|
||||
onAlignedWidthChanged: {
|
||||
if (shouldBeVisible)
|
||||
_setAnimatedSurfaceEnvelope();
|
||||
_kickBlurCommit();
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (!screen)
|
||||
return;
|
||||
closeTimer.stop();
|
||||
isClosing = false;
|
||||
animationsEnabled = false;
|
||||
_primeContent = true;
|
||||
|
||||
_frozenMaskX = maskX;
|
||||
_frozenMaskY = maskY;
|
||||
_frozenMaskWidth = maskWidth;
|
||||
_frozenMaskHeight = maskHeight;
|
||||
|
||||
if (_lastOpenedScreen !== null && _lastOpenedScreen !== screen) {
|
||||
contentWindow.visible = false;
|
||||
backgroundWindow.visible = false;
|
||||
}
|
||||
_lastOpenedScreen = screen;
|
||||
|
||||
if (contentContainer) {
|
||||
// animationsEnabled is false here, so this snaps to closed without animating.
|
||||
morph.openProgress = 0;
|
||||
}
|
||||
|
||||
_setSurfaceGeometry(alignedX, alignedY, alignedWidth, alignedHeight);
|
||||
if (backgroundWindowRequired)
|
||||
backgroundWindow.visible = true;
|
||||
contentWindow.visible = true;
|
||||
|
||||
animationsEnabled = true;
|
||||
shouldBeVisible = true;
|
||||
if (screen) {
|
||||
PopoutManager.showPopout(popoutHandle);
|
||||
opened();
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
isClosing = true;
|
||||
shouldBeVisible = false;
|
||||
_primeContent = false;
|
||||
PopoutManager.popoutChanged();
|
||||
closeTimer.restart();
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
shouldBeVisible ? close() : open();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onScreensChanged() {
|
||||
if (!shouldBeVisible || !screen)
|
||||
return;
|
||||
const currentScreenName = screen.name;
|
||||
let screenStillExists = false;
|
||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||
if (Quickshell.screens[i].name === currentScreenName) {
|
||||
screenStillExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!screenStillExists) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeTimer
|
||||
interval: Theme.variantCloseInterval(animationDuration)
|
||||
onTriggered: {
|
||||
if (!shouldBeVisible) {
|
||||
isClosing = false;
|
||||
contentWindow.visible = false;
|
||||
backgroundWindow.visible = false;
|
||||
PopoutManager.hidePopout(popoutHandle);
|
||||
popoutClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real screenWidth: screen ? screen.width : 0
|
||||
readonly property real screenHeight: screen ? screen.height : 0
|
||||
readonly property real dpr: screen ? screen.devicePixelRatio : 1
|
||||
|
||||
readonly property var shadowLevel: Theme.elevationLevel3
|
||||
readonly property real shadowFallbackOffset: 6
|
||||
readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.popoutElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, effectiveShadowDirection, shadowFallbackOffset, 8, 16) : 0
|
||||
readonly property real shadowMotionPadding: fluidStandaloneActive ? 0 : Math.max(0, animationOffset)
|
||||
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
||||
readonly property real alignedWidth: Theme.px(popupWidth, dpr)
|
||||
readonly property real alignedHeight: Theme.px(popupHeight, dpr)
|
||||
property real renderedAlignedY: alignedY
|
||||
property real renderedAlignedHeight: alignedHeight
|
||||
readonly property bool renderedGeometryGrowing: alignedHeight >= renderedAlignedHeight
|
||||
|
||||
Behavior on renderedAlignedY {
|
||||
enabled: root.animationsEnabled && contentWindow.visible && root.shouldBeVisible
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(root.animationDuration, root.renderedGeometryGrowing)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.renderedGeometryGrowing ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on renderedAlignedHeight {
|
||||
enabled: root.animationsEnabled && contentWindow.visible && root.shouldBeVisible
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(root.animationDuration, root.renderedGeometryGrowing)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.renderedGeometryGrowing ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
onAlignedHeightChanged: {
|
||||
if (shouldBeVisible)
|
||||
_setAnimatedSurfaceEnvelope();
|
||||
_kickBlurCommit();
|
||||
if (!suspendShadowWhileResizing || !shouldBeVisible)
|
||||
return;
|
||||
_resizeActive = true;
|
||||
resizeSettleTimer.restart();
|
||||
}
|
||||
onShouldBeVisibleChanged: {
|
||||
_kickBlurCommit();
|
||||
if (!shouldBeVisible) {
|
||||
_resizeActive = false;
|
||||
resizeSettleTimer.stop();
|
||||
}
|
||||
}
|
||||
onBackgroundWindowRequiredChanged: {
|
||||
if (shouldBeVisible)
|
||||
backgroundWindow.visible = backgroundWindowRequired;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: resizeSettleTimer
|
||||
interval: 80
|
||||
repeat: false
|
||||
onTriggered: root._resizeActive = false
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: surfaceSettleTimer
|
||||
interval: Math.max(0, Theme.variantDuration(root.animationDuration, root.renderedGeometryGrowing) + 32)
|
||||
repeat: false
|
||||
onTriggered: root._setSettledSurfaceGeometry()
|
||||
}
|
||||
|
||||
readonly property real alignedX: Theme.snap((() => {
|
||||
const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true;
|
||||
const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4;
|
||||
const popupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue;
|
||||
const leftGap = _edgeClearance("left", popupGap, adjacentBarInfo.leftBar > 0 ? adjacentBarInfo.leftBar : 0);
|
||||
const rightGap = _edgeClearance("right", popupGap, adjacentBarInfo.rightBar > 0 ? adjacentBarInfo.rightBar : 0);
|
||||
|
||||
switch (effectiveBarPosition) {
|
||||
case SettingsData.Position.Left:
|
||||
return Math.max(leftGap, Math.min(screenWidth - popupWidth - rightGap, triggerX));
|
||||
case SettingsData.Position.Right:
|
||||
return Math.max(leftGap, Math.min(screenWidth - popupWidth - rightGap, triggerX - popupWidth));
|
||||
default:
|
||||
const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2);
|
||||
const minX = leftGap;
|
||||
const maxX = screenWidth - popupWidth - rightGap;
|
||||
return Math.max(minX, Math.min(maxX, rawX));
|
||||
}
|
||||
})(), dpr)
|
||||
|
||||
readonly property real alignedY: Theme.snap((() => {
|
||||
const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true;
|
||||
const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4;
|
||||
const popupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue;
|
||||
const topGap = _edgeClearance("top", popupGap, adjacentBarInfo.topBar > 0 ? adjacentBarInfo.topBar : 0);
|
||||
const bottomGap = _edgeClearance("bottom", popupGap, adjacentBarInfo.bottomBar > 0 ? adjacentBarInfo.bottomBar : 0);
|
||||
|
||||
switch (effectiveBarPosition) {
|
||||
case SettingsData.Position.Bottom:
|
||||
return Math.max(topGap, Math.min(screenHeight - popupHeight - bottomGap, triggerY - popupHeight));
|
||||
case SettingsData.Position.Top:
|
||||
return Math.max(topGap, Math.min(screenHeight - popupHeight - bottomGap, triggerY));
|
||||
default:
|
||||
const rawY = triggerY - (popupHeight / 2);
|
||||
const minY = topGap;
|
||||
const maxY = screenHeight - popupHeight - bottomGap;
|
||||
return Math.max(minY, Math.min(maxY, rawY));
|
||||
}
|
||||
})(), dpr)
|
||||
|
||||
readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0
|
||||
readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0
|
||||
readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0
|
||||
readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0
|
||||
|
||||
readonly property real maskX: {
|
||||
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
|
||||
return Math.max(triggeringBarLeftExclusion, adjacentLeftBar);
|
||||
}
|
||||
|
||||
readonly property real maskY: {
|
||||
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
|
||||
return Math.max(triggeringBarTopExclusion, adjacentTopBar);
|
||||
}
|
||||
|
||||
readonly property real maskWidth: {
|
||||
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
|
||||
const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar);
|
||||
return Math.max(100, screenWidth - maskX - rightExclusion);
|
||||
}
|
||||
|
||||
readonly property real maskHeight: {
|
||||
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
|
||||
const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar);
|
||||
return Math.max(100, screenHeight - maskY - bottomExclusion);
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: backgroundWindow
|
||||
screen: root.screen
|
||||
visible: false
|
||||
color: "transparent"
|
||||
// Skip buffer updates when there's nothing to render. Briefly flipped
|
||||
// true via _bgCommitWindow when _surfaceBodyW/H changes so the
|
||||
// contentHoleRect mask carve-out actually commits to the compositor.
|
||||
updatesEnabled: root.overlayContent !== null || root._bgCommitWindow
|
||||
|
||||
WlrLayershell.namespace: root.layerNamespace + ":background"
|
||||
WlrLayershell.layer: WlrLayershell.Top
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: maskRect
|
||||
Region {
|
||||
item: contentHoleRect
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: maskRect
|
||||
visible: false
|
||||
color: "transparent"
|
||||
x: root.backgroundDismissWindowRequired ? root._frozenMaskX : 0
|
||||
y: root.backgroundDismissWindowRequired ? root._frozenMaskY : 0
|
||||
width: (root.backgroundDismissWindowRequired && shouldBeVisible && backgroundInteractive) ? root._frozenMaskWidth : 0
|
||||
height: (root.backgroundDismissWindowRequired && shouldBeVisible && backgroundInteractive) ? root._frozenMaskHeight : 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: contentHoleRect
|
||||
visible: false
|
||||
color: "transparent"
|
||||
x: root.backgroundDismissWindowRequired ? root._surfaceBodyX : 0
|
||||
y: root.backgroundDismissWindowRequired ? root._surfaceBodyY : 0
|
||||
width: (root.backgroundDismissWindowRequired && shouldBeVisible) ? root._surfaceBodyW : 0
|
||||
height: (root.backgroundDismissWindowRequired && shouldBeVisible) ? root._surfaceBodyH : 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: false
|
||||
enabled: root.backgroundDismissWindowRequired && shouldBeVisible && backgroundInteractive
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: backgroundClicked()
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: overlayLoader
|
||||
anchors.fill: parent
|
||||
active: root.overlayContent !== null && backgroundWindow.visible
|
||||
sourceComponent: root.overlayContent
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: contentWindow
|
||||
screen: root.screen
|
||||
visible: false
|
||||
color: "transparent"
|
||||
readonly property bool closeVisualActive: root.shouldBeVisible || root.isClosing
|
||||
|
||||
WindowBlur {
|
||||
id: popoutBlur
|
||||
targetWindow: contentWindow
|
||||
readonly property real s: Math.min(1, contentContainer.scaleValue)
|
||||
readonly property bool trackBlurFromBarEdge: root.fluidStandaloneActive
|
||||
readonly property bool blurAlive: trackBlurFromBarEdge ? (contentContainer.revealWidth > 0 && contentContainer.revealHeight > 0) : root.shouldBeVisible
|
||||
|
||||
blurX: trackBlurFromBarEdge ? contentContainer.x + contentContainer.revealX : contentContainer.x + contentContainer.width * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
|
||||
blurY: trackBlurFromBarEdge ? contentContainer.y + contentContainer.revealY : contentContainer.y + contentContainer.height * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
|
||||
blurWidth: blurAlive ? (trackBlurFromBarEdge ? contentContainer.revealWidth : contentContainer.width * s) : 0
|
||||
blurHeight: blurAlive ? (trackBlurFromBarEdge ? contentContainer.revealHeight : contentContainer.height * s) : 0
|
||||
blurRadius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
WlrLayershell.namespace: root.layerNamespace
|
||||
WlrLayershell.layer: {
|
||||
switch (Quickshell.env("DMS_POPOUT_LAYER")) {
|
||||
case "bottom":
|
||||
root.log.warn("'bottom' layer is not valid for popouts. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
root.log.warn("'background' layer is not valid for popouts. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (customKeyboardFocus !== null)
|
||||
return customKeyboardFocus;
|
||||
if (!shouldBeVisible)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
|
||||
anchors {
|
||||
left: true
|
||||
top: true
|
||||
bottom: root._fullHeight
|
||||
}
|
||||
|
||||
WlrLayershell.margins {
|
||||
left: root._surfaceMarginLeft
|
||||
top: root._fullHeight ? 0 : root._surfaceMarginTop
|
||||
}
|
||||
|
||||
implicitWidth: root._surfaceW
|
||||
implicitHeight: root._fullHeight ? 0 : root._surfaceH
|
||||
|
||||
mask: contentInputMask
|
||||
|
||||
Region {
|
||||
id: contentInputMask
|
||||
item: contentMaskRect
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentMaskRect
|
||||
visible: false
|
||||
x: contentContainer.x
|
||||
y: contentContainer.y
|
||||
width: contentWindow.closeVisualActive ? root.alignedWidth : 0
|
||||
height: contentWindow.closeVisualActive ? root.renderedAlignedHeight : 0
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentContainer
|
||||
x: shadowBuffer + root.alignedX - root._surfaceBodyX
|
||||
y: root._fullHeight ? root.renderedAlignedY : shadowBuffer + root.renderedAlignedY - root._surfaceBodyY
|
||||
width: root.alignedWidth
|
||||
height: root.renderedAlignedHeight
|
||||
|
||||
readonly property bool barTop: effectiveBarPosition === SettingsData.Position.Top
|
||||
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
|
||||
readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left
|
||||
readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right
|
||||
readonly property string connectedBarSide: barTop ? "top" : (barBottom ? "bottom" : (barLeft ? "left" : "right"))
|
||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||
readonly property bool depthEffect: Theme.isDepthEffect
|
||||
readonly property real directionalTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
|
||||
readonly property real directionalTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
|
||||
readonly property real depthTravel: Math.max(root.animationOffset * 0.7, 28)
|
||||
readonly property real sectionTilt: (triggerSection === "left" ? -1 : (triggerSection === "right" ? 1 : 0))
|
||||
readonly property real offsetX: {
|
||||
if (directionalEffect) {
|
||||
if (barLeft)
|
||||
return -directionalTravelX;
|
||||
if (barRight)
|
||||
return directionalTravelX;
|
||||
if (barTop || barBottom)
|
||||
return 0;
|
||||
return sectionTilt * directionalTravelX * 0.2;
|
||||
}
|
||||
if (depthEffect) {
|
||||
if (barLeft)
|
||||
return -depthTravel;
|
||||
if (barRight)
|
||||
return depthTravel;
|
||||
if (barTop || barBottom)
|
||||
return 0;
|
||||
return sectionTilt * depthTravel * 0.2;
|
||||
}
|
||||
return barLeft ? root.animationOffset : (barRight ? -root.animationOffset : 0);
|
||||
}
|
||||
readonly property real offsetY: {
|
||||
if (directionalEffect) {
|
||||
if (barBottom)
|
||||
return directionalTravelY;
|
||||
if (barTop)
|
||||
return -directionalTravelY;
|
||||
if (barLeft || barRight)
|
||||
return 0;
|
||||
return directionalTravelY;
|
||||
}
|
||||
if (depthEffect) {
|
||||
if (barBottom)
|
||||
return depthTravel;
|
||||
if (barTop)
|
||||
return -depthTravel;
|
||||
if (barLeft || barRight)
|
||||
return 0;
|
||||
return depthTravel;
|
||||
}
|
||||
return barBottom ? -root.animationOffset : (barTop ? root.animationOffset : 0);
|
||||
}
|
||||
|
||||
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
||||
|
||||
// openProgress: 0 = closed (at offset, scaleCollapsed), 1 = open (at 0, scale 1).
|
||||
QtObject {
|
||||
id: morph
|
||||
property real openProgress: 0
|
||||
Behavior on openProgress {
|
||||
enabled: root.animationsEnabled
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real animX: contentContainer.offsetX * (1 - morph.openProgress)
|
||||
readonly property real animY: contentContainer.offsetY * (1 - morph.openProgress)
|
||||
readonly property real scaleValue: contentContainer.computedScaleCollapsed + (1.0 - contentContainer.computedScaleCollapsed) * morph.openProgress
|
||||
readonly property real clampedAnimX: Math.max(-width, Math.min(animX, width))
|
||||
readonly property real clampedAnimY: Math.max(-height, Math.min(animY, height))
|
||||
readonly property real revealWidth: {
|
||||
if (!root.fluidStandaloneActive)
|
||||
return width;
|
||||
if (barLeft)
|
||||
return Theme.snap(Math.max(0, width + clampedAnimX), root.dpr);
|
||||
if (barRight)
|
||||
return Theme.snap(Math.max(0, width - clampedAnimX), root.dpr);
|
||||
return width;
|
||||
}
|
||||
readonly property real revealHeight: {
|
||||
if (!root.fluidStandaloneActive)
|
||||
return height;
|
||||
if (barTop)
|
||||
return Theme.snap(Math.max(0, height + clampedAnimY), root.dpr);
|
||||
if (barBottom)
|
||||
return Theme.snap(Math.max(0, height - clampedAnimY), root.dpr);
|
||||
return height;
|
||||
}
|
||||
readonly property real revealX: root.fluidStandaloneActive && barRight ? Theme.snap(width - revealWidth, root.dpr) : 0
|
||||
readonly property real revealY: root.fluidStandaloneActive && barBottom ? Theme.snap(height - revealHeight, root.dpr) : 0
|
||||
|
||||
Component.onCompleted: morph.openProgress = root.shouldBeVisible ? 1 : 0
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onShouldBeVisibleChanged() {
|
||||
morph.openProgress = root.shouldBeVisible ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: directionalClipMask
|
||||
|
||||
readonly property bool shouldClip: root.fluidStandaloneActive
|
||||
|
||||
clip: shouldClip
|
||||
x: shouldClip ? contentContainer.revealX : 0
|
||||
y: shouldClip ? contentContainer.revealY : 0
|
||||
width: shouldClip ? contentContainer.revealWidth : parent.width
|
||||
height: shouldClip ? contentContainer.revealHeight : parent.height
|
||||
|
||||
Item {
|
||||
id: rollOutAdjuster
|
||||
readonly property real baseWidth: contentContainer.width
|
||||
readonly property real baseHeight: contentContainer.height
|
||||
|
||||
x: directionalClipMask.x !== 0 ? -directionalClipMask.x : 0
|
||||
y: directionalClipMask.y !== 0 ? -directionalClipMask.y : 0
|
||||
width: baseWidth
|
||||
height: baseHeight
|
||||
clip: false
|
||||
|
||||
ElevationShadow {
|
||||
id: shadowSource
|
||||
width: rollOutAdjuster.baseWidth
|
||||
height: rollOutAdjuster.baseHeight
|
||||
opacity: contentWrapper.publishedOpacity
|
||||
scale: root.fluidStandaloneActive ? 1 : contentWrapper.scale
|
||||
x: root.fluidStandaloneActive ? 0 : contentWrapper.x
|
||||
y: root.fluidStandaloneActive ? 0 : contentWrapper.y
|
||||
level: root.shadowLevel
|
||||
direction: root.effectiveShadowDirection
|
||||
fallbackOffset: root.shadowFallbackOffset
|
||||
targetRadius: Theme.cornerRadius
|
||||
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive)
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentWrapper
|
||||
width: rollOutAdjuster.baseWidth
|
||||
height: rollOutAdjuster.baseHeight
|
||||
|
||||
// publishedOpacity tracks Item.opacity on the GUI thread so consumers (WindowBlur,
|
||||
// ElevationShadow, sibling rect) see interpolated values while the visual runs on
|
||||
// the render thread via OpacityAnimator.
|
||||
property bool _renderActive: Theme.isDirectionalEffect || shouldBeVisible
|
||||
property real publishedOpacity: Theme.isDirectionalEffect ? 1 : (shouldBeVisible ? 1 : 0)
|
||||
|
||||
opacity: Theme.isDirectionalEffect ? 1 : (shouldBeVisible ? 1 : 0)
|
||||
visible: _renderActive
|
||||
scale: contentContainer.scaleValue
|
||||
transformOrigin: Item.Center
|
||||
x: Theme.snap(contentContainer.animX + (rollOutAdjuster.baseWidth - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||
y: Theme.snap(contentContainer.animY + (rollOutAdjuster.baseHeight - height) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||
|
||||
layer.enabled: !Theme.isDirectionalEffect && publishedOpacity < 1
|
||||
layer.smooth: false
|
||||
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
onRunningChanged: {
|
||||
if (!running && !root.shouldBeVisible)
|
||||
contentWrapper._renderActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on publishedOpacity {
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (root.shouldBeVisible)
|
||||
contentWrapper._renderActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: contentWindow
|
||||
function onVisibleChanged() {
|
||||
if (!contentWindow.visible)
|
||||
contentWrapper._renderActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
active: root._primeContent || shouldBeVisible || contentWindow.visible
|
||||
asynchronous: false
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: rollOutAdjuster.baseWidth
|
||||
height: rollOutAdjuster.baseHeight
|
||||
x: root.fluidStandaloneActive ? 0 : contentWrapper.x
|
||||
y: root.fluidStandaloneActive ? 0 : contentWrapper.y
|
||||
opacity: contentWrapper.publishedOpacity
|
||||
scale: root.fluidStandaloneActive ? 1 : contentWrapper.scale
|
||||
visible: contentWrapper.visible
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
|
||||
border.width: BlurService.borderWidth
|
||||
z: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: focusHelper
|
||||
parent: contentContainer
|
||||
anchors.fill: parent
|
||||
visible: !root.contentHandlesKeys
|
||||
enabled: !root.contentHandlesKeys
|
||||
focus: !root.contentHandlesKeys
|
||||
Keys.onPressed: event => {
|
||||
if (root.contentHandlesKeys)
|
||||
return;
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
close();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
@@ -6,8 +7,6 @@ import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
@@ -25,27 +24,28 @@ PanelWindow {
|
||||
property string title: ""
|
||||
property alias container: contentContainer
|
||||
property real customTransparency: -1
|
||||
property bool mappedVisible: false
|
||||
signal aboutToHide
|
||||
|
||||
function show() {
|
||||
visible = true
|
||||
isVisible = true
|
||||
mappedVisible = true;
|
||||
Qt.callLater(() => { isVisible = true; });
|
||||
}
|
||||
|
||||
function hide() {
|
||||
aboutToHide()
|
||||
isVisible = false
|
||||
aboutToHide();
|
||||
isVisible = false;
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (isVisible) {
|
||||
hide()
|
||||
hide();
|
||||
} else {
|
||||
show()
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
visible: isVisible
|
||||
visible: root.mappedVisible
|
||||
screen: modelData
|
||||
|
||||
anchors.top: true
|
||||
@@ -83,15 +83,15 @@ PanelWindow {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: alignedWidth
|
||||
height: alignedHeight
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
|
||||
property real slideOffset: alignedWidth
|
||||
property real slideOffset: root.alignedWidth
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onIsVisibleChanged() {
|
||||
slideContainer.slideOffset = root.isVisible ? 0 : slideContainer.width
|
||||
slideContainer.slideOffset = root.isVisible ? 0 : slideContainer.width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,8 +102,8 @@ PanelWindow {
|
||||
easing.type: Easing.OutCubic
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running && !isVisible) {
|
||||
root.visible = false
|
||||
if (!running && !root.isVisible) {
|
||||
root.mappedVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ PanelWindow {
|
||||
layer.textureSize: Qt.size(width * root.dpr, height * root.dpr)
|
||||
opacity: 1
|
||||
|
||||
readonly property real effectiveTransparency: customTransparency >= 0 ? customTransparency : SettingsData.popupTransparency
|
||||
readonly property real effectiveTransparency: root.customTransparency >= 0 ? root.customTransparency : SettingsData.popupTransparency
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
@@ -8,6 +9,7 @@ Item {
|
||||
|
||||
required property var targetWindow
|
||||
property var blurItem: null
|
||||
property bool blurEnabled: Theme.connectedSurfaceBlurEnabled
|
||||
property real blurX: 0
|
||||
property real blurY: 0
|
||||
property real blurWidth: 0
|
||||
@@ -17,7 +19,7 @@ Item {
|
||||
property var _region: null
|
||||
|
||||
function _apply() {
|
||||
if (!BlurService.enabled || !targetWindow) {
|
||||
if (!blurEnabled || !BlurService.enabled || !targetWindow) {
|
||||
_cleanup();
|
||||
return;
|
||||
}
|
||||
@@ -43,6 +45,8 @@ Item {
|
||||
_region = null;
|
||||
}
|
||||
|
||||
onBlurEnabledChanged: _apply()
|
||||
|
||||
Connections {
|
||||
target: BlurService
|
||||
function onEnabledChanged() {
|
||||
@@ -51,7 +55,7 @@ Item {
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.targetWindow
|
||||
target: root.targetWindow ?? null
|
||||
function onVisibleChanged() {
|
||||
if (root.targetWindow && root.targetWindow.visible) {
|
||||
root._region = null;
|
||||
|
||||
Reference in New Issue
Block a user