Photo Editor/CanvasImageView.swift
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
The CanvasImageView is a basic NSView subclass. It demonstrates manually drawing an image inside of a normal draw() method, along with event tracking. The event tracking moves the clip view that contains the image, or allows drawing on top of it. |
*/ |
import Cocoa |
class CanvasImageView: NSView { |
// PhotoDocumentWindowController also has an EditMode, but this view only supports two styles of editing: moving and drawing. |
enum EditMode: Int { |
case move |
case draw |
} |
var image: NSImage? { |
didSet(oldImage) { |
if (oldImage != image) { |
needsDisplay = true |
invalidateIntrinsicContentSize() |
} |
} |
} |
var delegate: CanvasImageViewDelegate? |
override init(frame frameRect: NSRect) { |
super.init(frame: frameRect) |
commonSetup() |
} |
required init?(coder: NSCoder) { |
super.init(coder: coder) |
commonSetup() |
} |
private func commonSetup() { |
layerContentsRedrawPolicy = .onSetNeedsDisplay |
} |
override func draw(_ dirtyRect: NSRect) { |
if let image = image { |
image.draw(in: bounds) |
} |
} |
// Make the origin be the top left |
override var isFlipped: Bool { |
return true |
} |
override var intrinsicContentSize: NSSize { |
get { |
if let image = image { |
return image.size |
} else { |
return NSSize(width: 800, height: 600) |
} |
} |
} |
private func getEditMode() -> EditMode { |
if let delegate = delegate { |
return delegate.getEditMode(in: self) |
} else { |
return .move |
} |
} |
private func sendImageChanged() { |
if let delegate = delegate { |
delegate.canvasImageView(self, didChangeImage: image) |
} |
} |
override func mouseDown(with event: NSEvent) { |
switch getEditMode() { |
case .move: |
trackForMove(event: event) |
case .draw: |
trackForDraw(event: event) |
} |
} |
// Move the scrollview when the mouse moves |
private func trackForMove(event mouseDownEvent: NSEvent) { |
let window = self.window! |
let startingPoint = mouseDownEvent.locationInWindow |
let contentClipView = enclosingScrollView!.contentView |
let startingOrigin = contentClipView.bounds.origin |
window.trackEvents(matching: [.leftMouseDragged, .leftMouseUp], timeout:NSEventDurationForever, mode: .defaultRunLoopMode) { event, stop in |
let currentPoint = event.locationInWindow |
let xMoved = startingPoint.x - currentPoint.x // move to the right moves the contents the opposite direction |
let yMoved = currentPoint.y - startingPoint.y |
let newOrigin = NSPoint(x: startingOrigin.x + xMoved, y: startingOrigin.y + yMoved) |
contentClipView.scroll(to: newOrigin) |
if event.type == .leftMouseUp { |
stop.pointee = true |
} |
} |
} |
// "draw" on the image |
private func trackForDraw(event mouseDownEvent: NSEvent) { |
// Draw on a copy of the image |
guard let image = image?.copy() as? NSImage else { return } |
let window = self.window! |
var lastPoint = convert(mouseDownEvent.locationInWindow, from: nil) |
window.trackEvents(matching: [.leftMouseDragged, .leftMouseUp], timeout:NSEventDurationForever, mode: .defaultRunLoopMode) { event, stop in |
let currentPoint = self.convert(event.locationInWindow, from: nil) |
image.lockFocusFlipped(true) |
let path = NSBezierPath() |
path.move(to: lastPoint) |
path.line(to: currentPoint) |
let color = NSColor.black |
color.set() |
path.lineWidth = 4.0 // easy to see |
path.stroke() |
image.unlockFocus() |
lastPoint = currentPoint |
// Refresh what we are showing |
self.image = image |
self.needsDisplay = true |
if event.type == .leftMouseUp { |
stop.pointee = true |
} |
} |
// Tell the delegate the image changed only once at the end of the mouse tracking |
sendImageChanged() |
} |
} |
// We don't want to tie our implementation to any specific controller, and instead use delegation via a protocol |
protocol CanvasImageViewDelegate { |
func canvasImageView(_ canvasImageView: CanvasImageView, didChangeImage image: NSImage?) |
func getEditMode(in canvasImageView: CanvasImageView) -> CanvasImageView.EditMode |
} |
// Provide a default implementation to make the protocol implementation optional |
extension CanvasImageViewDelegate { |
func canvasImageView(_ canvasImageView: CanvasImageView, didChange image: NSImage?) { |
} |
func getEditMode(in canvasImageView: CanvasImageView) -> CanvasImageView.EditMode { |
return CanvasImageView.EditMode.move |
} |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-10-27