1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-14 17:52:10 -04:00

frame(ConnectedMode): Wire up Notifications

This commit is contained in:
purian23
2026-04-11 22:09:45 -04:00
parent 7e6ccd3c79
commit 0c4f0e65bb
5 changed files with 504 additions and 131 deletions

View File

@@ -14,19 +14,14 @@ Item {
required property real cutoutLeftInset required property real cutoutLeftInset
required property real cutoutRightInset required property real cutoutRightInset
required property real cutoutRadius required property real cutoutRadius
property color borderColor: Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity)
Rectangle { Rectangle {
id: borderRect id: borderRect
anchors.fill: parent anchors.fill: parent
// Bake frameOpacity into the color alpha rather than using the `opacity` property. // Bake frameOpacity into the color alpha rather than using the `opacity` property
// Qt Quick can skip layer.effect processing on items with opacity < 1 as an color: root.borderColor
// optimization, causing the MultiEffect inverted mask to stop working and the
// Rectangle to render as a plain square at low opacity values.
color: Qt.rgba(SettingsData.effectiveFrameColor.r,
SettingsData.effectiveFrameColor.g,
SettingsData.effectiveFrameColor.b,
SettingsData.frameOpacity)
layer.enabled: true layer.enabled: true
layer.effect: MultiEffect { layer.effect: MultiEffect {

View File

@@ -44,6 +44,7 @@ PanelWindow {
"x": 0, "x": 0,
"y": 0 "y": 0
}) })
readonly property var _notifState: ConnectedModeState.notificationStates[win._screenName] || ConnectedModeState.emptyNotificationState
// ─── Connected chrome convenience properties ────────────────────────────── // ─── Connected chrome convenience properties ──────────────────────────────
readonly property bool _connectedActive: win._frameActive && SettingsData.connectedFrameModeActive readonly property bool _connectedActive: win._frameActive && SettingsData.connectedFrameModeActive
@@ -218,6 +219,18 @@ PanelWindow {
height: _active ? win._dockConnectorRadius() * 2 : 0 height: _active ? win._dockConnectorRadius() * 2 : 0
} }
Item {
id: _notifBodyBlurAnchor
visible: false
readonly property bool _active: win._frameActive && win._notifState.visible && win._notifState.bodyW > 0 && win._notifState.bodyH > 0
x: _active ? Theme.snap(win._notifState.bodyX, win._dpr) : 0
y: _active ? Theme.snap(win._notifState.bodyY, win._dpr) : 0
width: _active ? Theme.snap(win._notifState.bodyW, win._dpr) : 0
height: _active ? Theme.snap(win._notifState.bodyH, win._dpr) : 0
}
Region { Region {
id: _staticBlurRegion id: _staticBlurRegion
x: 0 x: 0
@@ -267,6 +280,11 @@ PanelWindow {
radius: win._dockConnectorRadius() radius: win._dockConnectorRadius()
} }
} }
Region {
item: _notifBodyBlurAnchor
radius: win._surfaceRadius
}
} }
// ─── Connector position helpers (dock) ───────────────────────────────── // ─── Connector position helpers (dock) ─────────────────────────────────
@@ -528,7 +546,7 @@ PanelWindow {
FrameBorder { FrameBorder {
anchors.fill: parent anchors.fill: parent
visible: win._frameActive visible: win._frameActive && !win._connectedActive
cutoutTopInset: win.cutoutTopInset cutoutTopInset: win.cutoutTopInset
cutoutBottomInset: win.cutoutBottomInset cutoutBottomInset: win.cutoutBottomInset
cutoutLeftInset: win.cutoutLeftInset cutoutLeftInset: win.cutoutLeftInset
@@ -539,9 +557,27 @@ PanelWindow {
// ─── Connected chrome fills ─────────────────────────────────────────────── // ─── Connected chrome fills ───────────────────────────────────────────────
Item { Item {
id: _connectedChrome id: _connectedSurfaceLayer
anchors.fill: parent anchors.fill: parent
visible: win._connectedActive visible: win._connectedActive
opacity: win._surfaceOpacity
layer.enabled: opacity < 1
layer.smooth: false
FrameBorder {
anchors.fill: parent
borderColor: win._opaqueSurfaceColor
cutoutTopInset: win.cutoutTopInset
cutoutBottomInset: win.cutoutBottomInset
cutoutLeftInset: win.cutoutLeftInset
cutoutRightInset: win.cutoutRightInset
cutoutRadius: win.cutoutRadius
}
Item {
id: _connectedChrome
anchors.fill: parent
visible: true
Item { Item {
id: _popoutChrome id: _popoutChrome
@@ -550,9 +586,6 @@ PanelWindow {
y: win._popoutChromeY() y: win._popoutChromeY()
width: win._popoutChromeWidth() width: win._popoutChromeWidth()
height: win._popoutChromeHeight() height: win._popoutChromeHeight()
opacity: win._surfaceOpacity
layer.enabled: opacity < 1
layer.smooth: false
Item { Item {
id: _popoutClip id: _popoutClip
@@ -586,9 +619,6 @@ PanelWindow {
y: win._dockChromeY() y: win._dockChromeY()
width: win._dockChromeWidth() width: win._dockChromeWidth()
height: win._dockChromeHeight() height: win._dockChromeHeight()
opacity: win._surfaceOpacity
layer.enabled: opacity < 1
layer.smooth: false
Rectangle { Rectangle {
id: _dockFill id: _dockFill
@@ -634,4 +664,35 @@ PanelWindow {
} }
} }
} }
Item {
id: _notifChrome
visible: _notifBodyBlurAnchor._active
readonly property string _notifSide: win._notifState.barSide
readonly property bool _isHoriz: _notifSide === "top" || _notifSide === "bottom"
readonly property real _notifCcr: Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, (_isHoriz ? _notifBodyBlurAnchor.width : _notifBodyBlurAnchor.height) / 2)), win._dpr)
readonly property real _sideUnderlap: _isHoriz ? 0 : win._seamOverlap
readonly property real _bodyW: Theme.snap(_notifBodyBlurAnchor.width + _sideUnderlap, win._dpr)
readonly property real _bodyH: Theme.snap(_notifBodyBlurAnchor.height, win._dpr)
z: _isHoriz ? 0 : -1
x: Theme.snap(_notifBodyBlurAnchor.x - (_isHoriz ? _notifCcr : (_notifSide === "left" ? _sideUnderlap : 0)), win._dpr)
y: Theme.snap(_notifBodyBlurAnchor.y - (_isHoriz ? 0 : _notifCcr), win._dpr)
width: _isHoriz ? Theme.snap(_bodyW + _notifCcr * 2, win._dpr) : _bodyW
height: Theme.snap(_bodyH + (_isHoriz ? 0 : _notifCcr * 2), win._dpr)
ConnectedShape {
visible: _notifBodyBlurAnchor._active && _notifBodyBlurAnchor.width > 0 && _notifBodyBlurAnchor.height > 0
barSide: _notifChrome._notifSide
bodyWidth: _notifChrome._bodyW
bodyHeight: _notifChrome._bodyH
connectorRadius: _notifChrome._notifCcr
surfaceRadius: win._surfaceRadius
fillColor: win._opaqueSurfaceColor
x: 0
y: 0
}
}
}
} }

View File

@@ -10,13 +10,29 @@ import qs.Widgets
PanelWindow { PanelWindow {
id: win id: win
readonly property bool connectedFrameMode: SettingsData.frameEnabled
&& Theme.isConnectedEffect
&& SettingsData.isScreenInPreferences(win.screen, SettingsData.frameScreenPreferences)
readonly property string notifBarSide: {
const pos = SettingsData.notificationPopupPosition;
if (pos === -1) return "top";
switch (pos) {
case SettingsData.Position.Top: return "right";
case SettingsData.Position.Left: return "left";
case SettingsData.Position.BottomCenter: return "bottom";
case SettingsData.Position.Right: return "right";
case SettingsData.Position.Bottom: return "left";
default: return "top";
}
}
WindowBlur { WindowBlur {
targetWindow: win targetWindow: win
blurX: content.x + content.cardInset + swipeTx.x + tx.x blurX: content.x + content.cardInset + swipeTx.x + tx.x
blurY: content.y + content.cardInset + swipeTx.y + tx.y blurY: content.y + content.cardInset + swipeTx.y + tx.y
blurWidth: !win._finalized ? Math.max(0, content.width - content.cardInset * 2) : 0 blurWidth: !win._finalized && !win.connectedFrameMode ? Math.max(0, content.width - content.cardInset * 2) : 0
blurHeight: !win._finalized ? Math.max(0, content.height - content.cardInset * 2) : 0 blurHeight: !win._finalized && !win.connectedFrameMode ? Math.max(0, content.height - content.cardInset * 2) : 0
blurRadius: SettingsData.connectedFrameModeActive ? Theme.connectedSurfaceRadius : Theme.cornerRadius blurRadius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
} }
WlrLayershell.namespace: "dms:notification-popup" WlrLayershell.namespace: "dms:notification-popup"
@@ -84,6 +100,7 @@ PanelWindow {
signal exitStarted signal exitStarted
signal exitFinished signal exitFinished
signal popupHeightChanged signal popupHeightChanged
signal popupChromeGeometryChanged
function startExit() { function startExit() {
if (exiting || _isDestroying) { if (exiting || _isDestroying) {
@@ -91,6 +108,7 @@ PanelWindow {
} }
exiting = true; exiting = true;
exitStarted(); exitStarted();
popupChromeGeometryChanged();
exitAnim.restart(); exitAnim.restart();
exitWatchdog.restart(); exitWatchdog.restart();
if (NotificationService.removeFromVisibleNotifications) if (NotificationService.removeFromVisibleNotifications)
@@ -171,6 +189,7 @@ PanelWindow {
duration: Theme.variantDuration(descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration, descriptionExpanded) duration: Theme.variantDuration(descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration, descriptionExpanded)
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: descriptionExpanded ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve easing.bezierCurve: descriptionExpanded ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
onFinished: win.popupHeightChanged()
} }
} }
@@ -263,12 +282,24 @@ PanelWindow {
}); });
} }
function _frameEdgeInset(side) {
if (!screen)
return 0;
const edges = SettingsData.getActiveBarEdgesForScreen(screen);
const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness;
return Math.max(0, Math.round(Theme.px(raw, dpr)));
}
function getTopMargin() { function getTopMargin() {
const popupPos = SettingsData.notificationPopupPosition; const popupPos = SettingsData.notificationPopupPosition;
const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left; const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left;
if (!isTop) if (!isTop)
return 0; return 0;
if (connectedFrameMode) {
const cornerClear = isCenterPosition ? 0 : (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr));
return _frameEdgeInset("top") + cornerClear + screenY;
}
const barInfo = getBarInfo(); const barInfo = getBarInfo();
const base = barInfo.topBar > 0 ? barInfo.topBar : Theme.popupDistance; const base = barInfo.topBar > 0 ? barInfo.topBar : Theme.popupDistance;
return base + screenY; return base + screenY;
@@ -280,6 +311,10 @@ PanelWindow {
if (!isBottom) if (!isBottom)
return 0; return 0;
if (connectedFrameMode) {
const cornerClear = isCenterPosition ? 0 : (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr));
return _frameEdgeInset("bottom") + cornerClear + screenY;
}
const barInfo = getBarInfo(); const barInfo = getBarInfo();
const base = barInfo.bottomBar > 0 ? barInfo.bottomBar : Theme.popupDistance; const base = barInfo.bottomBar > 0 ? barInfo.bottomBar : Theme.popupDistance;
return base + screenY; return base + screenY;
@@ -294,6 +329,8 @@ PanelWindow {
if (!isLeft) if (!isLeft)
return 0; return 0;
if (connectedFrameMode)
return _frameEdgeInset("left");
const barInfo = getBarInfo(); const barInfo = getBarInfo();
return barInfo.leftBar > 0 ? barInfo.leftBar : Theme.popupDistance; return barInfo.leftBar > 0 ? barInfo.leftBar : Theme.popupDistance;
} }
@@ -307,6 +344,8 @@ PanelWindow {
if (!isRight) if (!isRight)
return 0; return 0;
if (connectedFrameMode)
return _frameEdgeInset("right");
const barInfo = getBarInfo(); const barInfo = getBarInfo();
return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance; return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance;
} }
@@ -351,10 +390,64 @@ PanelWindow {
return Theme.snap(getContentY() - windowShadowPad, dpr); return Theme.snap(getContentY() - windowShadowPad, dpr);
} }
function _swipeDismissTarget() {
return (content.swipeDismissDirection < 0 ? -1 : 1) * content.width;
}
function _frameEdgeSwipeDirection() {
const popupPos = SettingsData.notificationPopupPosition;
return (popupPos === SettingsData.Position.Left || popupPos === SettingsData.Position.Bottom) ? -1 : 1;
}
function _swipeDismissesTowardFrameEdge() {
return content.swipeDismissDirection === _frameEdgeSwipeDirection();
}
function popupChromeMotionActive() {
return exiting || content.swipeActive || content.swipeDismissing || Math.abs(content.swipeOffset) > 0.5;
}
function popupLayoutReservesSlot() {
return !content.swipeDismissing;
}
function popupChromeReservesSlot() {
return !content.swipeDismissing;
}
function popupChromeReleaseProgress() {
if (content.swipeDismissing)
return Math.max(0, Math.min(1, Math.abs(content.swipeOffset) / Math.max(1, content.swipeTravelDistance)));
if (!exiting)
return 0;
const exitOffset = isCenterPosition ? tx.y : tx.x;
return Math.max(0, Math.min(1, Math.abs(exitOffset) / Math.max(1, exitTravel)));
}
function popupChromeMotionX() {
if (!popupChromeMotionActive() || isCenterPosition)
return 0;
const motion = content.swipeOffset + tx.x;
if (content.swipeDismissing && !_swipeDismissesTowardFrameEdge())
return exiting ? Theme.snap(tx.x, dpr) : 0;
if (content.swipeActive && motion * _frameEdgeSwipeDirection() < 0)
return 0;
return Theme.snap(motion, dpr);
}
function popupChromeMotionY() {
return popupChromeMotionActive() ? Theme.snap(tx.y, dpr) : 0;
}
readonly property bool screenValid: win.screen && !_isDestroying readonly property bool screenValid: win.screen && !_isDestroying
readonly property real dpr: screenValid ? CompositorService.getScreenScale(win.screen) : 1 readonly property real dpr: screenValid ? CompositorService.getScreenScale(win.screen) : 1
readonly property real alignedWidth: Theme.px(Math.max(0, implicitWidth - (windowShadowPad * 2)), dpr) readonly property real alignedWidth: Theme.px(Math.max(0, implicitWidth - (windowShadowPad * 2)), dpr)
readonly property real alignedHeight: Theme.px(Math.max(0, implicitHeight - (windowShadowPad * 2)), dpr) readonly property real alignedHeight: Theme.px(Math.max(0, implicitHeight - (windowShadowPad * 2)), dpr)
onScreenYChanged: popupChromeGeometryChanged()
onScreenChanged: popupChromeGeometryChanged()
onConnectedFrameModeChanged: popupChromeGeometryChanged()
onAlignedWidthChanged: popupChromeGeometryChanged()
onAlignedHeightChanged: popupChromeGeometryChanged()
Item { Item {
id: content id: content
@@ -363,7 +456,7 @@ PanelWindow {
y: Theme.snap(windowShadowPad, dpr) y: Theme.snap(windowShadowPad, dpr)
width: alignedWidth width: alignedWidth
height: alignedHeight height: alignedHeight
visible: !win._finalized visible: !win._finalized && !chromeOnlyExit
scale: cardHoverHandler.hovered ? 1.01 : 1.0 scale: cardHoverHandler.hovered ? 1.01 : 1.0
transformOrigin: Item.Center transformOrigin: Item.Center
@@ -375,13 +468,25 @@ PanelWindow {
} }
property real swipeOffset: 0 property real swipeOffset: 0
readonly property real dismissThreshold: isCenterPosition ? height * 0.4 : width * 0.35 property real swipeDismissDirection: 1
property bool chromeOnlyExit: false
readonly property real dismissThreshold: width * 0.35
readonly property real swipeFadeStartRatio: 0.75 readonly property real swipeFadeStartRatio: 0.75
readonly property real swipeTravelDistance: isCenterPosition ? height : width readonly property real swipeTravelDistance: width
readonly property real swipeFadeStartOffset: swipeTravelDistance * swipeFadeStartRatio readonly property real swipeFadeStartOffset: swipeTravelDistance * swipeFadeStartRatio
readonly property real swipeFadeDistance: Math.max(1, swipeTravelDistance - swipeFadeStartOffset) readonly property real swipeFadeDistance: Math.max(1, swipeTravelDistance - swipeFadeStartOffset)
readonly property bool swipeActive: swipeDragHandler.active readonly property bool swipeActive: swipeDragHandler.active
property bool swipeDismissing: false property bool swipeDismissing: false
onSwipeDismissingChanged: {
if (!win.connectedFrameMode)
return;
win.popupHeightChanged();
win.popupChromeGeometryChanged();
}
onSwipeOffsetChanged: {
if (win.connectedFrameMode)
win.popupChromeGeometryChanged();
}
readonly property bool shadowsAllowed: Theme.elevationEnabled && SettingsData.notificationPopupShadowEnabled readonly property bool shadowsAllowed: Theme.elevationEnabled && SettingsData.notificationPopupShadowEnabled
readonly property var elevLevel: cardHoverHandler.hovered ? Theme.elevationLevel4 : Theme.elevationLevel3 readonly property var elevLevel: cardHoverHandler.hovered ? Theme.elevationLevel4 : Theme.elevationLevel3
@@ -422,7 +527,7 @@ PanelWindow {
shadowOffsetX: content.shadowOffsetX shadowOffsetX: content.shadowOffsetX
shadowOffsetY: content.shadowOffsetY shadowOffsetY: content.shadowOffsetY
shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent" shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent"
shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed && !win.connectedFrameMode
layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr)) layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically layer.textureMirroring: ShaderEffectSource.MirrorVertically
@@ -431,8 +536,12 @@ PanelWindow {
sourceRect.y: content.shadowRenderPadding + content.cardInset sourceRect.y: content.shadowRenderPadding + content.cardInset
sourceRect.width: Math.max(0, content.width - (content.cardInset * 2)) sourceRect.width: Math.max(0, content.width - (content.cardInset * 2))
sourceRect.height: Math.max(0, content.height - (content.cardInset * 2)) sourceRect.height: Math.max(0, content.height - (content.cardInset * 2))
sourceRect.radius: Theme.cornerRadius sourceRect.radius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
sourceRect.color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) sourceRect.color: win.connectedFrameMode ? Theme.popupLayerColor(Theme.surfaceContainer) : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
sourceRect.antialiasing: true
sourceRect.layer.enabled: win.connectedFrameMode
sourceRect.layer.smooth: true
sourceRect.layer.textureSize: win.connectedFrameMode && win.dpr > 1 ? Qt.size(Math.ceil(sourceRect.width * win.dpr), Math.ceil(sourceRect.height * win.dpr)) : Qt.size(0, 0)
sourceRect.border.color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08) sourceRect.border.color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08)
sourceRect.border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 0 sourceRect.border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 0
@@ -470,10 +579,10 @@ PanelWindow {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.margins: content.cardInset anchors.margins: content.cardInset
radius: Theme.cornerRadius radius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
color: "transparent" color: "transparent"
border.color: BlurService.borderColor border.color: win.connectedFrameMode ? "transparent" : BlurService.borderColor
border.width: BlurService.borderWidth border.width: win.connectedFrameMode ? 0 : BlurService.borderWidth
z: 100 z: 100
} }
@@ -873,14 +982,15 @@ PanelWindow {
DragHandler { DragHandler {
id: swipeDragHandler id: swipeDragHandler
target: null target: null
xAxis.enabled: !isCenterPosition xAxis.enabled: true
yAxis.enabled: isCenterPosition yAxis.enabled: false
onActiveChanged: { onActiveChanged: {
if (active || win.exiting || content.swipeDismissing) if (active || win.exiting || content.swipeDismissing)
return; return;
if (Math.abs(content.swipeOffset) > content.dismissThreshold) { if (Math.abs(content.swipeOffset) > content.dismissThreshold) {
content.swipeDismissDirection = content.swipeOffset < 0 ? -1 : 1;
content.swipeDismissing = true; content.swipeDismissing = true;
swipeDismissAnim.start(); swipeDismissAnim.start();
} else { } else {
@@ -892,15 +1002,7 @@ PanelWindow {
if (win.exiting) if (win.exiting)
return; return;
const raw = isCenterPosition ? translation.y : translation.x; content.swipeOffset = translation.x;
if (isTopCenter) {
content.swipeOffset = Math.min(0, raw);
} else if (isBottomCenter) {
content.swipeOffset = Math.max(0, raw);
} else {
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
content.swipeOffset = isLeft ? Math.min(0, raw) : Math.max(0, raw);
}
} }
} }
@@ -931,20 +1033,28 @@ PanelWindow {
id: swipeDismissAnim id: swipeDismissAnim
target: content target: content
property: "swipeOffset" property: "swipeOffset"
to: isTopCenter ? -content.height : isBottomCenter ? content.height : (SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom ? -content.width : content.width) to: win._swipeDismissTarget()
duration: Theme.notificationExitDuration duration: Theme.notificationExitDuration
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
onStopped: { onStopped: {
const inwardConnectedExit = win.connectedFrameMode && !win.isCenterPosition && !win._swipeDismissesTowardFrameEdge();
if (inwardConnectedExit)
content.chromeOnlyExit = true;
if (win.connectedFrameMode && (win.isCenterPosition || inwardConnectedExit)) {
win.startExit();
NotificationService.dismissNotification(notificationData);
} else {
NotificationService.dismissNotification(notificationData); NotificationService.dismissNotification(notificationData);
win.forceExit(); win.forceExit();
} }
} }
}
transform: [ transform: [
Translate { Translate {
id: swipeTx id: swipeTx
x: isCenterPosition ? 0 : content.swipeOffset x: content.swipeOffset
y: isCenterPosition ? content.swipeOffset : 0 y: 0
}, },
Translate { Translate {
id: tx id: tx
@@ -955,6 +1065,14 @@ PanelWindow {
return isLeft ? -entryTravel : entryTravel; return isLeft ? -entryTravel : entryTravel;
} }
y: isTopCenter ? -entryTravel : isBottomCenter ? entryTravel : 0 y: isTopCenter ? -entryTravel : isBottomCenter ? entryTravel : 0
onXChanged: {
if (win.connectedFrameMode)
win.popupChromeGeometryChanged();
}
onYChanged: {
if (win.connectedFrameMode)
win.popupChromeGeometryChanged();
}
} }
] ]
} }

View File

@@ -8,24 +8,40 @@ QtObject {
property var modelData property var modelData
property int topMargin: 0 property int topMargin: 0
readonly property bool compactMode: SettingsData.notificationCompactMode readonly property bool compactMode: SettingsData.notificationCompactMode
readonly property bool connectedFrameMode: SettingsData.connectedFrameModeActive readonly property bool notificationConnectedMode: SettingsData.frameEnabled
&& Theme.isConnectedEffect
&& SettingsData.isScreenInPreferences(manager.modelData, SettingsData.frameScreenPreferences)
readonly property string notifBarSide: {
const pos = SettingsData.notificationPopupPosition;
if (pos === -1) return "top";
switch (pos) {
case SettingsData.Position.Top: return "right";
case SettingsData.Position.Left: return "left";
case SettingsData.Position.BottomCenter: return "bottom";
case SettingsData.Position.Right: return "right";
case SettingsData.Position.Bottom: return "left";
default: return "top";
}
}
readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding
readonly property real popupIconSize: compactMode ? Theme.notificationIconSizeCompact : Theme.notificationIconSizeNormal readonly property real popupIconSize: compactMode ? Theme.notificationIconSizeCompact : Theme.notificationIconSizeNormal
readonly property real actionButtonHeight: compactMode ? 20 : 24 readonly property real actionButtonHeight: compactMode ? 20 : 24
readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
readonly property real popupSpacing: connectedFrameMode ? 0 : (compactMode ? 0 : Theme.spacingXS) readonly property real popupSpacing: notificationConnectedMode ? 0 : (compactMode ? 0 : Theme.spacingXS)
readonly property real collapsedContentHeight: Math.max(popupIconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)) readonly property real collapsedContentHeight: Math.max(popupIconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2))
readonly property int baseNotificationHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing + popupSpacing readonly property int baseNotificationHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing + popupSpacing
property var popupWindows: [] property var popupWindows: []
property var destroyingWindows: new Set() property var destroyingWindows: new Set()
property var pendingDestroys: [] property var pendingDestroys: []
property int destroyDelayMs: 100 property int destroyDelayMs: 100
property bool _chromeSyncPending: false
property Component popupComponent property Component popupComponent
popupComponent: Component { popupComponent: Component {
NotificationPopup { NotificationPopup {
onExitFinished: manager._onPopupExitFinished(this) onExitFinished: manager._onPopupExitFinished(this)
onPopupHeightChanged: manager._onPopupHeightChanged(this) onPopupHeightChanged: manager._onPopupHeightChanged(this)
onPopupChromeGeometryChanged: manager._onPopupChromeGeometryChanged(this)
} }
} }
@@ -109,6 +125,14 @@ QtObject {
return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData; return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData;
} }
function _layoutWindows() {
return popupWindows.filter(p => _isValidWindow(p) && p.notificationData?.popup && !p.exiting && (!p.popupLayoutReservesSlot || p.popupLayoutReservesSlot()));
}
function _chromeWindows() {
return popupWindows.filter(p => p && p.status !== Component.Null && p.visible && !p._finalized && p.hasValidData && (p.notificationData?.popup || p.exiting));
}
function _isFocusedScreen() { function _isFocusedScreen() {
if (!SettingsData.notificationFocusedMonitor) if (!SettingsData.notificationFocusedMonitor)
return true; return true;
@@ -117,19 +141,25 @@ QtObject {
} }
function _sync(newWrappers) { function _sync(newWrappers) {
let needsReposition = false;
for (const p of popupWindows.slice()) { for (const p of popupWindows.slice()) {
if (!_isValidWindow(p) || p.exiting) if (!_isValidWindow(p) || p.exiting)
continue; continue;
if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1) { if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1) {
p.notificationData.removedByLimit = true; p.notificationData.removedByLimit = true;
p.notificationData.popup = false; p.notificationData.popup = false;
needsReposition = true;
} }
} }
for (const w of newWrappers) { for (const w of newWrappers) {
if (w && !_hasWindowFor(w) && _isFocusedScreen()) if (w && !_hasWindowFor(w) && _isFocusedScreen()) {
_insertAtTop(w); _insertAtTop(w);
needsReposition = false;
} }
} }
if (needsReposition)
_repositionAll();
}
function _popupHeight(p) { function _popupHeight(p) {
return (p.alignedHeight || p.implicitHeight || (baseNotificationHeight - popupSpacing)) + popupSpacing; return (p.alignedHeight || p.implicitHeight || (baseNotificationHeight - popupSpacing)) + popupSpacing;
@@ -158,7 +188,7 @@ QtObject {
} }
function _repositionAll() { function _repositionAll() {
const active = popupWindows.filter(p => _isValidWindow(p) && p.notificationData?.popup && !p.exiting); const active = _layoutWindows();
const pinnedSlots = []; const pinnedSlots = [];
for (const p of active) { for (const p of active) {
@@ -182,6 +212,168 @@ QtObject {
win.screenY = currentY; win.screenY = currentY;
currentY += _popupHeight(win); currentY += _popupHeight(win);
} }
_scheduleNotificationChromeSync();
}
function _scheduleNotificationChromeSync() {
if (_chromeSyncPending)
return;
_chromeSyncPending = true;
Qt.callLater(() => {
_chromeSyncPending = false;
_syncNotificationChromeState();
});
}
function _popupChromeRect(p, useMotionOffset) {
if (!p || !p.screen)
return null;
const motionX = useMotionOffset && p.popupChromeMotionX ? p.popupChromeMotionX() : 0;
const motionY = useMotionOffset && p.popupChromeMotionY ? p.popupChromeMotionY() : 0;
const x = (p.getContentX ? p.getContentX() : 0) + motionX;
const y = (p.getContentY ? p.getContentY() : 0) + motionY;
const w = p.alignedWidth || 0;
const h = Math.max(p.alignedHeight || 0, baseNotificationHeight);
if (w <= 0 || h <= 0)
return null;
return {
x: x,
y: y,
right: x + w,
bottom: y + h
};
}
function _popupChromeBoundsRect(p, trailing, useMotionOffset) {
const rect = _popupChromeRect(p, useMotionOffset);
if (!rect || p !== trailing || !p.popupChromeReleaseProgress)
return rect;
const progress = Math.max(0, Math.min(1, p.popupChromeReleaseProgress()));
if (progress <= 0)
return rect;
const anchorsTop = _stackAnchorsTop();
const h = Math.max(0, rect.bottom - rect.y);
const shrink = h * progress;
if (anchorsTop)
rect.bottom = Math.max(rect.y, rect.bottom - shrink);
else
rect.y = Math.min(rect.bottom, rect.y + shrink);
return rect;
}
function _stackAnchorsTop() {
const pos = SettingsData.notificationPopupPosition;
return pos === -1 || pos === SettingsData.Position.Top || pos === SettingsData.Position.Left;
}
function _trailingChromeWindow(candidates) {
const anchorsTop = _stackAnchorsTop();
let trailing = null;
let edge = anchorsTop ? -Infinity : Infinity;
for (const p of candidates) {
const rect = _popupChromeRect(p, false);
if (!rect)
continue;
const candidateEdge = anchorsTop ? rect.bottom : rect.y;
if ((anchorsTop && candidateEdge > edge) || (!anchorsTop && candidateEdge < edge)) {
edge = candidateEdge;
trailing = p;
}
}
return trailing;
}
function _chromeWindowReservesSlot(p, trailing) {
if (p === trailing)
return true;
return !p.popupChromeReservesSlot || p.popupChromeReservesSlot();
}
function _stackAnchoredChromeEdge(candidates) {
const anchorsTop = _stackAnchorsTop();
let edge = anchorsTop ? Infinity : -Infinity;
for (const p of candidates) {
const rect = _popupChromeRect(p, false);
if (!rect)
continue;
if (anchorsTop && rect.y < edge)
edge = rect.y;
if (!anchorsTop && rect.bottom > edge)
edge = rect.bottom;
}
if (edge === Infinity || edge === -Infinity)
return null;
return {
anchorsTop: anchorsTop,
edge: edge
};
}
function _syncNotificationChromeState() {
const screenName = manager.modelData?.name || "";
if (!screenName)
return;
if (!notificationConnectedMode) {
ConnectedModeState.clearNotificationState(screenName);
return;
}
const chromeCandidates = _chromeWindows();
if (chromeCandidates.length === 0) {
ConnectedModeState.clearNotificationState(screenName);
return;
}
const trailing = chromeCandidates.length > 1 ? _trailingChromeWindow(chromeCandidates) : null;
let active = chromeCandidates;
if (chromeCandidates.length > 1) {
const reserving = chromeCandidates.filter(p => _chromeWindowReservesSlot(p, trailing));
if (reserving.length > 0)
active = reserving;
}
let minX = Infinity;
let minY = Infinity;
let maxXEnd = -Infinity;
let maxYEnd = -Infinity;
const useMotionOffset = active.length === 1 && active[0].popupChromeMotionActive && active[0].popupChromeMotionActive();
for (const p of active) {
const rect = _popupChromeBoundsRect(p, trailing, useMotionOffset);
if (!rect)
continue;
if (rect.x < minX)
minX = rect.x;
if (rect.y < minY)
minY = rect.y;
if (rect.right > maxXEnd)
maxXEnd = rect.right;
if (rect.bottom > maxYEnd)
maxYEnd = rect.bottom;
}
const stackEdge = _stackAnchoredChromeEdge(chromeCandidates);
if (stackEdge !== null) {
if (stackEdge.anchorsTop && stackEdge.edge < minY)
minY = stackEdge.edge;
if (!stackEdge.anchorsTop && stackEdge.edge > maxYEnd)
maxYEnd = stackEdge.edge;
}
if (minX === Infinity || minY === Infinity || maxXEnd <= minX || maxYEnd <= minY) {
ConnectedModeState.clearNotificationState(screenName);
return;
}
ConnectedModeState.setNotificationState(screenName, {
visible: true,
barSide: notifBarSide,
bodyX: minX,
bodyY: minY,
bodyW: maxXEnd - minX,
bodyH: maxYEnd - minY
});
}
function _onPopupChromeGeometryChanged(p) {
if (!p || popupWindows.indexOf(p) === -1)
return;
_scheduleNotificationChromeSync();
} }
function _onPopupHeightChanged(p) { function _onPopupHeightChanged(p) {
@@ -228,8 +420,15 @@ QtObject {
} }
popupWindows = []; popupWindows = [];
destroyingWindows.clear(); destroyingWindows.clear();
_chromeSyncPending = false;
_syncNotificationChromeState();
} }
onNotificationConnectedModeChanged: _scheduleNotificationChromeSync()
onNotifBarSideChanged: _scheduleNotificationChromeSync()
onModelDataChanged: _scheduleNotificationChromeSync()
onTopMarginChanged: _repositionAll()
onPopupWindowsChanged: { onPopupWindowsChanged: {
if (popupWindows.length > 0 && !sweeper.running) { if (popupWindows.length > 0 && !sweeper.running) {
sweeper.start(); sweeper.start();

View File

@@ -114,7 +114,7 @@ Item {
} }
radiusX: root._cr radiusX: root._cr
radiusY: root._cr radiusY: root._cr
direction: PathArc.Counterclockwise direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
} }
// Body edge to first convex corner // Body edge to first convex corner
@@ -169,7 +169,7 @@ Item {
} }
radiusX: root._sr radiusX: root._sr
radiusY: root._sr radiusY: root._sr
direction: PathArc.Clockwise direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise
} }
// Far edge // Far edge
@@ -224,7 +224,7 @@ Item {
} }
radiusX: root._sr radiusX: root._sr
radiusY: root._sr radiusY: root._sr
direction: PathArc.Clockwise direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise
} }
// Body edge to second concave arc // Body edge to second concave arc
@@ -279,7 +279,7 @@ Item {
} }
radiusX: root._cr radiusX: root._cr
radiusY: root._cr radiusY: root._cr
direction: PathArc.Counterclockwise direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
} }
} }
} }