🎉 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.
This commit is contained in:
213
ShotScreen/Sources/DraggableImageView.swift
Normal file
213
ShotScreen/Sources/DraggableImageView.swift
Normal file
@@ -0,0 +1,213 @@
|
||||
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: [:])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user