fedora
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
|
||||
// Local queue for this screen only (bounded to prevent memory issues)
|
||||
property var messageQueue: []
|
||||
property int maxQueueSize: 10
|
||||
property bool isShowingToast: false
|
||||
|
||||
// If true, immediately show new toasts
|
||||
property bool replaceOnNew: true
|
||||
|
||||
Connections {
|
||||
target: ToastService
|
||||
|
||||
function onNotify(title, description, icon, type, duration, actionLabel, actionCallback) {
|
||||
root.enqueueToast({
|
||||
"title": title,
|
||||
"description": description,
|
||||
"icon": icon,
|
||||
"type": type,
|
||||
"duration": duration,
|
||||
"actionLabel": actionLabel || "",
|
||||
"actionCallback": actionCallback || null,
|
||||
"timestamp": Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
function onDismiss() {
|
||||
root.dismissToast();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear queue on component destruction to prevent orphaned toasts
|
||||
Component.onDestruction: {
|
||||
messageQueue = [];
|
||||
isShowingToast = false;
|
||||
hideTimer.stop();
|
||||
quickSwitchTimer.stop();
|
||||
}
|
||||
|
||||
function dismissToast() {
|
||||
if (windowLoader.item) {
|
||||
hideTimer.stop();
|
||||
windowLoader.item.hideToast();
|
||||
}
|
||||
}
|
||||
|
||||
function enqueueToast(toastData) {
|
||||
// Safe logging - fix the substring bug
|
||||
var descPreview = (toastData.description || "").substring(0, 100).replace(/\n/g, " ");
|
||||
Logger.d("ToastScreen", "Queuing", toastData.type, ":", toastData.title, descPreview);
|
||||
|
||||
// Bounded queue to prevent unbounded memory growth
|
||||
if (messageQueue.length >= maxQueueSize) {
|
||||
Logger.d("ToastScreen", "Queue full, dropping oldest toast");
|
||||
messageQueue.shift();
|
||||
}
|
||||
|
||||
if (replaceOnNew && isShowingToast) {
|
||||
// Cancel current toast and clear queue for latest toast
|
||||
messageQueue = []; // Clear existing queue
|
||||
messageQueue.push(toastData);
|
||||
|
||||
// Hide current toast immediately
|
||||
root.dismissToast();
|
||||
|
||||
// Process new toast after a brief delay
|
||||
isShowingToast = false;
|
||||
quickSwitchTimer.restart();
|
||||
} else {
|
||||
// Queue the toast
|
||||
messageQueue.push(toastData);
|
||||
processQueue();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: quickSwitchTimer
|
||||
interval: 50 // Brief delay for smooth transition
|
||||
onTriggered: root.processQueue()
|
||||
}
|
||||
|
||||
function processQueue() {
|
||||
if (messageQueue.length === 0 || isShowingToast) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = messageQueue.shift();
|
||||
isShowingToast = true;
|
||||
|
||||
// Store the toast data for when loader is ready
|
||||
windowLoader.pendingToast = data;
|
||||
|
||||
// Activate the loader - onStatusChanged will handle showing the toast
|
||||
windowLoader.active = true;
|
||||
}
|
||||
|
||||
function onToastHidden() {
|
||||
isShowingToast = false;
|
||||
|
||||
// Deactivate the loader to completely remove the window and free memory
|
||||
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
|
||||
// This is good for RAM efficiency when toasts are infrequent
|
||||
Loader {
|
||||
id: windowLoader
|
||||
active: false // Only active when showing a toast
|
||||
|
||||
// Store pending toast data
|
||||
property var pendingToast: null
|
||||
|
||||
onStatusChanged: {
|
||||
// When loader becomes ready, show the pending toast
|
||||
if (status === Loader.Ready && pendingToast !== null) {
|
||||
item.showToast(pendingToast.title, pendingToast.description, pendingToast.icon, pendingToast.type, pendingToast.duration, pendingToast.actionLabel, pendingToast.actionCallback);
|
||||
pendingToast = null;
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: panel
|
||||
|
||||
property alias toastItem: toastItem
|
||||
|
||||
screen: root.screen
|
||||
|
||||
// Parse location setting
|
||||
readonly property string location: Settings.data.notifications?.location || "top_right"
|
||||
readonly property bool isTop: location.startsWith("top")
|
||||
readonly property bool isBottom: location.startsWith("bottom")
|
||||
readonly property bool isLeft: location.endsWith("_left")
|
||||
readonly property bool isRight: location.endsWith("_right")
|
||||
readonly property bool isCentered: location === "top" || location === "bottom"
|
||||
|
||||
readonly property bool isFramed: Settings.data.bar.barType === "framed"
|
||||
readonly property real frameThickness: Settings.data.bar.frameThickness ?? 8
|
||||
|
||||
readonly property string barPos: Settings.getBarPositionForScreen(panel.screen?.name)
|
||||
readonly property bool isFloating: Settings.data.bar.barType === "floating"
|
||||
readonly property real barHeight: Style.getBarHeightForScreen(panel.screen?.name)
|
||||
|
||||
// Calculate bar and frame offsets for each edge separately
|
||||
readonly property int barOffsetTop: {
|
||||
if (barPos !== "top")
|
||||
return isFramed ? frameThickness : 0;
|
||||
const floatMarginV = isFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0;
|
||||
return barHeight + floatMarginV;
|
||||
}
|
||||
|
||||
readonly property int barOffsetBottom: {
|
||||
if (barPos !== "bottom")
|
||||
return isFramed ? frameThickness : 0;
|
||||
const floatMarginV = isFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0;
|
||||
return barHeight + floatMarginV;
|
||||
}
|
||||
|
||||
readonly property int barOffsetLeft: {
|
||||
if (barPos !== "left")
|
||||
return isFramed ? frameThickness : 0;
|
||||
const floatMarginH = isFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0;
|
||||
return barHeight + floatMarginH;
|
||||
}
|
||||
|
||||
readonly property int barOffsetRight: {
|
||||
if (barPos !== "right")
|
||||
return isFramed ? frameThickness : 0;
|
||||
const floatMarginH = isFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0;
|
||||
return barHeight + floatMarginH;
|
||||
}
|
||||
|
||||
readonly property int shadowPadding: Style.shadowBlurMax + Style.marginL
|
||||
|
||||
// Anchoring
|
||||
anchors.top: isTop
|
||||
anchors.bottom: isBottom
|
||||
anchors.left: isLeft
|
||||
anchors.right: isRight
|
||||
|
||||
// Margins for PanelWindow - only apply bar offset for the specific edge where the bar is
|
||||
margins.top: isTop ? barOffsetTop - shadowPadding + Style.marginM : 0
|
||||
margins.bottom: isBottom ? barOffsetBottom - shadowPadding + Style.marginM : 0
|
||||
margins.left: isLeft ? barOffsetLeft - shadowPadding + Style.marginM : 0
|
||||
margins.right: isRight ? barOffsetRight - shadowPadding + Style.marginM : 0
|
||||
|
||||
implicitWidth: Math.round(toastItem.width)
|
||||
implicitHeight: Math.round(toastItem.height)
|
||||
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.layer: (Settings.data.notifications && Settings.data.notifications.overlayLayer) ? WlrLayer.Overlay : WlrLayer.Top
|
||||
WlrLayershell.namespace: "noctalia-toast-" + (screen?.name || "unknown")
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
// Make shadow area click-through, only toast content is clickable
|
||||
mask: Region {
|
||||
x: 0
|
||||
y: 0
|
||||
width: panel.width
|
||||
height: panel.height
|
||||
intersection: Intersection.Xor
|
||||
|
||||
Region {
|
||||
// The clickable content area is inset by shadowPadding from all edges
|
||||
x: panel.shadowPadding
|
||||
y: panel.shadowPadding
|
||||
width: Math.max(0, panel.width - panel.shadowPadding * 2)
|
||||
height: Math.max(0, panel.height - panel.shadowPadding * 2)
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(title, description, icon, type, duration, actionLabel, actionCallback) {
|
||||
toastItem.show(title, description, icon, type, duration, actionLabel, actionCallback);
|
||||
}
|
||||
|
||||
function hideToast() {
|
||||
toastItem.hideImmediately();
|
||||
}
|
||||
|
||||
Toast {
|
||||
id: toastItem
|
||||
onHidden: root.onToastHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user