notifcenter: collapsible app groups, hover-only group dismiss, full opacity on notif hover
This commit is contained in:
parent
a502faef19
commit
862169aba0
2 changed files with 110 additions and 22 deletions
|
|
@ -28,7 +28,7 @@ Item {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: _hover.hovered ? M.Theme.base02 : M.Theme.base01
|
||||
opacity: Math.max(M.Theme.barOpacity, 0.9)
|
||||
opacity: _hover.hovered ? 1.0 : Math.max(M.Theme.barOpacity, 0.9)
|
||||
radius: M.Theme.radius
|
||||
|
||||
Behavior on color {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,18 @@ M.HoverPanel {
|
|||
|
||||
property var _pendingDismissIds: []
|
||||
|
||||
// Collapsed groups set — reassign to trigger reactivity
|
||||
property var _collapsedGroups: ({})
|
||||
|
||||
function _toggleCollapse(appName) {
|
||||
const next = Object.assign({}, _collapsedGroups);
|
||||
if (next[appName])
|
||||
delete next[appName];
|
||||
else
|
||||
next[appName] = true;
|
||||
_collapsedGroups = next;
|
||||
}
|
||||
|
||||
// Group notifications by appName, sorted by max urgency desc then most recent time desc
|
||||
readonly property var _groups: {
|
||||
const map = {};
|
||||
|
|
@ -93,21 +105,26 @@ M.HoverPanel {
|
|||
});
|
||||
}
|
||||
|
||||
// Flat model: group header followed by its notifications
|
||||
// Flat model: group header followed by its notifications (omitted when collapsed)
|
||||
readonly property var _flatModel: {
|
||||
const arr = [];
|
||||
for (const g of _groups) {
|
||||
const collapsed = !!_collapsedGroups[g.appName];
|
||||
arr.push({
|
||||
type: "header",
|
||||
appName: g.appName,
|
||||
appIcon: g.appIcon,
|
||||
count: g.notifs.length
|
||||
count: g.notifs.length,
|
||||
collapsed: collapsed,
|
||||
summaries: g.notifs.map(n => n.summary || "").filter(Boolean).join(" · ")
|
||||
});
|
||||
for (const n of g.notifs)
|
||||
arr.push({
|
||||
type: "notif",
|
||||
data: n
|
||||
});
|
||||
if (!collapsed) {
|
||||
for (const n of g.notifs)
|
||||
arr.push({
|
||||
type: "notif",
|
||||
data: n
|
||||
});
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
|
@ -218,11 +235,25 @@ M.HoverPanel {
|
|||
readonly property var _notif: _type === "notif" ? modelData.data : null
|
||||
|
||||
width: menuWindow.contentWidth
|
||||
height: _targetHeight * _heightScale
|
||||
height: _displayTargetHeight * _heightScale
|
||||
clip: true
|
||||
opacity: 0
|
||||
|
||||
readonly property real _targetHeight: _type === "header" ? 28 : _notifCard.implicitHeight
|
||||
readonly property real _targetHeight: {
|
||||
if (_type === "header")
|
||||
return modelData.collapsed ? (28 + M.Theme.fontSize + 4) : 28;
|
||||
return _notifCard.implicitHeight;
|
||||
}
|
||||
|
||||
// Animated version of _targetHeight — smoothly transitions header height on collapse
|
||||
property real _displayTargetHeight: _targetHeight
|
||||
Behavior on _displayTargetHeight {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
property real _heightScale: 1
|
||||
property bool _skipDismiss: false
|
||||
|
||||
|
|
@ -257,11 +288,29 @@ M.HoverPanel {
|
|||
visible: notifDelegate._type === "header"
|
||||
anchors.fill: parent
|
||||
|
||||
HoverHandler {
|
||||
id: _headerHover
|
||||
}
|
||||
|
||||
// Tap target for collapse — covers header row only, excludes dismiss button
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: _groupDismissBtn.left
|
||||
anchors.top: parent.top
|
||||
height: 28
|
||||
|
||||
TapHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onTapped: menuWindow._toggleCollapse(notifDelegate.modelData.appName)
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: _headerIcon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: (28 - height) / 2
|
||||
width: M.Theme.fontSize + 2
|
||||
height: M.Theme.fontSize + 2
|
||||
source: {
|
||||
|
|
@ -278,12 +327,29 @@ M.HoverPanel {
|
|||
asynchronous: true
|
||||
}
|
||||
|
||||
// Collapse chevron
|
||||
Text {
|
||||
id: _chevron
|
||||
anchors.right: _groupDismissBtn.left
|
||||
anchors.rightMargin: 8
|
||||
anchors.top: parent.top
|
||||
height: 28
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: notifDelegate._type === "header" && notifDelegate.modelData.collapsed ? "\u25B8" : "\u25BE"
|
||||
color: M.Theme.base04
|
||||
font.pixelSize: M.Theme.fontSize - 2
|
||||
font.family: M.Theme.fontFamily
|
||||
}
|
||||
|
||||
// App name
|
||||
Text {
|
||||
anchors.left: _headerIcon.visible ? _headerIcon.right : parent.left
|
||||
anchors.leftMargin: _headerIcon.visible ? 6 : 10
|
||||
anchors.right: _groupDismissBtn.left
|
||||
anchors.rightMargin: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: _chevron.left
|
||||
anchors.rightMargin: 4
|
||||
anchors.top: parent.top
|
||||
height: 28
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: notifDelegate._type === "header" ? (notifDelegate.modelData.appName || "Unknown") : ""
|
||||
color: M.Theme.base05
|
||||
font.pixelSize: M.Theme.fontSize - 1
|
||||
|
|
@ -292,28 +358,50 @@ M.HoverPanel {
|
|||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Dismiss button — opacity-hidden when header not hovered
|
||||
Text {
|
||||
id: _groupDismissBtn
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.top: parent.top
|
||||
height: 28
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: "\uF1F8"
|
||||
color: _groupDismissArea.containsMouse ? M.Theme.base08 : M.Theme.base04
|
||||
color: _groupDismissHover.hovered ? M.Theme.base08 : M.Theme.base04
|
||||
font.pixelSize: M.Theme.fontSize - 1
|
||||
font.family: M.Theme.iconFontFamily
|
||||
opacity: _headerHover.hovered ? 1 : 0
|
||||
|
||||
MouseArea {
|
||||
id: _groupDismissArea
|
||||
anchors.fill: parent
|
||||
anchors.margins: -4
|
||||
hoverEnabled: true
|
||||
HoverHandler {
|
||||
id: _groupDismissHover
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
onTapped: {
|
||||
if (notifDelegate._type === "header")
|
||||
menuWindow._cascadeGroupDismiss(notifDelegate.modelData.appName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collapsed preview: notification summaries on a subtitle row
|
||||
Text {
|
||||
visible: notifDelegate._type === "header" && notifDelegate.modelData.collapsed
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 28
|
||||
height: M.Theme.fontSize + 4
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: notifDelegate._type === "header" ? (notifDelegate.modelData.summaries ?? "") : ""
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: M.Theme.fontSize - 2
|
||||
font.family: M.Theme.fontFamily
|
||||
color: M.Theme.base04
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Individual notification ----
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue