From 084e69b644d17aab1e4cb61109c3f942331a8fb1 Mon Sep 17 00:00:00 2001 From: Dal Rupnik Date: Wed, 13 May 2026 15:41:19 +0200 Subject: [PATCH] fix: float drop toast above the VM window, not inside it The notification banner used to sit inside the VM window's top-right corner, overlapping guest content. Move it outside the window: the toast's bottom edge now sits 10 pt above the VM window's top edge, still right-aligned. The slide-in animation is unchanged. If the VM window is jammed against the top of the screen and there's no room above for the toast, fall back to the old "inside top-right" position so we never clip the menu bar. --- Sources/tart/DropProgressToast.swift | 39 ++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Sources/tart/DropProgressToast.swift b/Sources/tart/DropProgressToast.swift index 5eefbf9..c5424b4 100644 --- a/Sources/tart/DropProgressToast.swift +++ b/Sources/tart/DropProgressToast.swift @@ -270,22 +270,39 @@ final class DropProgressToast { self.cancelButton = btn } - /// Position the panel in the VM window's top-right corner with a small - /// inset. When `animated` is true (first appearance of a drop session), - /// the panel starts off-screen-right and slides into place over ~0.18 s, - /// mimicking macOS notification banners. + /// Position the panel just *above* the VM window's top edge, right-aligned + /// to the window's right edge — i.e. the toast floats outside the VM + /// frame, like a macOS notification banner perched on top of the app. + /// When `animated` is true (first appearance of a drop session) the panel + /// starts off-screen-right and slides into place over ~0.18 s. + /// + /// If the VM window is jammed against the top of the screen and there + /// isn't room for the toast above it, we fall back to sitting inside the + /// window's top-right corner so the toast never clips the menu bar. private func positionPanel(animated: Bool) { guard let panel = panel, let parent = anchorWindow else { return } let parentFrame = parent.frame let size = panel.frame.size - let rightInset: CGFloat = 16 - // Clear the titlebar plus a bit of breathing room. - let topInset: CGFloat = 50 + let rightInset: CGFloat = 8 // align toast's right edge ~flush with window + let gapAbove: CGFloat = 10 // breathing room between toast and titlebar - let target = NSPoint( - x: parentFrame.maxX - size.width - rightInset, - y: parentFrame.maxY - size.height - topInset - ) + // Right-aligned to the VM window's right edge. + let targetX = parentFrame.maxX - size.width - rightInset + + // Default: bottom of toast `gapAbove` above the top of the VM window. + var targetY = parentFrame.maxY + gapAbove + + // Clamp so the toast never overlaps the menu bar on the active screen. + if let screen = parent.screen ?? NSScreen.screens.first { + let menuBarBottom = screen.visibleFrame.maxY + if targetY + size.height > menuBarBottom { + // No room above: tuck into the window's top-right corner under the + // titlebar instead. 50 pt clears the title bar plus a bit of inset. + targetY = parentFrame.maxY - size.height - 50 + } + } + + let target = NSPoint(x: targetX, y: targetY) if animated { // Start off-screen to the right, then slide in.