quickshell: gnome-style calendar popup with weather, media controls, notifications
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
4d52da994c
commit
bb25df0b03
1 changed files with 476 additions and 197 deletions
|
|
@ -43,6 +43,16 @@ in
|
|||
nmcli = "${pkgs.networkmanager}/bin/nmcli";
|
||||
# Follow stylix's monospace choice so a font swap propagates to the bar
|
||||
monoFont = osConfig.stylix.fonts.monospace.name;
|
||||
# 7-day forecast JSON from Open-Meteo (no API key). Location is
|
||||
# auto-detected by IP via ipinfo.io, falling back to London.
|
||||
weatherFetchScript = pkgs.writeShellScript "weather-fetch" ''
|
||||
loc=$(${pkgs.curl}/bin/curl -sf --max-time 5 https://ipinfo.io/loc 2>/dev/null || true)
|
||||
case "$loc" in
|
||||
*,*) lat=''${loc%,*}; lon=''${loc#*,} ;;
|
||||
*) lat=51.51; lon=-0.13 ;;
|
||||
esac
|
||||
${pkgs.curl}/bin/curl -sf --max-time 10 "https://api.open-meteo.com/v1/forecast?latitude=$lat&longitude=$lon&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=auto&forecast_days=7"
|
||||
'';
|
||||
in {
|
||||
"quickshell/qmldir" = {
|
||||
onChange = qsRestart;
|
||||
|
|
@ -91,6 +101,7 @@ in
|
|||
readonly property string notifSound: "${pkgs.libcanberra-gtk3}/bin/canberra-gtk-play"
|
||||
readonly property string hyprlock: "${pkgs.hyprlock}/bin/hyprlock"
|
||||
readonly property string systemctl: "${pkgs.systemd}/bin/systemctl"
|
||||
readonly property string weatherFetch: "${weatherFetchScript}"
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
|
@ -378,6 +389,7 @@ in
|
|||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Services.UPower
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
|
@ -547,10 +559,10 @@ in
|
|||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: bar.toggleDropdown(calPopup)
|
||||
onClicked: bar.toggleDropdown(calPopup, function() { calPopup.resetView(); })
|
||||
onEntered: {
|
||||
if (bar.activeDropdown) {
|
||||
if (bar.activeDropdown !== calPopup) bar.toggleDropdown(calPopup);
|
||||
if (bar.activeDropdown !== calPopup) bar.toggleDropdown(calPopup, function() { calPopup.resetView(); });
|
||||
else bar.activeDropdown.resetAutoClose();
|
||||
}
|
||||
}
|
||||
|
|
@ -1696,240 +1708,507 @@ in
|
|||
}
|
||||
''}
|
||||
|
||||
// Calendar popup
|
||||
// Calendar popup — GNOME-style two-pane panel.
|
||||
// Left: navigable month calendar + 7-day weather strip.
|
||||
// Right: MPRIS media controls + notification list.
|
||||
BarDropdown {
|
||||
id: calPopup
|
||||
dropdownX: bar.width / 2
|
||||
fullWidth: calCol.width + 8
|
||||
fullHeight: calCol.height + 4
|
||||
fullWidth: calRow.width + 24
|
||||
fullHeight: calRow.height + 24
|
||||
autoCloseMs: 3000
|
||||
|
||||
Column {
|
||||
id: calCol
|
||||
// Month being viewed; reset to today when the popup opens
|
||||
// (via the setup function passed to bar.toggleDropdown).
|
||||
property int viewYear: clockText.now.getFullYear()
|
||||
property int viewMonth: clockText.now.getMonth()
|
||||
function resetView() {
|
||||
viewYear = clockText.now.getFullYear();
|
||||
viewMonth = clockText.now.getMonth();
|
||||
}
|
||||
function shiftMonth(d) {
|
||||
let m = viewMonth + d;
|
||||
if (m < 0) { viewMonth = 11; viewYear--; }
|
||||
else if (m > 11) { viewMonth = 0; viewYear++; }
|
||||
else viewMonth = m;
|
||||
}
|
||||
|
||||
// --- Weather: 7-day forecast, refreshed every 30 min ---
|
||||
property var weatherDays: []
|
||||
Process {
|
||||
id: weatherProc
|
||||
command: [Commands.weatherFetch]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
let j = JSON.parse(text);
|
||||
let out = [];
|
||||
for (let i = 0; i < j.daily.time.length && i < 7; i++) {
|
||||
out.push({
|
||||
day: new Date(j.daily.time[i] + "T12:00:00").toLocaleDateString(Qt.locale(), "ddd").slice(0, 2),
|
||||
code: j.daily.weather_code[i],
|
||||
max: Math.round(j.daily.temperature_2m_max[i]),
|
||||
min: Math.round(j.daily.temperature_2m_min[i])
|
||||
});
|
||||
}
|
||||
if (out.length > 0) calPopup.weatherDays = out;
|
||||
} catch (e) { /* keep previous forecast */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
interval: 1800000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: weatherProc.running = true
|
||||
}
|
||||
function weatherGlyph(code) {
|
||||
if (code === 0) return "\u{f0599}"; // sunny
|
||||
if (code <= 2) return "\u{f0595}"; // partly cloudy
|
||||
if (code === 3) return "\u{f0590}"; // overcast
|
||||
if (code <= 48) return "\u{f0591}"; // fog
|
||||
if (code <= 57) return "\u{f0597}"; // drizzle
|
||||
if (code <= 67) return "\u{f0596}"; // rain
|
||||
if (code <= 77) return "\u{f0598}"; // snow
|
||||
if (code <= 82) return "\u{f0597}"; // showers
|
||||
if (code <= 86) return "\u{f0598}"; // snow showers
|
||||
return "\u{f0593}"; // thunder
|
||||
}
|
||||
|
||||
// --- Media: prefer the actively playing MPRIS player ---
|
||||
property var player: {
|
||||
let ps = Mpris.players.values;
|
||||
for (let i = 0; i < ps.length; i++) {
|
||||
if (ps[i].playbackState === MprisPlaybackState.Playing) return ps[i];
|
||||
}
|
||||
return ps.length > 0 ? ps[0] : null;
|
||||
}
|
||||
|
||||
Row {
|
||||
id: calRow
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
|
||||
spacing: 8
|
||||
anchors.topMargin: 12
|
||||
spacing: 16
|
||||
opacity: calPopup.open ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
Text {
|
||||
id: calTitle
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: clockText.now.toLocaleDateString(Qt.locale(), "dddd, d MMMM yyyy")
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
// ── Left pane: calendar + weather ──
|
||||
Column {
|
||||
id: calLeftCol
|
||||
width: 7 * 32
|
||||
spacing: 8
|
||||
|
||||
Row {
|
||||
id: weekdayRow
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
||||
Text {
|
||||
required property var modelData
|
||||
width: 32
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: modelData
|
||||
color: Theme.base04
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
}
|
||||
// Month header: ‹ [Month Year] › — label click jumps to today
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 28
|
||||
|
||||
Grid {
|
||||
columns: 7
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
id: calRepeater
|
||||
model: 42
|
||||
|
||||
Rectangle {
|
||||
required property int index
|
||||
width: 32
|
||||
height: 26
|
||||
radius: 4
|
||||
color: {
|
||||
let d = clockText.now;
|
||||
let first = new Date(d.getFullYear(), d.getMonth(), 1);
|
||||
let startDay = (first.getDay() + 6) % 7;
|
||||
let dayNum = index - startDay + 1;
|
||||
let daysInMonth = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
|
||||
return (dayNum === d.getDate() && dayNum >= 1 && dayNum <= daysInMonth)
|
||||
? Theme.base03 : "transparent";
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
let d = clockText.now;
|
||||
let first = new Date(d.getFullYear(), d.getMonth(), 1);
|
||||
let startDay = (first.getDay() + 6) % 7;
|
||||
let dayNum = parent.index - startDay + 1;
|
||||
let daysInMonth = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
|
||||
return (dayNum >= 1 && dayNum <= daysInMonth) ? dayNum.toString() : "";
|
||||
}
|
||||
color: {
|
||||
let d = clockText.now;
|
||||
let first = new Date(d.getFullYear(), d.getMonth(), 1);
|
||||
let startDay = (first.getDay() + 6) % 7;
|
||||
let dayNum = parent.index - startDay + 1;
|
||||
return (dayNum === d.getDate()) ? Theme.base05 : Theme.base04;
|
||||
}
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 7 * 32 + 8
|
||||
height: 1
|
||||
color: Theme.base02
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
width: 7 * 32 + 8
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Rectangle {
|
||||
width: 28; height: 28; radius: 6
|
||||
anchors.left: parent.left
|
||||
color: calPrevMa.containsMouse ? Theme.base02 : "transparent"
|
||||
Text {
|
||||
text: "Notifications"
|
||||
anchors.centerIn: parent
|
||||
text: "\u{f0141}"
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
font.pixelSize: 16
|
||||
}
|
||||
Item { Layout.fillWidth: true; width: 10 }
|
||||
Text {
|
||||
anchors.right: parent.right
|
||||
text: bar.notifServer.trackedNotifications.values.length > 0 ? "Clear all" : ""
|
||||
color: Theme.base04
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 11
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
let notifs = bar.notifServer.trackedNotifications.values;
|
||||
for (let i = notifs.length - 1; i >= 0; i--) {
|
||||
notifs[i].dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: calPrevMa
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: calPopup.shiftMonth(-1)
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 4
|
||||
width: 7 * 32 + 8
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Text {
|
||||
visible: bar.notifServer.trackedNotifications.values.length === 0
|
||||
text: "No notifications"
|
||||
color: Theme.base03
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 11
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: new Date(calPopup.viewYear, calPopup.viewMonth, 1).toLocaleDateString(Qt.locale(), "MMMM yyyy")
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
font.weight: Font.Medium
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: calPopup.resetView()
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: bar.notifServer.trackedNotifications
|
||||
Rectangle {
|
||||
width: 28; height: 28; radius: 6
|
||||
anchors.right: parent.right
|
||||
color: calNextMa.containsMouse ? Theme.base02 : "transparent"
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "\u{f0142}"
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16
|
||||
}
|
||||
MouseArea {
|
||||
id: calNextMa
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: calPopup.shiftMonth(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: notifItem
|
||||
required property var modelData
|
||||
width: 7 * 32 + 8
|
||||
height: notifCol.height + 12
|
||||
radius: 6
|
||||
color: Theme.base01
|
||||
Row {
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
||||
Text {
|
||||
required property var modelData
|
||||
width: 32
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: modelData
|
||||
color: Theme.base04
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: notifCol
|
||||
anchors.left: parent.left
|
||||
anchors.right: dismissBtn.left
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 6
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: notifItem.modelData.summary || notifItem.modelData.appName
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 11
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
Grid {
|
||||
columns: 7
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: 42
|
||||
Rectangle {
|
||||
required property int index
|
||||
property int dayNum: {
|
||||
let first = new Date(calPopup.viewYear, calPopup.viewMonth, 1);
|
||||
let startDay = (first.getDay() + 6) % 7;
|
||||
return index - startDay + 1;
|
||||
}
|
||||
property int daysInMonth: new Date(calPopup.viewYear, calPopup.viewMonth + 1, 0).getDate()
|
||||
property bool isToday: dayNum === clockText.now.getDate()
|
||||
&& calPopup.viewMonth === clockText.now.getMonth()
|
||||
&& calPopup.viewYear === clockText.now.getFullYear()
|
||||
width: 32
|
||||
height: 26
|
||||
radius: 4
|
||||
color: isToday ? Theme.base03 : "transparent"
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: notifItem.modelData.body || ""
|
||||
anchors.centerIn: parent
|
||||
text: parent.dayNum >= 1 && parent.dayNum <= parent.daysInMonth ? parent.dayNum.toString() : ""
|
||||
color: parent.isToday ? Theme.base05 : Theme.base04
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.base02
|
||||
}
|
||||
|
||||
// 7-day weather strip
|
||||
Row {
|
||||
width: parent.width
|
||||
visible: calPopup.weatherDays.length > 0
|
||||
Repeater {
|
||||
model: calPopup.weatherDays
|
||||
Column {
|
||||
required property var modelData
|
||||
width: 32
|
||||
spacing: 2
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: modelData.day
|
||||
color: Theme.base04
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.Wrap
|
||||
visible: text !== ""
|
||||
}
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: calPopup.weatherGlyph(modelData.code)
|
||||
color: Theme.base0C
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 14
|
||||
}
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: modelData.max + "°"
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
}
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: modelData.min + "°"
|
||||
color: Theme.base03
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 4
|
||||
visible: notifItem.modelData.actions.length > 0
|
||||
Repeater {
|
||||
model: notifItem.modelData.actions
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
width: actionText.width + 12
|
||||
height: actionText.height + 4
|
||||
radius: 4
|
||||
color: actionMa.containsMouse ? Theme.base02 : Theme.base01
|
||||
border.width: 1
|
||||
border.color: Theme.base02
|
||||
Text {
|
||||
id: actionText
|
||||
anchors.centerIn: parent
|
||||
text: modelData.text
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
}
|
||||
MouseArea {
|
||||
id: actionMa
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: modelData.invoke()
|
||||
// Vertical separator between panes
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: Math.max(calLeftCol.height, calRightCol.height)
|
||||
color: Theme.base02
|
||||
}
|
||||
|
||||
// ── Right pane: media + notifications ──
|
||||
Column {
|
||||
id: calRightCol
|
||||
width: 300
|
||||
spacing: 8
|
||||
|
||||
// Media player card
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 64
|
||||
radius: 8
|
||||
color: Theme.base01
|
||||
visible: calPopup.player !== null
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 10
|
||||
|
||||
Rectangle {
|
||||
width: 48; height: 48
|
||||
radius: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Theme.base02
|
||||
clip: true
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: albumArt.status !== Image.Ready
|
||||
text: "\u{f0387}"
|
||||
color: Theme.base04
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 20
|
||||
}
|
||||
Image {
|
||||
id: albumArt
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
source: calPopup.player ? calPopup.player.trackArtUrl : ""
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - 48 - 10 - 88 - 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
Text {
|
||||
width: parent.width
|
||||
text: calPopup.player ? calPopup.player.trackTitle : ""
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: calPopup.player ? calPopup.player.trackArtist : ""
|
||||
color: Theme.base04
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
Repeater {
|
||||
model: [
|
||||
{ glyph: "\u{f04ae}", act: "prev" },
|
||||
{ glyph: calPopup.player && calPopup.player.playbackState === MprisPlaybackState.Playing ? "\u{f03e4}" : "\u{f040a}", act: "toggle" },
|
||||
{ glyph: "\u{f04ad}", act: "next" }
|
||||
]
|
||||
Rectangle {
|
||||
id: mediaBtn
|
||||
required property var modelData
|
||||
width: 28; height: 28; radius: 14
|
||||
color: mediaBtnMa.containsMouse ? Theme.base02 : "transparent"
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: mediaBtn.modelData.glyph
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16
|
||||
}
|
||||
MouseArea {
|
||||
id: mediaBtnMa
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
let p = calPopup.player;
|
||||
if (!p) return;
|
||||
if (mediaBtn.modelData.act === "prev") p.previous();
|
||||
else if (mediaBtn.modelData.act === "next") p.next();
|
||||
else p.togglePlaying();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: dismissBtn
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 6
|
||||
text: "\u{f0156}"
|
||||
color: dismissMa.containsMouse ? Theme.base05 : Theme.base03
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12
|
||||
MouseArea {
|
||||
id: dismissMa
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: notifItem.modelData.dismiss()
|
||||
// Notifications header
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 20
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Notifications"
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: bar.notifServer.trackedNotifications.values.length > 0 ? "Clear all" : ""
|
||||
color: Theme.base04
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 11
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
let notifs = bar.notifServer.trackedNotifications.values;
|
||||
for (let i = notifs.length - 1; i >= 0; i--) {
|
||||
notifs[i].dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 4
|
||||
width: parent.width
|
||||
|
||||
Text {
|
||||
visible: bar.notifServer.trackedNotifications.values.length === 0
|
||||
text: "No notifications"
|
||||
color: Theme.base03
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 11
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: bar.notifServer.trackedNotifications
|
||||
|
||||
Rectangle {
|
||||
id: notifItem
|
||||
required property var modelData
|
||||
width: calRightCol.width
|
||||
height: notifCol.height + 12
|
||||
radius: 6
|
||||
color: Theme.base01
|
||||
|
||||
Column {
|
||||
id: notifCol
|
||||
anchors.left: parent.left
|
||||
anchors.right: dismissBtn.left
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 6
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: notifItem.modelData.summary || notifItem.modelData.appName
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 11
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: notifItem.modelData.body || ""
|
||||
color: Theme.base04
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.Wrap
|
||||
visible: text !== ""
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 4
|
||||
visible: notifItem.modelData.actions.length > 0
|
||||
Repeater {
|
||||
model: notifItem.modelData.actions
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
width: actionText.width + 12
|
||||
height: actionText.height + 4
|
||||
radius: 4
|
||||
color: actionMa.containsMouse ? Theme.base02 : Theme.base01
|
||||
border.width: 1
|
||||
border.color: Theme.base02
|
||||
Text {
|
||||
id: actionText
|
||||
anchors.centerIn: parent
|
||||
text: modelData.text
|
||||
color: Theme.base05
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 10
|
||||
}
|
||||
MouseArea {
|
||||
id: actionMa
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: modelData.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: dismissBtn
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 6
|
||||
text: "\u{f0156}"
|
||||
color: dismissMa.containsMouse ? Theme.base05 : Theme.base03
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12
|
||||
MouseArea {
|
||||
id: dismissMa
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: notifItem.modelData.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue