Files
noctalia-shell/Modules/Toast/ToastScreen.qml
T

215 lines
6.2 KiB
QML

import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
Item {
id: root
required property ShellScreen screen
required property real scaling
property bool active: false
// Local queue for this screen only
property var messageQueue: []
property bool isShowingToast: false
// If true, immediately show new toasts
property bool replaceOnNew: true
Connections {
target: ScalingService
function onScaleChanged(screenName, scale) {
if (screenName === root.screen.name) {
root.scaling = scale
}
}
}
Connections {
target: ToastService
enabled: root.active
function onNotify(message, description, type, duration) {
root.enqueueToast({
"message": message,
"description": description,
"type": type,
"duration": duration,
"timestamp": Date.now()
})
}
}
// Clear queue on component destruction to prevent orphaned toasts
Component.onDestruction: {
messageQueue = []
isShowingToast = false
hideTimer.stop()
quickSwitchTimer.stop()
}
function enqueueToast(toastData) {
Logger.log("ToastScreen", "Queuing", toastData.type, ":", toastData.message, toastData.description.substr(100).replace(/\n/g, ""))
if (replaceOnNew && isShowingToast) {
// Cancel current toast and clear queue for latest toast
messageQueue = [] // Clear existing queue
messageQueue.push(toastData)
// Hide current toast immediately
if (windowLoader.item) {
hideTimer.stop()
windowLoader.item.hideToast()
}
// Process new toast after a brief delay
isShowingToast = false
quickSwitchTimer.restart()
} else {
// Original behavior - queue the toast
messageQueue.push(toastData)
processQueue()
}
}
Timer {
id: quickSwitchTimer
interval: 50 // Brief delay for smooth transition
onTriggered: root.processQueue()
}
function processQueue() {
if (!active || messageQueue.length === 0 || isShowingToast) {
return
}
var data = messageQueue.shift()
isShowingToast = true
// Activate the loader and show toast
windowLoader.active = true
// Need a small delay to ensure the window is created
Qt.callLater(() => {
if (windowLoader.item) {
windowLoader.item.showToast(data.message, data.description, data.type, data.duration)
}
})
}
function onToastHidden() {
isShowingToast = false
// Deactivate the loader to completely remove the window
windowLoader.active = false
// Small delay before processing next toast
hideTimer.restart()
}
Timer {
id: hideTimer
interval: 200
onTriggered: root.processQueue()
}
// The loader that creates/destroys the PanelWindow as needed
Loader {
id: windowLoader
active: false // Only active when showing a toast
sourceComponent: PanelWindow {
id: panel
property alias toastItem: toastItem
screen: root.screen
readonly property string location: (Settings.data.notifications && Settings.data.notifications.location) ? Settings.data.notifications.location : "top_right"
readonly property bool isTop: (location === "top") || (location.length >= 3 && location.substring(0, 3) === "top")
readonly property bool isBottom: (location === "bottom") || (location.length >= 6 && location.substring(0, 6) === "bottom")
readonly property bool isLeft: location.indexOf("_left") >= 0
readonly property bool isRight: location.indexOf("_right") >= 0
readonly property bool isCentered: (location === "top" || location === "bottom")
// Anchor selection based on location (window edges)
anchors.top: isTop
anchors.bottom: isBottom
anchors.left: isLeft
anchors.right: isRight
// Margins depending on bar position and chosen location
margins.top: {
if (!(anchors.top))
return 0
var base = Style.marginM * scaling
if (Settings.data.bar.position === "top") {
var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
return (Style.barHeight * scaling) + base + floatExtraV
}
return base
}
margins.bottom: {
if (!(anchors.bottom))
return 0
var base = Style.marginM * scaling
if (Settings.data.bar.position === "bottom") {
var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
return (Style.barHeight * scaling) + base + floatExtraV
}
return base
}
margins.left: {
if (!(anchors.left))
return 0
var base = Style.marginM * scaling
if (Settings.data.bar.position === "left") {
var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
return (Style.barHeight * scaling) + base + floatExtraH
}
return base
}
margins.right: {
if (!(anchors.right))
return 0
var base = Style.marginM * scaling
if (Settings.data.bar.position === "right") {
var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
return (Style.barHeight * scaling) + base + floatExtraH
}
return base
}
implicitWidth: 420 * root.scaling
implicitHeight: toastItem.height
color: Color.transparent
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: PanelWindow.ExclusionMode.Ignore
function showToast(message, description, type, duration) {
toastItem.show(message, description, type, duration)
}
function hideToast() {
toastItem.hideImmediately()
}
SimpleToast {
id: toastItem
anchors.horizontalCenter: parent.horizontalCenter
onHidden: root.onToastHidden()
}
}
}
}