// // ImageView.swift // Cyberlock // // Created by Juraldinio on 9/1/19. // Copyright © 2019 Omicronmedia. All rights reserved. // import Foundation import AppKit final class ImageView: NSImageView { typealias TrackingMouseOverClosure = (_ imageView: ImageView, _ isHover: Bool) -> Void private var trackingArea: NSTrackingArea? var roundImage = false var imageUrl: URL? { didSet { guard let url = self.imageUrl else { self.image = nil return } DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let image = NSImage(contentsOf: url) else { return } DispatchQueue.main.async { self?.image = image } } } } var allowLeftClickMenu: Bool = false var useHandCursor = false { didSet { self.window?.invalidateCursorRects(for: self) } } var mouseOverTracker: TrackingMouseOverClosure? { didSet { self.setupTrackingArea(enable: self.mouseOverTracker.isExist) } } var increaseHitArea = false var isWindowDragging = false var isDocked: Bool? private var canBeDragged = false override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.setup() } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setup() { self.setContentCompressionResistancePriority(.windowSizeStayPut, for: .horizontal) self.setContentCompressionResistancePriority(.windowSizeStayPut, for: .vertical) self.imageScaling = .scaleProportionallyUpOrDown self.translatesAutoresizingMaskIntoConstraints = false self.wantsLayer = true } private func setupTrackingArea(enable: Bool) { if let trackingArea = self.trackingArea { self.removeTrackingArea(trackingArea) } guard enable else { return } let area = NSTrackingArea.init(rect: self.bounds, options: [NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeAlways], owner: self, userInfo: nil) self.trackingArea = area self.addTrackingArea(area) } // MARK: - LifeCycle override func layout() { super.layout() self.setupTrackingArea(enable: self.mouseOverTracker.isExist) // Round image guard self.roundImage else { return } let radius = self.bounds.width / 2.0 self.layer?.cornerRadius = radius } override func resetCursorRects() { super.resetCursorRects() guard self.useHandCursor else { return } self.addCursorRect(self.bounds, cursor: NSCursor.pointingHand) } override func mouseDown(with event: NSEvent) { guard self.useHandCursor else { return } if self.allowLeftClickMenu , let menu = self.menu { NSMenu.popUpContextMenu(menu, with: event, for: self) return } guard let action = self.action else { super.mouseDown(with: event) return } NSApp.sendAction(action, to: self.target, from: self) } override func hitTest(_ point: NSPoint) -> NSView? { if self.isWindowDragging { let view = super.hitTest(point) self.canBeDragged = view == self return view } guard self.useHandCursor else { return nil } guard self.increaseHitArea else { return super.hitTest(point) } return self.frame.insetBy(dx: -5, dy: -5).contains(point) ? self : nil } override func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) self.mouseOverTracker?(self, true) } override func mouseExited(with event: NSEvent) { super.mouseExited(with: event) self.mouseOverTracker?(self, false) } override func mouseDragged(with event: NSEvent) { guard let isDocked = self.isDocked, !isDocked, self.canBeDragged else { return } self.window?.performDrag(with: event) } }