Files
shotscreen/ShotScreen/Sources/DraggableImageView.swift
Nick Roodenrijs 0dabed11d2 🎉 ShotScreen v1.0 - Initial Release
🚀 First official release of ShotScreen with complete feature set:

 Core Features:
- Advanced screenshot capture system
- Multi-monitor support
- Professional UI/UX design
- Automated update system with Sparkle
- Apple notarized & code signed

🛠 Technical Excellence:
- Native Swift macOS application
- Professional build & deployment pipeline
- Comprehensive error handling
- Memory optimized performance

📦 Distribution Ready:
- Professional DMG packaging
- Apple notarization complete
- No security warnings for users
- Ready for public distribution

This is the foundation release that establishes ShotScreen as a premium screenshot tool for macOS.
2025-06-28 16:15:15 +02:00

213 lines
8.4 KiB
Swift

import AppKit
// MARK: - DraggableImageView Protocol
protocol DraggableImageViewClickHandler: AnyObject {
func thumbnailWasClicked(image: NSImage)
}
// MARK: - DraggableImageView
class DraggableImageView: NSImageView {
var onDragStart: (() -> Void)?
weak var appDelegate: ScreenshotApp?
private var mouseDownEvent: NSEvent?
private let dragThreshold: CGFloat = 3.0
private var isPerformingDrag: Bool = false
// 🎨 NEW: Track the file URL for the current image (for BGR mode)
var imageURL: URL?
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.imageScaling = .scaleProportionallyUpOrDown
self.imageAlignment = .alignCenter
self.animates = true
self.imageFrameStyle = .none
self.registerForDraggedTypes([.fileURL, .URL, .tiff, .png])
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.imageScaling = .scaleProportionallyUpOrDown
self.imageAlignment = .alignCenter
self.animates = true
self.imageFrameStyle = .none
self.registerForDraggedTypes([.fileURL, .URL, .tiff, .png])
}
override func mouseDown(with event: NSEvent) {
self.mouseDownEvent = event
self.isPerformingDrag = false
}
override func mouseDragged(with event: NSEvent) {
guard let mouseDownEvent = self.mouseDownEvent else {
super.mouseDragged(with: event)
return
}
if !isPerformingDrag {
let dragThreshold: CGFloat = 3.0
let deltaX = abs(event.locationInWindow.x - mouseDownEvent.locationInWindow.x)
let deltaY = abs(event.locationInWindow.y - mouseDownEvent.locationInWindow.y)
if deltaX > dragThreshold || deltaY > dragThreshold {
isPerformingDrag = true
self.mouseDownEvent = nil
guard let unwrappedAppDelegate = appDelegate else {
isPerformingDrag = false
return
}
// 🎨 FIXED: Check for available URL (BGR mode or normal mode) before proceeding
let sourceURLForDrag = self.imageURL ?? unwrappedAppDelegate.tempURL
guard let finalSourceURL = sourceURLForDrag else {
print("❌ DraggableImageView: No valid URL available for dragging (imageURL: \(imageURL?.path ?? "nil"), tempURL: \(unwrappedAppDelegate.tempURL?.path ?? "nil"))")
isPerformingDrag = false
return
}
print("🎯 DraggableImageView: Starting drag with URL: \(finalSourceURL.path)")
if let preview = unwrappedAppDelegate.activePreviewWindow, preview.isVisible {
preview.orderOut(nil as Any?)
}
unwrappedAppDelegate.gridViewManager?.showGrid(previewFrame: self.window?.frame)
// NIEUW: Start drag session voor proximity monitoring
unwrappedAppDelegate.gridViewManager?.startDragSession()
let fileItem = NSDraggingItem(pasteboardWriter: finalSourceURL as NSURL)
if let imageToDrag = self.image {
let fullFrame = convert(bounds, to: nil)
let scale: CGFloat = 0.05
let yOffset: CGFloat = 30
let scaledFrame = NSRect(
x: fullFrame.midX - fullFrame.width * scale / 2,
y: fullFrame.midY - fullFrame.height * scale / 2 - yOffset,
width: fullFrame.width * scale,
height: fullFrame.height * scale
)
fileItem.setDraggingFrame(scaledFrame, contents: imageToDrag)
}
let items: [NSDraggingItem] = [fileItem]
beginDraggingSession(with: items, event: event, source: self)
} else {
return
}
}
}
override func mouseUp(with event: NSEvent) {
if !isPerformingDrag {
if let image = self.image, let appDelegate = self.appDelegate {
appDelegate.thumbnailWasClicked(image: image)
}
super.mouseUp(with: event)
}
self.mouseDownEvent = nil
self.isPerformingDrag = false
}
}
// MARK: - NSDraggingSource
extension DraggableImageView: NSDraggingSource {
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
return .copy
}
func draggingSession(_ session: NSDraggingSession, willBeginAt screenPoint: NSPoint) {
// Drag session beginning
}
func draggingSession(_ session: NSDraggingSession, movedTo screenPoint: NSPoint) {
guard let appDel = self.appDelegate,
let gridManager = appDel.gridViewManager,
let gridWindow = gridManager.gridWindow else { return }
let gridFrame = gridWindow.frame
let distanceToGrid = min(
abs(screenPoint.x - gridFrame.minX),
abs(screenPoint.x - gridFrame.maxX)
)
// Update visual feedback based on proximity
if Int(distanceToGrid) % 50 == 0 {
let minScale: CGFloat = 0.05
let maxScale: CGFloat = 0.35
let maxDistance: CGFloat = 300
if distanceToGrid < maxDistance {
let progress = distanceToGrid / maxDistance
_ = minScale + (progress * (maxScale - minScale))
}
}
}
func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
isPerformingDrag = false
// NIEUW: Stop drag session direct na drag end
appDelegate?.gridViewManager?.stopDragSession()
DispatchQueue.main.async { [weak self] in
guard let self = self, let appDel = self.appDelegate else {
return
}
let didDropOnGridAction = appDel.didGridHandleDrop
let didDropOnStashGridAction = appDel.didStashGridHandleDrop
let didDropOnAnyGridAction = didDropOnGridAction || didDropOnStashGridAction
let closeAfterDragSetting = SettingsManager.shared.closeAfterDrag
if !didDropOnAnyGridAction && appDel.gridViewManager?.gridWindow != nil {
appDel.gridViewManager?.hideGrid(monitorForReappear: false)
}
if didDropOnAnyGridAction {
// Drop handled by grid action (main or stash). Preview management deferred to grid action handler.
print("🔄 DraggableImageView: Grid action detected (main: \(didDropOnGridAction), stash: \(didDropOnStashGridAction))")
// 🔧 CRITICAL FIX: Handle ALL grid actions (both main and stash) with closeAfterDrag setting
print("🔄 Grid action completed - applying closeAfterDrag setting: \(closeAfterDragSetting)")
if closeAfterDragSetting {
print("🔄 Closing thumbnail after grid action due to closeAfterDrag setting")
appDel.closePreviewWithAnimation(immediate: false, preserveTempFile: false)
} else {
print("🔄 Keeping thumbnail visible after grid action (closeAfterDrag is OFF)")
appDel.ensurePreviewVisible()
}
} else {
if operation != [] {
if closeAfterDragSetting {
appDel.closePreviewWithAnimation(immediate: true)
} else {
appDel.ensurePreviewVisible()
}
} else {
appDel.ensurePreviewVisible()
}
}
// Reset both flags
appDel.didGridHandleDrop = false
appDel.didStashGridHandleDrop = false
}
}
}
// MARK: - NSImage Extension for PNG Data
extension NSImage {
func pngData() -> Data? {
guard let tiffData = self.tiffRepresentation,
let bitmapRep = NSBitmapImageRep(data: tiffData) else {
return nil
}
return bitmapRep.representation(using: .png, properties: [:])
}
}