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

notifications: fix compact spacing, reveal header bar, add bottom center

position, pointing hand cursor fix
This commit is contained in:
bbedward
2026-02-13 21:44:27 -05:00
parent ed495d4396
commit 62bc25782c
4 changed files with 268 additions and 266 deletions

View File

@@ -124,7 +124,7 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: cardPadding anchors.topMargin: cardPadding
anchors.leftMargin: Theme.spacingL anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL + ((cardHoverHandler.hovered || (keyboardNavigationActive && (isGroupSelected || (expanded && selectedNotificationIndex >= 0)))) ? Theme.notificationHoverRevealMargin : 0) anchors.rightMargin: Theme.spacingL + Theme.notificationHoverRevealMargin
height: collapsedContentHeight + extraHeight height: collapsedContentHeight + extraHeight
visible: !expanded visible: !expanded
@@ -146,9 +146,7 @@ Rectangle {
height: iconSize height: iconSize
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: descriptionExpanded anchors.topMargin: descriptionExpanded ? Math.max(0, (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 3)) / 2 - iconSize / 2) : Math.max(0, textContainer.height / 2 - iconSize / 2)
? Math.max(0, (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 3)) / 2 - iconSize / 2)
: Math.max(0, textContainer.height / 2 - iconSize / 2)
imageSource: { imageSource: {
if (hasNotificationImage) if (hasNotificationImage)
@@ -327,7 +325,7 @@ Rectangle {
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingL + ((cardHoverHandler.hovered || (keyboardNavigationActive && (isGroupSelected || (expanded && selectedNotificationIndex >= 0)))) ? Theme.notificationHoverRevealMargin : 0) anchors.rightMargin: Theme.spacingL + Theme.notificationHoverRevealMargin
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -375,7 +373,7 @@ Rectangle {
required property int index required property int index
readonly property bool messageExpanded: NotificationService.expandedMessages[modelData?.notification?.id] || false readonly property bool messageExpanded: NotificationService.expandedMessages[modelData?.notification?.id] || false
readonly property bool isSelected: root.selectedNotificationIndex === index readonly property bool isSelected: root.selectedNotificationIndex === index
readonly property bool actionsVisible: expandedDelegateHoverHandler.hovered || (root.keyboardNavigationActive && isSelected) readonly property bool actionsVisible: true
readonly property real expandedIconSize: compactMode ? Theme.notificationExpandedIconSizeCompact : Theme.notificationExpandedIconSizeNormal readonly property real expandedIconSize: compactMode ? Theme.notificationExpandedIconSizeCompact : Theme.notificationExpandedIconSizeNormal
HoverHandler { HoverHandler {
@@ -437,196 +435,240 @@ Rectangle {
} }
Item { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: compactMode ? Theme.spacingS : Theme.spacingM anchors.margins: compactMode ? Theme.spacingS : Theme.spacingM
anchors.bottomMargin: contentSpacing anchors.bottomMargin: contentSpacing
DankCircularImage { DankCircularImage {
id: messageIcon id: messageIcon
readonly property string rawImage: modelData?.image || "" readonly property string rawImage: modelData?.image || ""
readonly property string iconFromImage: { readonly property string iconFromImage: {
if (rawImage.startsWith("image://icon/")) if (rawImage.startsWith("image://icon/"))
return rawImage.substring(13); return rawImage.substring(13);
return "";
}
readonly property bool imageHasSpecialPrefix: {
const icon = iconFromImage;
return icon.startsWith("material:") || icon.startsWith("svg:") || icon.startsWith("unicode:") || icon.startsWith("image:");
}
readonly property bool hasNotificationImage: rawImage !== "" && !rawImage.startsWith("image://icon/")
width: expandedIconSize
height: expandedIconSize
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: compactMode ? Theme.spacingM : Theme.spacingXL
imageSource: {
if (hasNotificationImage)
return modelData.cleanImage;
if (imageHasSpecialPrefix)
return ""; return "";
const appIcon = modelData?.appIcon; }
if (!appIcon) readonly property bool imageHasSpecialPrefix: {
return iconFromImage ? "image://icon/" + iconFromImage : ""; const icon = iconFromImage;
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/")) return icon.startsWith("material:") || icon.startsWith("svg:") || icon.startsWith("unicode:") || icon.startsWith("image:");
return appIcon; }
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:")) readonly property bool hasNotificationImage: rawImage !== "" && !rawImage.startsWith("image://icon/")
return "";
return Quickshell.iconPath(appIcon, true);
}
fallbackIcon: { width: expandedIconSize
if (imageHasSpecialPrefix) height: expandedIconSize
return iconFromImage;
return modelData?.appIcon || iconFromImage || "";
}
fallbackText: {
const appName = modelData?.appName || "?";
return appName.charAt(0).toUpperCase();
}
}
Item {
anchors.left: messageIcon.right
anchors.leftMargin: Theme.spacingM
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.top: parent.top
anchors.bottom: parent.bottom
Column {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: buttonArea.top anchors.topMargin: compactMode ? Theme.spacingM : Theme.spacingXL
anchors.bottomMargin: contentSpacing
spacing: Theme.notificationContentSpacing
Row { imageSource: {
width: parent.width if (hasNotificationImage)
spacing: Theme.spacingXS return modelData.cleanImage;
readonly property real reservedTrailingWidth: expandedDelegateSeparator.implicitWidth + Math.max(expandedDelegateTimeText.implicitWidth, 72) + spacing if (imageHasSpecialPrefix)
return "";
StyledText { const appIcon = modelData?.appIcon;
id: expandedDelegateTitleText if (!appIcon)
width: Math.min(implicitWidth, Math.max(0, parent.width - parent.reservedTrailingWidth)) return iconFromImage ? "image://icon/" + iconFromImage : "";
text: { if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
let title = modelData?.summary || ""; return appIcon;
const appName = modelData?.appName || ""; if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
const prefix = appName + ""; return "";
if (appName && title.toLowerCase().startsWith(prefix.toLowerCase())) { return Quickshell.iconPath(appIcon, true);
title = title.substring(prefix.length);
}
return title;
}
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
StyledText {
id: expandedDelegateSeparator
text: (expandedDelegateTitleText.text.length > 0 && expandedDelegateTimeText.text.length > 0) ? " • " : ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal
}
StyledText {
id: expandedDelegateTimeText
text: modelData?.timeStr || ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal
visible: text.length > 0
}
} }
StyledText { fallbackIcon: {
id: bodyText if (imageHasSpecialPrefix)
property bool hasMoreText: truncated return iconFromImage;
return modelData?.appIcon || iconFromImage || "";
}
text: modelData?.htmlBody || "" fallbackText: {
color: Theme.surfaceVariantText const appName = modelData?.appName || "?";
font.pixelSize: Theme.fontSizeSmall return appName.charAt(0).toUpperCase();
width: parent.width
elide: messageExpanded ? Text.ElideNone : Text.ElideRight
maximumLineCount: messageExpanded ? -1 : 2
wrapMode: Text.WordWrap
visible: text.length > 0
linkColor: Theme.primary
onLinkActivated: link => Qt.openUrlExternally(link)
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (bodyText.hasMoreText || messageExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: mouse => {
if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
NotificationService.toggleMessageExpansion(modelData?.notification?.id || "");
}
}
propagateComposedEvents: true
onPressed: mouse => {
if (parent.hoveredLink) {
mouse.accepted = false;
}
}
onReleased: mouse => {
if (parent.hoveredLink) {
mouse.accepted = false;
}
}
}
} }
} }
Item { Item {
id: buttonArea anchors.left: messageIcon.right
anchors.left: parent.left anchors.leftMargin: Theme.spacingM
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
height: actionButtonHeight + contentSpacing
Row { Column {
visible: expandedDelegateWrapper.actionsVisible anchors.left: parent.left
opacity: visible ? 1 : 0
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.top: parent.top
spacing: contentSpacing anchors.bottom: buttonArea.top
anchors.bottomMargin: contentSpacing
spacing: Theme.notificationContentSpacing
Behavior on opacity { Row {
NumberAnimation { duration: Theme.shortDuration; easing.type: Theme.standardEasing } width: parent.width
spacing: Theme.spacingXS
readonly property real reservedTrailingWidth: expandedDelegateSeparator.implicitWidth + Math.max(expandedDelegateTimeText.implicitWidth, 72) + spacing
StyledText {
id: expandedDelegateTitleText
width: Math.min(implicitWidth, Math.max(0, parent.width - parent.reservedTrailingWidth))
text: {
let title = modelData?.summary || "";
const appName = modelData?.appName || "";
const prefix = appName + " • ";
if (appName && title.toLowerCase().startsWith(prefix.toLowerCase())) {
title = title.substring(prefix.length);
}
return title;
}
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
StyledText {
id: expandedDelegateSeparator
text: (expandedDelegateTitleText.text.length > 0 && expandedDelegateTimeText.text.length > 0) ? " • " : ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal
}
StyledText {
id: expandedDelegateTimeText
text: modelData?.timeStr || ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal
visible: text.length > 0
}
} }
Repeater { StyledText {
model: modelData?.actions || [] id: bodyText
property bool hasMoreText: truncated
text: modelData?.htmlBody || ""
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
width: parent.width
elide: messageExpanded ? Text.ElideNone : Text.ElideRight
maximumLineCount: messageExpanded ? -1 : 2
wrapMode: Text.WordWrap
visible: text.length > 0
linkColor: Theme.primary
onLinkActivated: link => Qt.openUrlExternally(link)
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (bodyText.hasMoreText || messageExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: mouse => {
if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
NotificationService.toggleMessageExpansion(modelData?.notification?.id || "");
}
}
propagateComposedEvents: true
onPressed: mouse => {
if (parent.hoveredLink) {
mouse.accepted = false;
}
}
onReleased: mouse => {
if (parent.hoveredLink) {
mouse.accepted = false;
}
}
}
}
}
Item {
id: buttonArea
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: actionButtonHeight + contentSpacing
Row {
visible: expandedDelegateWrapper.actionsVisible
opacity: visible ? 1 : 0
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: contentSpacing
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Repeater {
model: modelData?.actions || []
Rectangle {
property bool isHovered: false
width: Math.max(expandedActionText.implicitWidth + Theme.spacingM, Theme.notificationActionMinWidth)
height: actionButtonHeight
radius: Theme.notificationButtonCornerRadius
color: isHovered ? Theme.withAlpha(Theme.primary, Theme.stateLayerHover) : "transparent"
StyledText {
id: expandedActionText
text: {
const baseText = modelData.text || "Open";
if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0))
return `${baseText} (${index + 1})`;
return baseText;
}
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
anchors.centerIn: parent
elide: Text.ElideRight
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: parent.isHovered = true
onExited: parent.isHovered = false
onClicked: {
if (modelData && modelData.invoke)
modelData.invoke();
}
}
}
}
Rectangle { Rectangle {
id: expandedDelegateDismissBtn
property bool isHovered: false property bool isHovered: false
width: Math.max(expandedActionText.implicitWidth + Theme.spacingM, Theme.notificationActionMinWidth) visible: expandedDelegateWrapper.actionsVisible
opacity: visible ? 1 : 0
width: Math.max(expandedClearText.implicitWidth + Theme.spacingM, Theme.notificationActionMinWidth)
height: actionButtonHeight height: actionButtonHeight
radius: Theme.notificationButtonCornerRadius radius: Theme.notificationButtonCornerRadius
color: isHovered ? Theme.withAlpha(Theme.primary, Theme.stateLayerHover) : "transparent" color: isHovered ? Theme.withAlpha(Theme.primary, Theme.stateLayerHover) : "transparent"
StyledText { Behavior on opacity {
id: expandedActionText NumberAnimation {
text: { duration: Theme.shortDuration
const baseText = modelData.text || "Open"; easing.type: Theme.standardEasing
if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0))
return `${baseText} (${index + 1})`;
return baseText;
} }
}
StyledText {
id: expandedClearText
text: I18n.tr("Dismiss")
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
anchors.centerIn: parent anchors.centerIn: parent
elide: Text.ElideRight
} }
MouseArea { MouseArea {
@@ -635,53 +677,15 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onEntered: parent.isHovered = true onEntered: parent.isHovered = true
onExited: parent.isHovered = false onExited: parent.isHovered = false
onClicked: { onClicked: NotificationService.dismissNotification(modelData)
if (modelData && modelData.invoke)
modelData.invoke();
}
} }
} }
} }
Rectangle {
id: expandedDelegateDismissBtn
property bool isHovered: false
visible: expandedDelegateWrapper.actionsVisible
opacity: visible ? 1 : 0
width: Math.max(expandedClearText.implicitWidth + Theme.spacingM, Theme.notificationActionMinWidth)
height: actionButtonHeight
radius: Theme.notificationButtonCornerRadius
color: isHovered ? Theme.withAlpha(Theme.primary, Theme.stateLayerHover) : "transparent"
Behavior on opacity {
NumberAnimation { duration: Theme.shortDuration; easing.type: Theme.standardEasing }
}
StyledText {
id: expandedClearText
text: I18n.tr("Dismiss")
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: parent.isHovered = true
onExited: parent.isHovered = false
onClicked: NotificationService.dismissNotification(modelData)
}
}
} }
} }
} }
} }
} }
}
} }
DragHandler { DragHandler {
@@ -722,18 +726,13 @@ Rectangle {
} }
Row { Row {
visible: !expanded && (cardHoverHandler.hovered || (keyboardNavigationActive && isGroupSelected)) visible: !expanded
opacity: visible ? 1 : 0
anchors.right: clearButton.visible ? clearButton.left : parent.right anchors.right: clearButton.visible ? clearButton.left : parent.right
anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
anchors.top: collapsedContent.bottom anchors.top: collapsedContent.bottom
anchors.topMargin: contentSpacing anchors.topMargin: contentSpacing
spacing: contentSpacing spacing: contentSpacing
Behavior on opacity {
NumberAnimation { duration: Theme.shortDuration; easing.type: Theme.standardEasing }
}
Repeater { Repeater {
model: notificationGroup?.latestNotification?.actions || [] model: notificationGroup?.latestNotification?.actions || []
@@ -783,11 +782,7 @@ Rectangle {
property bool isHovered: false property bool isHovered: false
readonly property int actionCount: (notificationGroup?.latestNotification?.actions || []).length readonly property int actionCount: (notificationGroup?.latestNotification?.actions || []).length
visible: !expanded && actionCount < 3 && (cardHoverHandler.hovered || (keyboardNavigationActive && isGroupSelected)) visible: !expanded && actionCount < 3
opacity: visible ? 1 : 0
Behavior on opacity {
NumberAnimation { duration: Theme.shortDuration; easing.type: Theme.standardEasing }
}
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingL anchors.rightMargin: Theme.spacingL
anchors.top: collapsedContent.bottom anchors.top: collapsedContent.bottom
@@ -819,6 +814,7 @@ Rectangle {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
visible: !expanded && (notificationGroup?.count || 0) > 1 && !descriptionExpanded visible: !expanded && (notificationGroup?.count || 0) > 1 && !descriptionExpanded
cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.userInitiatedExpansion = true; root.userInitiatedExpansion = true;
NotificationService.toggleGroupExpansion(notificationGroup?.key || ""); NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
@@ -834,15 +830,6 @@ Rectangle {
anchors.rightMargin: Theme.spacingL anchors.rightMargin: Theme.spacingL
width: compactMode ? 52 : 60 width: compactMode ? 52 : 60
height: compactMode ? 24 : 28 height: compactMode ? 24 : 28
opacity: (cardHoverHandler.hovered || (keyboardNavigationActive && (isGroupSelected || (expanded && selectedNotificationIndex >= 0)))) ? 1 : 0
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
DankActionButton { DankActionButton {
anchors.left: parent.left anchors.left: parent.left
@@ -949,7 +936,7 @@ Rectangle {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
z: 10 z: -2
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.RightButton && notificationGroup) { if (mouse.button === Qt.RightButton && notificationGroup) {
notificationCardContextMenu.popup(); notificationCardContextMenu.popup();

View File

@@ -141,9 +141,11 @@ PanelWindow {
} }
property bool isTopCenter: SettingsData.notificationPopupPosition === -1 property bool isTopCenter: SettingsData.notificationPopupPosition === -1
property bool isBottomCenter: SettingsData.notificationPopupPosition === SettingsData.Position.BottomCenter
property bool isCenterPosition: isTopCenter || isBottomCenter
anchors.top: isTopCenter || SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Left anchors.top: isTopCenter || SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Left
anchors.bottom: SettingsData.notificationPopupPosition === SettingsData.Position.Bottom || SettingsData.notificationPopupPosition === SettingsData.Position.Right anchors.bottom: isBottomCenter || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom || SettingsData.notificationPopupPosition === SettingsData.Position.Right
anchors.left: SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom anchors.left: SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom
anchors.right: SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Right anchors.right: SettingsData.notificationPopupPosition === SettingsData.Position.Top || SettingsData.notificationPopupPosition === SettingsData.Position.Right
@@ -182,7 +184,7 @@ PanelWindow {
function getBottomMargin() { function getBottomMargin() {
const popupPos = SettingsData.notificationPopupPosition; const popupPos = SettingsData.notificationPopupPosition;
const isBottom = popupPos === SettingsData.Position.Bottom || popupPos === SettingsData.Position.Right; const isBottom = isBottomCenter || popupPos === SettingsData.Position.Bottom || popupPos === SettingsData.Position.Right;
if (!isBottom) if (!isBottom)
return 0; return 0;
@@ -192,7 +194,7 @@ PanelWindow {
} }
function getLeftMargin() { function getLeftMargin() {
if (isTopCenter) if (isCenterPosition)
return screen ? (screen.width - implicitWidth) / 2 : 0; return screen ? (screen.width - implicitWidth) / 2 : 0;
const popupPos = SettingsData.notificationPopupPosition; const popupPos = SettingsData.notificationPopupPosition;
@@ -205,7 +207,7 @@ PanelWindow {
} }
function getRightMargin() { function getRightMargin() {
if (isTopCenter) if (isCenterPosition)
return 0; return 0;
const popupPos = SettingsData.notificationPopupPosition; const popupPos = SettingsData.notificationPopupPosition;
@@ -232,7 +234,7 @@ PanelWindow {
visible: !win._finalized visible: !win._finalized
property real swipeOffset: 0 property real swipeOffset: 0
readonly property real dismissThreshold: isTopCenter ? height * 0.4 : width * 0.35 readonly property real dismissThreshold: isCenterPosition ? height * 0.4 : width * 0.35
readonly property bool swipeActive: swipeDragHandler.active readonly property bool swipeActive: swipeDragHandler.active
property bool swipeDismissing: false property bool swipeDismissing: false
@@ -329,7 +331,7 @@ PanelWindow {
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: cardPadding anchors.topMargin: cardPadding
anchors.leftMargin: Theme.spacingL anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL + (cardHoverHandler.hovered ? Theme.notificationHoverRevealMargin : 0) anchors.rightMargin: Theme.spacingL + Theme.notificationHoverRevealMargin
height: collapsedContentHeight + extraHeight height: collapsedContentHeight + extraHeight
DankCircularImage { DankCircularImage {
@@ -352,9 +354,7 @@ PanelWindow {
height: popupIconSize height: popupIconSize
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: descriptionExpanded anchors.topMargin: descriptionExpanded ? Math.max(0, (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 3)) / 2 - popupIconSize / 2) : Math.max(0, textContainer.height / 2 - popupIconSize / 2)
? Math.max(0, (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 3)) / 2 - popupIconSize / 2)
: Math.max(0, textContainer.height / 2 - popupIconSize / 2)
imageSource: { imageSource: {
if (!notificationData) if (!notificationData)
@@ -469,19 +469,9 @@ PanelWindow {
anchors.topMargin: cardPadding anchors.topMargin: cardPadding
anchors.rightMargin: Theme.spacingL anchors.rightMargin: Theme.spacingL
iconName: "close" iconName: "close"
iconSize: compactMode ? 16 : 18 iconSize: compactMode ? 14 : 16
buttonSize: compactMode ? 24 : 28 buttonSize: compactMode ? 20 : 24
z: 15 z: 15
opacity: cardHoverHandler.hovered ? 1 : 0
visible: opacity > 0
enabled: cardHoverHandler.hovered
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
onClicked: { onClicked: {
if (notificationData && !win.exiting) if (notificationData && !win.exiting)
@@ -500,7 +490,10 @@ PanelWindow {
z: 20 z: 20
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: Theme.shortDuration; easing.type: Theme.standardEasing } NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
} }
Repeater { Repeater {
@@ -552,7 +545,10 @@ PanelWindow {
visible: actionCount < 3 && cardHoverHandler.hovered visible: actionCount < 3 && cardHoverHandler.hovered
opacity: visible ? 1 : 0 opacity: visible ? 1 : 0
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: Theme.shortDuration; easing.type: Theme.standardEasing } NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
} }
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingL anchors.rightMargin: Theme.spacingL
@@ -594,6 +590,7 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true propagateComposedEvents: true
z: -1 z: -1
onEntered: { onEntered: {
@@ -624,8 +621,8 @@ PanelWindow {
DragHandler { DragHandler {
id: swipeDragHandler id: swipeDragHandler
target: null target: null
xAxis.enabled: !isTopCenter xAxis.enabled: !isCenterPosition
yAxis.enabled: isTopCenter yAxis.enabled: isCenterPosition
onActiveChanged: { onActiveChanged: {
if (active || win.exiting || content.swipeDismissing) if (active || win.exiting || content.swipeDismissing)
@@ -643,9 +640,11 @@ PanelWindow {
if (win.exiting) if (win.exiting)
return; return;
const raw = isTopCenter ? translation.y : translation.x; const raw = isCenterPosition ? translation.y : translation.x;
if (isTopCenter) { if (isTopCenter) {
content.swipeOffset = Math.min(0, raw); content.swipeOffset = Math.min(0, raw);
} else if (isBottomCenter) {
content.swipeOffset = Math.max(0, raw);
} else { } else {
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom; const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
content.swipeOffset = isLeft ? Math.min(0, raw) : Math.max(0, raw); content.swipeOffset = isLeft ? Math.min(0, raw) : Math.max(0, raw);
@@ -653,7 +652,7 @@ PanelWindow {
} }
} }
opacity: 1 - Math.abs(content.swipeOffset) / (isTopCenter ? content.height : content.width * 0.6) opacity: 1 - Math.abs(content.swipeOffset) / (isCenterPosition ? content.height : content.width * 0.6)
Behavior on opacity { Behavior on opacity {
enabled: !content.swipeActive enabled: !content.swipeActive
@@ -674,7 +673,7 @@ PanelWindow {
id: swipeDismissAnim id: swipeDismissAnim
target: content target: content
property: "swipeOffset" property: "swipeOffset"
to: isTopCenter ? -content.height : (SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom ? -content.width : content.width) to: isTopCenter ? -content.height : isBottomCenter ? content.height : (SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom ? -content.width : content.width)
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
onStopped: { onStopped: {
@@ -686,18 +685,18 @@ PanelWindow {
transform: [ transform: [
Translate { Translate {
id: swipeTx id: swipeTx
x: isTopCenter ? 0 : content.swipeOffset x: isCenterPosition ? 0 : content.swipeOffset
y: isTopCenter ? content.swipeOffset : 0 y: isCenterPosition ? content.swipeOffset : 0
}, },
Translate { Translate {
id: tx id: tx
x: { x: {
if (isTopCenter) if (isCenterPosition)
return 0; return 0;
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom; const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
return isLeft ? -Anims.slidePx : Anims.slidePx; return isLeft ? -Anims.slidePx : Anims.slidePx;
} }
y: isTopCenter ? -Anims.slidePx : 0 y: isTopCenter ? -Anims.slidePx : isBottomCenter ? Anims.slidePx : 0
} }
] ]
} }
@@ -706,20 +705,22 @@ PanelWindow {
id: enterX id: enterX
target: tx target: tx
property: isTopCenter ? "y" : "x" property: isCenterPosition ? "y" : "x"
from: { from: {
if (isTopCenter) if (isTopCenter)
return -Anims.slidePx; return -Anims.slidePx;
if (isBottomCenter)
return Anims.slidePx;
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom; const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
return isLeft ? -Anims.slidePx : Anims.slidePx; return isLeft ? -Anims.slidePx : Anims.slidePx;
} }
to: 0 to: 0
duration: Theme.notificationEnterDuration duration: Theme.notificationEnterDuration
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: isTopCenter ? Theme.expressiveCurves.standardDecel : Theme.expressiveCurves.emphasizedDecel easing.bezierCurve: isCenterPosition ? Theme.expressiveCurves.standardDecel : Theme.expressiveCurves.emphasizedDecel
onStopped: { onStopped: {
if (!win.exiting && !win._isDestroying) { if (!win.exiting && !win._isDestroying) {
if (isTopCenter) { if (isCenterPosition) {
if (Math.abs(tx.y) < 0.5) if (Math.abs(tx.y) < 0.5)
win.entered(); win.entered();
} else { } else {
@@ -737,11 +738,13 @@ PanelWindow {
PropertyAnimation { PropertyAnimation {
target: tx target: tx
property: isTopCenter ? "y" : "x" property: isCenterPosition ? "y" : "x"
from: 0 from: 0
to: { to: {
if (isTopCenter) if (isTopCenter)
return -Anims.slidePx; return -Anims.slidePx;
if (isBottomCenter)
return Anims.slidePx;
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom; const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
return isLeft ? -Anims.slidePx : Anims.slidePx; return isLeft ? -Anims.slidePx : Anims.slidePx;
} }

View File

@@ -11,8 +11,9 @@ QtObject {
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 popupSpacing: Theme.spacingXS readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
readonly property int baseNotificationHeight: cardPadding * 2 + popupIconSize + actionButtonHeight + Theme.spacingS + popupSpacing readonly property real popupSpacing: compactMode ? 0 : Theme.spacingXS
readonly property int baseNotificationHeight: cardPadding * 2 + popupIconSize + actionButtonHeight + contentSpacing + popupSpacing
property int maxTargetNotifications: 4 property int maxTargetNotifications: 4
property var popupWindows: [] // strong refs to windows (live until exitFinished) property var popupWindows: [] // strong refs to windows (live until exitFinished)
property var destroyingWindows: new Set() property var destroyingWindows: new Set()

View File

@@ -211,22 +211,33 @@ Item {
return I18n.tr("Top Left", "screen position option"); return I18n.tr("Top Left", "screen position option");
case SettingsData.Position.Right: case SettingsData.Position.Right:
return I18n.tr("Bottom Right", "screen position option"); return I18n.tr("Bottom Right", "screen position option");
case SettingsData.Position.BottomCenter:
return I18n.tr("Bottom Center", "screen position option");
default: default:
return I18n.tr("Top Right", "screen position option"); return I18n.tr("Top Right", "screen position option");
} }
} }
options: [I18n.tr("Top Right", "screen position option"), I18n.tr("Top Left", "screen position option"), I18n.tr("Top Center", "screen position option"), I18n.tr("Bottom Right", "screen position option"), I18n.tr("Bottom Left", "screen position option")] options: [I18n.tr("Top Right", "screen position option"), I18n.tr("Top Left", "screen position option"), I18n.tr("Top Center", "screen position option"), I18n.tr("Bottom Center", "screen position option"), I18n.tr("Bottom Right", "screen position option"), I18n.tr("Bottom Left", "screen position option")]
onValueChanged: value => { onValueChanged: value => {
if (value === I18n.tr("Top Right", "screen position option")) { switch (value) {
case I18n.tr("Top Right", "screen position option"):
SettingsData.set("notificationPopupPosition", SettingsData.Position.Top); SettingsData.set("notificationPopupPosition", SettingsData.Position.Top);
} else if (value === I18n.tr("Top Left", "screen position option")) { break;
case I18n.tr("Top Left", "screen position option"):
SettingsData.set("notificationPopupPosition", SettingsData.Position.Left); SettingsData.set("notificationPopupPosition", SettingsData.Position.Left);
} else if (value === I18n.tr("Top Center", "screen position option")) { break;
case I18n.tr("Top Center", "screen position option"):
SettingsData.set("notificationPopupPosition", -1); SettingsData.set("notificationPopupPosition", -1);
} else if (value === I18n.tr("Bottom Right", "screen position option")) { break;
case I18n.tr("Bottom Center", "screen position option"):
SettingsData.set("notificationPopupPosition", SettingsData.Position.BottomCenter);
break;
case I18n.tr("Bottom Right", "screen position option"):
SettingsData.set("notificationPopupPosition", SettingsData.Position.Right); SettingsData.set("notificationPopupPosition", SettingsData.Position.Right);
} else if (value === I18n.tr("Bottom Left", "screen position option")) { break;
case I18n.tr("Bottom Left", "screen position option"):
SettingsData.set("notificationPopupPosition", SettingsData.Position.Bottom); SettingsData.set("notificationPopupPosition", SettingsData.Position.Bottom);
break;
} }
SettingsData.sendTestNotifications(); SettingsData.sendTestNotifications();
} }