From abf69c195c5c6f56de441c924c971bb524876277 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 12:32:27 -0700 Subject: [PATCH 01/18] trimmed whitespace --- FoundationHeaders.swift | 2 +- VPL/AppDelegate.swift | 4 +- VPL/Data/Node.swift | 16 +-- VPL/Data/NodeTrigger.swift | 42 ++++---- VPL/Data/NodeValue.swift | 38 +++---- VPL/Data/NodeVariable.swift | 28 ++--- VPL/Data/ValueType.swift | 22 ++-- VPL/OCR/DrawingCanvas.swift | 86 +++++++-------- VPL/OCR/ImageUtils.swift | 34 +++--- VPL/OCR/OCRRequest.swift | 68 ++++++------ VPL/Rendering/CanvasViewController.swift | 56 +++++----- VPL/Rendering/CodeOutputView.swift | 40 +++---- .../DisplayNodeContentView.swift | 4 +- .../Content Views/DrawCanvasNodeView.swift | 70 ++++++------ .../Node/Content Views/GenericInputView.swift | 48 ++++----- .../Node/Content Views/ValueChooserView.swift | 38 +++---- VPL/Rendering/Node/DisplayNode.swift | 76 ++++++------- VPL/Rendering/Node/DisplayNodeCanvas.swift | 84 +++++++-------- .../Node/DisplayNodeCanvasOverlay.swift | 32 +++--- VPL/Rendering/Node/DisplayNodeSocket.swift | 100 +++++++++--------- .../Node/Implementations/ArrayNodes.swift | 40 +++---- .../Node/Implementations/BaseNode.swift | 6 +- .../Node/Implementations/ConstNodes.swift | 30 +++--- .../Implementations/ControlFlowNodes.swift | 18 ++-- .../Implementations/DictionaryNodes.swift | 34 +++--- .../Implementations/DisplayableNode.swift | 18 ++-- .../Node/Implementations/MathNodes.swift | 38 +++---- .../Node/Implementations/MiscNodes.swift | 10 +- .../Node/Implementations/VariableNodes.swift | 20 ++-- VPL/Utils.swift | 6 +- 30 files changed, 554 insertions(+), 554 deletions(-) diff --git a/FoundationHeaders.swift b/FoundationHeaders.swift index b2d01b5..5fe66ed 100644 --- a/FoundationHeaders.swift +++ b/FoundationHeaders.swift @@ -10360,4 +10360,4 @@ class URLSessionStreamTask : Foundation.URLSessionTask { } var NSURLErrorCannotCloseFile: Swift.Int { get {} -} \ No newline at end of file +} diff --git a/VPL/AppDelegate.swift b/VPL/AppDelegate.swift index 044af3d..7436281 100644 --- a/VPL/AppDelegate.swift +++ b/VPL/AppDelegate.swift @@ -17,12 +17,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Create canvas view controller let canvasViewController = CanvasViewController() - + // Create the window window = UIWindow(frame: UIScreen.main.bounds) window!.rootViewController = canvasViewController window!.makeKeyAndVisible() - + return true } diff --git a/VPL/Data/Node.swift b/VPL/Data/Node.swift index bb28ba9..bb7d9a6 100644 --- a/VPL/Data/Node.swift +++ b/VPL/Data/Node.swift @@ -10,7 +10,7 @@ import Foundation public enum NodeOutput { case triggers([OutputTrigger]), value(OutputValue), none - + /// Returns the triggers, if a triggers type. public var triggers: [OutputTrigger]? { if case let .triggers(triggers) = self { @@ -19,7 +19,7 @@ public enum NodeOutput { return nil } } - + /// Returns the value, if a value type. public var value: OutputValue? { if case let .value(value) = self { @@ -37,9 +37,9 @@ public protocol Node: class { var inputValues: [InputValue] { get } var inputVariables: [InputVariable] { get } var output: NodeOutput { get } - + init() - + func assemble() -> String } @@ -63,14 +63,14 @@ extension Node { return nil } } - + /// Variables that this node can use. public var availableVariables: [NodeVariable] { // Add variables available from all parent triggers let trigger = nearestControlNode return (trigger?.target?.exposedVariables ?? []) + (trigger?.target?.owner.availableVariables ?? []) } - + public func setupConnections() { inputTrigger?.owner = self for value in inputValues { value.owner = self } @@ -84,7 +84,7 @@ extension Node { break } } - + public func destroy() { inputTrigger?.reset() for value in inputValues { value.reset() } @@ -97,7 +97,7 @@ extension Node { break } } - + public func assembleOutputTrigger(id: String? = nil) -> String { if case let .triggers(triggers) = output { if let id = id { diff --git a/VPL/Data/NodeTrigger.swift b/VPL/Data/NodeTrigger.swift index cce576f..8e03bb7 100644 --- a/VPL/Data/NodeTrigger.swift +++ b/VPL/Data/NodeTrigger.swift @@ -11,36 +11,36 @@ import Foundation public final class InputTrigger { /// The node that owns this trigger. public internal(set) weak var owner: Node! - + /// The connected trigger. public private(set) var target: OutputTrigger? - + public init() { - + } - + /// Determines if two triggers can be connected. public func canConnect(to newTarget: OutputTrigger) -> Bool { return newTarget.canConnect(to: self) } - + /// Connects this trigger to another trigger. public func connect(to newTarget: OutputTrigger) { // Set the new target target = newTarget - + // Connect the other node if newTarget.target !== self { newTarget.connect(to: self) } } - + /// Disconnects any targets this is connected to. public func reset() { // Remove the target let tmpTarget = target target = nil - + // Remove other target if needed if tmpTarget?.target != nil { tmpTarget?.reset() @@ -51,62 +51,62 @@ public final class InputTrigger { public final class OutputTrigger { /// The node that owns this trigger public internal(set) weak var owner: Node! - + /// An identifier for this trigger. public let id: String - + /// Name for this trigger. public let name: String - + /// The connected trigger. public private(set) var target: InputTrigger? - + /// Variables availables to any other nodes further along the control flow. public let exposedVariables: [NodeVariable] - + public init(id: String, name: String, exposedVariables: [NodeVariable] = []) { self.id = id self.name = name self.exposedVariables = exposedVariables - + // Set owner on variables for variable in exposedVariables { variable.owner = self } } - + public convenience init(exposedVariables: [NodeVariable] = []) { self.init(id: "next", name: "Next", exposedVariables: exposedVariables) } - + /// Determines if two triggers can be connected. public func canConnect(to newTarget: InputTrigger) -> Bool { return owner !== newTarget.owner && target == nil && newTarget.target == nil } - + /// Connects this trigger to another trigger. public func connect(to newTarget: InputTrigger) { // Set the new target target = newTarget - + // Connect the other node if newTarget.target !== self { newTarget.connect(to: self) } } - + /// Disconnects any targets this is connected to. public func reset() { // Remove the target let tmpTarget = target target = nil - + // Remove other target if needed if tmpTarget?.target != nil { tmpTarget?.reset() } } - + /// Assembles the code. public func assemble() -> String { return target?.owner.assemble() ?? "" diff --git a/VPL/Data/NodeValue.swift b/VPL/Data/NodeValue.swift index 030fd77..183ae68 100644 --- a/VPL/Data/NodeValue.swift +++ b/VPL/Data/NodeValue.swift @@ -11,53 +11,53 @@ import Foundation public final class InputValue { /// The node that owns this value. public internal(set) weak var owner: Node! - + /// An identifier for this value. public let id: String - + /// Name for this value. public let name: String - + /// The type of value this holds. public let type: ValueType - + /// The connected value. public private(set) var target: OutputValue? - + public init(id: String, name: String, type: ValueType) { self.id = id self.name = name self.type = type } - + /// Determines if two values can be connected. public func canConnect(to newTarget: OutputValue) -> Bool { return newTarget.canConnect(to: self) } - + /// Connects this value to another value. public func connect(to newTarget: OutputValue) { // Set the new target target = newTarget - + // Connect the other node if newTarget.target !== self { newTarget.connect(to: self) } } - + /// Disconnects any targets this is connected to. public func reset() { // Remove the target let tmpTarget = target target = nil - + // Remove other target if needed if tmpTarget?.target != nil { tmpTarget?.reset() } } - + /// Assembles the code. public func assemble() -> String { return target?.owner.assemble() ?? "" @@ -67,39 +67,39 @@ public final class InputValue { public final class OutputValue { /// The node that owns this value public internal(set) weak var owner: Node! - + /// The type of value this holds. public let type: ValueType - + /// The connected value. public private(set) var target: InputValue? - + public init(type: ValueType) { self.type = type } - + /// Determines if two values can be connected. public func canConnect(to newTarget: InputValue) -> Bool { return type.canCast(to: newTarget.type) && owner !== newTarget.owner && target == nil && newTarget.target == nil } - + /// Connects this value to another value. public func connect(to newTarget: InputValue) { // Set the new target target = newTarget - + // Connect the other node if newTarget.target !== self { newTarget.connect(to: self) } } - + /// Disconnects any targets this is connected to. public func reset() { // Remove the target let tmpTarget = target target = nil - + // Remove other target if needed if tmpTarget?.target != nil { tmpTarget?.reset() diff --git a/VPL/Data/NodeVariable.swift b/VPL/Data/NodeVariable.swift index d873b01..cc2ef18 100644 --- a/VPL/Data/NodeVariable.swift +++ b/VPL/Data/NodeVariable.swift @@ -10,19 +10,19 @@ import Foundation public class NodeVariable { public static var variableId: String { return String(format: "v%06x", Int(arc4random())) } - + /// The trigger that owns this variable. public internal(set) weak var owner: OutputTrigger! - + /// A UUID that represents this variable in the code itself. public let id: String - + /// Label for human readability. public let name: String - + /// The type of variable. public let type: ValueType - + public init(name: String, type: ValueType) { self.id = NodeVariable.variableId self.name = name @@ -33,43 +33,43 @@ public class NodeVariable { public final class InputVariable { /// The node that owns this value. public internal(set) weak var owner: Node! - + /// An identifier for this value. public let id: String - + /// Name for this value. public let name: String - + /// The type of value this holds. public let type: ValueType - + /// The connected value. public private(set) var target: NodeVariable? - + public init(id: String, name: String, type: ValueType) { self.id = id self.name = name self.type = type } - + /// Determines if two values can be connected. public func canConnect(to newTarget: NodeVariable) -> Bool { /// Make sure the node can see the variable. return newTarget.type.canCast(to: type) && owner.availableVariables.contains { $0 === newTarget } } - + /// Connects this value to another value. public func connect(to newTarget: NodeVariable) { // Set the new target target = newTarget } - + /// Disconnects any targets this is connected to. public func reset() { // Remove the target target = nil } - + /// Assembles the code. public func assemble() -> String { return target?.id ?? "NO VARIABLE" diff --git a/VPL/Data/ValueType.swift b/VPL/Data/ValueType.swift index 044996a..313ccb8 100644 --- a/VPL/Data/ValueType.swift +++ b/VPL/Data/ValueType.swift @@ -11,34 +11,34 @@ import Foundation public indirect enum ValueType: CustomStringConvertible { /// Custom variable type. These correspond to the exact Swift variable type. case type(String) - + /// `unknown` is used as a way of passing around non-primitive or unknown /// value types. This basically allows for having functionality that the VFL /// does not support yet. *However*, this removes safety can may cause /// compile time errors after assembly. case unknown - + /// Pseudo-generic types. case generic(String, [ValueType]) - + /// Provides as a way of having a flexible variable type that is inherited /// from another input value. `inputId` is the ID of the `InputValue` that /// represents where the data comes from. // case proxy(inputId: String) - + public static var bool: ValueType { return .type("Bool") } public static var int: ValueType { return .type("Int") } public static var float: ValueType { return .type("Float") } public static var string: ValueType { return .type("String") } - + public static func array(_ inner: ValueType) -> ValueType { return .generic("Array", [inner]) - + } public static func dictionary(_ a: ValueType, _ b: ValueType) -> ValueType { return .generic("Dictionary", [a, b]) } - + public var description: String { switch self { case .type(let type): @@ -49,13 +49,13 @@ public indirect enum ValueType: CustomStringConvertible { return "unknown" } } - + public func canCast(to other: ValueType) -> Bool { // Anything cna be casted to unknown if case .unknown = other { return true } - + // Check for other casting switch self { case .type(let type): @@ -70,14 +70,14 @@ public indirect enum ValueType: CustomStringConvertible { if subtypes.count != otherSubtypes.count { return false } - + // Check each subtype can cast for i in 0.. Void)? public var onInputFinish: ((_ charBox: CGRect) -> Void)? - + override init(frame: CGRect) { super.init(frame: frame) - + backgroundColor = UIColor(patternImage: UIImage(named: "background.png")!) - + imageView.frame = bounds tempImageView.frame = bounds overlayImageView.frame = bounds @@ -49,19 +49,19 @@ class DrawingCanvas: UIView { addSubview(tempImageView) addSubview(overlayImageView) } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func complete() -> UIImage? { // TODO: Clip the text to visible - + // Get the result guard let transparentResult = imageView.image else { return nil } - + // Add a white background UIGraphicsBeginImageContextWithOptions(frame.size, false, 0) guard let context = UIGraphicsGetCurrentContext() else { @@ -69,46 +69,46 @@ class DrawingCanvas: UIView { } context.setFillColor(gray: 1.0, alpha: 1.0) context.fill(imageView.bounds) - + // Draw the image on top of it transparentResult.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height), blendMode: .normal, alpha: 1.0) - + // Get the final output let result = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - + // Clear the image imageView.image = nil - + return result } - - + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { guard lastPoint == nil, let touch = touches.first else { print("No touch or already has last point.") return } - + // Save the last position lastPoint = touch.location(in: self) strokeMinPosition = lastPoint strokeMaxPosition = lastPoint - + // Call input start onInputStart?() } - + override func touchesMoved(_ touches: Set, with event: UIEvent?) { guard let lastPoint = lastPoint, let touch = touches.first else { print("No touch or already missing last point.") return } - + // Draw a line between the previous and current point let currentPoint = touch.location(in: self) drawLine(from: lastPoint, to: currentPoint) - + // Save the point self.lastPoint = currentPoint if let strokeMinPosition = strokeMinPosition { @@ -120,20 +120,20 @@ class DrawingCanvas: UIView { self.strokeMaxPosition?.y = max(strokeMaxPosition.y, lastPoint.y) } } - + override func touchesEnded(_ touches: Set, with event: UIEvent?) { guard let lastPoint = lastPoint, let touch = touches.first else { print("No touch or already missing last point.") return } - + // Draw a single point if just touched let currentPoint = touch.location(in: self) drawLine(from: lastPoint, to: currentPoint) - + // Begin new context UIGraphicsBeginImageContextWithOptions(frame.size, false, 0) - + // Fill a blank background guard let context = UIGraphicsGetCurrentContext() else { print("Failed to get graphics context.") @@ -141,18 +141,18 @@ class DrawingCanvas: UIView { return } context.clear(bounds) - + // Draw the images into the new context imageView.image?.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height), blendMode: .normal, alpha: 1.0) tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height), blendMode: .normal, alpha: 1.0) - + // Assign the image imageView.image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - + // Clear the temp image view tempImageView.image = nil - + // Update the min and max if let strokeMinPosition = strokeMinPosition { self.strokeMinPosition?.x = min(strokeMinPosition.x, lastPoint.x) @@ -162,7 +162,7 @@ class DrawingCanvas: UIView { self.strokeMaxPosition?.x = max(strokeMaxPosition.x, lastPoint.x) self.strokeMaxPosition?.y = max(strokeMaxPosition.y, lastPoint.y) } - + // Calculate the character box var charBox: CGRect = CGRect.zero if let strokeMinPosition = strokeMinPosition, let strokeMaxPosition = strokeMaxPosition { @@ -171,24 +171,24 @@ class DrawingCanvas: UIView { width: strokeMaxPosition.x - strokeMinPosition.x, height: strokeMaxPosition.y - strokeMinPosition.y ) } - + // Remove the last point self.lastPoint = nil strokeMinPosition = nil strokeMaxPosition = nil - + // Call input finish onInputFinish?(charBox) } - + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { // Clear the temp image and ignroe the stroke tempImageView.image = nil - + // Remove the last point self.lastPoint = nil } - + /// Draw a line between two points. func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) { // Start a new context @@ -199,18 +199,18 @@ class DrawingCanvas: UIView { return } tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)) - + // Draw the line context.move(to: fromPoint) context.addLine(to: toPoint) - + // Stroke the path context.setLineCap(.round) context.setLineWidth(brushWidth) context.setStrokeColor(gray: 0, alpha: 1) context.setBlendMode(.normal) context.strokePath() - + // Render to the temp image tempImageView.image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() @@ -221,7 +221,7 @@ class DrawingCanvas: UIView { imageView.frame = bounds tempImageView.frame = bounds overlayImageView.frame = bounds - + // Clear other images tempImageView.image = nil imageView.image = nil diff --git a/VPL/OCR/ImageUtils.swift b/VPL/OCR/ImageUtils.swift index 7cd1366..6fb532e 100755 --- a/VPL/OCR/ImageUtils.swift +++ b/VPL/OCR/ImageUtils.swift @@ -178,7 +178,7 @@ func preProcess(image: UIImage, size: CGSize, invert shouldInvert: Bool = false, let height = image.size.height let addToHeight2 = height / 2 let addToWidth2 = ((6 * height) / 3 - width) / 2 - + // Process the image var image = image if shouldInvert { @@ -189,7 +189,7 @@ func preProcess(image: UIImage, size: CGSize, invert shouldInvert: Bool = false, } image = resize(image: image, targetSize: size) image = convertToGrayscale(image: image) - + return image } @@ -203,7 +203,7 @@ func invert(image: UIImage) -> UIImage { print("Failed to get CGImage.") return UIImage() } - + // Invert the image let img = CIImage(cgImage: cgImage) filter.setDefaults() @@ -233,47 +233,47 @@ extension UIImage { if x < 0 || x > Int(size.width) || y < 0 || y > Int(size.height) { return nil } - + guard let providerData = cgImage?.dataProvider?.data else { return nil } let data = CFDataGetBytePtr(providerData)! - + let numberOfComponents = 4 let pixelData = ((Int(size.width) * y) + x) * numberOfComponents - + let r = CGFloat(data[pixelData]) / 255.0 let g = CGFloat(data[pixelData + 1]) / 255.0 let b = CGFloat(data[pixelData + 2]) / 255.0 let a = CGFloat(data[pixelData + 3]) / 255.0 - + return UIColor(red: r, green: g, blue: b, alpha: a) } - + // From: https://stackoverflow.com/a/48759198 func trimWhiteRect() -> CGRect { - + let cgImage = self.cgImage! - + let width = cgImage.width let height = cgImage.height - + let colorSpace = CGColorSpaceCreateDeviceRGB() let bytesPerPixel:Int = 4 let bytesPerRow = bytesPerPixel * width let bitsPerComponent = 8 let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue - + guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo), let ptr = context.data?.assumingMemoryBound(to: UInt8.self) else { return CGRect.zero } - + context.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: width, height: height)) - + var minX = width var minY = height var maxX: Int = 0 var maxY: Int = 0 - + for x in 1.. maxX { maxX = x } @@ -290,7 +290,7 @@ extension UIImage { } } } - + return CGRect(x: CGFloat(minX),y: CGFloat(minY), width: CGFloat(maxX-minX), height: CGFloat(maxY-minY)) } } diff --git a/VPL/OCR/OCRRequest.swift b/VPL/OCR/OCRRequest.swift index 3e47a4b..db065d2 100644 --- a/VPL/OCR/OCRRequest.swift +++ b/VPL/OCR/OCRRequest.swift @@ -23,7 +23,7 @@ enum OCRResult { enum OCRDataset { case digits, alphanum, chars74k - + func createModel() throws -> VNCoreMLModel { switch self { case .digits: @@ -34,7 +34,7 @@ enum OCRDataset { return try VNCoreMLModel(for: Chars74k().model) } } - + func preprocess(input: UIImage) -> UIImage { switch self { case .digits: @@ -51,19 +51,19 @@ enum OCRDataset { class OCRRequest { /// The dataset being used by the request. private let dataset: OCRDataset - + /// Model used for the request private let model: VNCoreMLModel - + /// Image that was given to the request. private let image: UIImage - + /// Results of the request. private var queryResults = [Int: [Int: OCRResult]]() - + /// Callback for when the request is complete. private let onComplete: (String, OCRResultBreakdown) -> Void - + /// If the request is complete. public var completed: Bool { // Check if there are still any nil values @@ -77,7 +77,7 @@ class OCRRequest { } return true } - + @discardableResult public init(dataset: OCRDataset, image: UIImage, singleCharacter: Bool, onComplete: @escaping (String, OCRResultBreakdown) -> Void) throws { // Save the image @@ -85,14 +85,14 @@ class OCRRequest { let convertedImage = image |> adjustColors |> convertToGrayscale self.image = image self.onComplete = onComplete - + // Save the model self.model = try dataset.createModel() - + if singleCharacter { // Set the query results queryResults = [0: [0: .loading]] - + // Get the cropped image for the character let charBox = image.trimWhiteRect() if let cropped = crop(image: image, rectangle: charBox) { @@ -111,7 +111,7 @@ class OCRRequest { try handler.perform([request]) } } - + private func detectTextHandler(request: VNRequest, error: Error?) { // Validate the results if let error = error { @@ -122,22 +122,22 @@ class OCRRequest { print("No results.") return } - + // Setup query results for (wordIndex, observation) in observations.enumerated() { guard let charBoxes = observation.characterBoxes else { continue } - + // Add dictionary to results queryResults[wordIndex] = [:] - + // Place a spot in the results for (charIndex, _) in charBoxes.enumerated() { queryResults[wordIndex]![charIndex] = .loading } } - + // Handle each observation for (wordIndex, observation) in observations.enumerated() { // Validate char boxes @@ -145,8 +145,8 @@ class OCRRequest { print("Missing character boxes for observation.") continue } - - + + // Handle each character box for (charIndex, charBox) in charBoxes.enumerated() { // Get the cropped image for the character @@ -161,18 +161,18 @@ class OCRRequest { } } } - + // Attempt completion, in case there rae no results self.attemptCompletion() } - + private func classifyImage(image: UIImage, charBox: CGRect, wordIndex: Int, characterIndex: Int) { // Convert the image guard let ciImage = CIImage(image: image) else { print("Failed to convert UIImage to CIImage.") return } - + // Create a request let request = VNCoreMLRequest(model: model) { (request, error) in // Get the resulting string @@ -183,16 +183,16 @@ class OCRRequest { print("Incorrect result type from VNCoreMLRequest.") return } - + // Insert the result objc_sync_enter(self) self.queryResults[wordIndex]![characterIndex] = .some(result, image, charBox) objc_sync_exit(self) - + // Try completing the request self.attemptCompletion() } - + // Handle the request let handler = VNImageRequestHandler(ciImage: ciImage) DispatchQueue.global(qos: .userInteractive).async { @@ -203,7 +203,7 @@ class OCRRequest { } } } - + private func serializeResults() -> (String, OCRResultBreakdown) { // Iterate through each word and append the characters to the string var result = "" @@ -222,33 +222,33 @@ class OCRRequest { case .failure: print("Character failure.") } - + // Next character characterIndex += 1 } - + // Add space if not the last word if queryResults[wordIndex + 1] != nil { result += " " resultBreakdown.append(nil) } - + // Next word wordIndex += 1 } - + return (result, resultBreakdown) } - + private func attemptCompletion() { // Make sure it's completed guard completed else { return } - + // Serialize the result let result = self.serializeResults() - + // Call the completion handler DispatchQueue.main.async { self.onComplete(result.0, result.1) @@ -264,7 +264,7 @@ extension DrawingCanvas { print("Failed to create overlay context.") return } - + for char in breakdown { // Make sure it's not a aspace guard let char = char else { @@ -293,7 +293,7 @@ extension DrawingCanvas { ) } } - + // Finish context overlayImageView.image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() diff --git a/VPL/Rendering/CanvasViewController.swift b/VPL/Rendering/CanvasViewController.swift index f0a7219..176c3d4 100644 --- a/VPL/Rendering/CanvasViewController.swift +++ b/VPL/Rendering/CanvasViewController.swift @@ -11,35 +11,35 @@ import UIKit public class CanvasViewController: UIViewController { /// Shortcut for custom node popover. var customNodeShortcut: String = "X" - + /// View nodes that can be created. public var spawnableNodes: [DisplayableNode.Type] = defaultNodes - + /// Output of the code. var outputView: CodeOutputView! - + /// Canvas that holds all of the nodes public let canvas: DisplayNodeCanvas - + /// Canvas for all of the drawing for quick shortcuts var drawingCanvas: DrawingCanvas! - + /// Timer for committing shortcuts var commitDrawingTimer: Timer? - + public init() { canvas = DisplayNodeCanvas(frame: CGRect.zero) - + super.init(nibName: nil, bundle: nil) } - + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + public override func viewDidLoad() { super.viewDidLoad() - + // Add the text outputView = CodeOutputView() view.addSubview(outputView) @@ -59,7 +59,7 @@ public class CanvasViewController: UIViewController { canvas.rightAnchor.constraint(equalTo: view.rightAnchor).activate() canvas.topAnchor.constraint(equalTo: view.topAnchor).activate() canvas.bottomAnchor.constraint(equalTo: outputView.topAnchor).activate() - + // Add drawing canvas drawingCanvas = DrawingCanvas(frame: canvas.bounds) canvas.backgroundView = drawingCanvas @@ -73,17 +73,17 @@ public class CanvasViewController: UIViewController { let timer = Timer(timeInterval: 0.5, repeats: false) { _ in // Remove the timer self.commitDrawingTimer = nil - + // Get the drawing guard let output = self.drawingCanvas.complete() else { print("Drawing has no image.") return } - + // Process try! OCRRequest(dataset: .alphanum, image: removeRetinaData(image: output), singleCharacter: true) { (result, breakdown) in assert(breakdown.count == 1) - + // Get the character's center guard let firstBreakdown = breakdown.first, let charResult = firstBreakdown else { print("Failed to get first char breakdown.") @@ -94,17 +94,17 @@ public class CanvasViewController: UIViewController { return } let charCenter = CGPoint(x: charBox.midX, y: charBox.midY) - + // Overlay the breakdown for debug info // self.drawingCanvas.overlayOCRBreakdown(breakdown: breakdown) - + // Present custom node popover if character == self.customNodeShortcut { self.nodeListPopover(nodes: self.spawnableNodes, charBox: charBox, showShortcuts: true) } else { // Find the nodes for the character let availableNodes = self.spawnableNodes.filter { $0.shortcutCharacter == character } - + // Create the node or popup a list if availableNodes.count > 1 { self.nodeListPopover(nodes: availableNodes, charBox: charBox, showShortcuts: false) @@ -119,7 +119,7 @@ public class CanvasViewController: UIViewController { RunLoop.main.add(timer, forMode: RunLoopMode.defaultRunLoopMode) self.commitDrawingTimer = timer } - + // Assemble the code assembleCode() } @@ -128,7 +128,7 @@ public class CanvasViewController: UIViewController { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } - + func assembleCode() { let assembled = self.canvas.assemble() self.outputView.render(code: assembled.trimmingCharacters(in: .whitespacesAndNewlines)) @@ -138,14 +138,14 @@ public class CanvasViewController: UIViewController { func create(node nodeType: DisplayableNode.Type, position: CGPoint) -> DisplayNode? { // Create the node let node = nodeType.init() - + // Create and insert the display node let displayNode = DisplayNode(node: node) canvas.insert(node: displayNode, at: position) - + return displayNode } - + /// Creates a popover to create a node. func nodeListPopover(nodes: [DisplayableNode.Type], charBox: CGRect, showShortcuts: Bool) { // Create the controller @@ -154,11 +154,11 @@ public class CanvasViewController: UIViewController { message: showShortcuts ? "Node shortcuts are displayed in parentheses." : nil, preferredStyle: .actionSheet ) - + // Configure the popover alert.popoverPresentationController?.sourceView = self.view alert.popoverPresentationController?.sourceRect = charBox - + // Display the nodes for node in nodes { // Create the title with the shortcut (only if listing all nodes) @@ -166,21 +166,21 @@ public class CanvasViewController: UIViewController { if let shortcut = node.shortcutCharacter, showShortcuts { title += " (\(shortcut))" } - + // Create an action to spawn the node let action = UIAlertAction(title: title, style: .default) { _ in self.create(node: node, position: CGPoint(x: charBox.midX, y: charBox.midY)) } alert.addAction(action) } - + // Present it self.present(alert, animated: true) } - + public override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - + // Rerender the overlay canvas.updateState() } diff --git a/VPL/Rendering/CodeOutputView.swift b/VPL/Rendering/CodeOutputView.swift index e579b7d..0619ea5 100644 --- a/VPL/Rendering/CodeOutputView.swift +++ b/VPL/Rendering/CodeOutputView.swift @@ -21,21 +21,21 @@ class CodeOutputView: UIView { "switch", "throws", "true", "try", "var", "weak", "where", "while", "willSet" ] - + let splitCharacters: [String] = [ "\n", "at ", "{", "}", "(", ")", "[", "]" ] - + var code: String = "" - + var textView: UITextView = UITextView(frame: CGRect.zero) - + var copyButton: UIButton = UIButton() - + init() { super.init(frame: CGRect.zero) - + // Style the view backgroundColor = UIColor(white: 0.96, alpha: 1.0) - + // Create text view let inset: CGFloat = 16 textView.textContainerInset = UIEdgeInsets(top: inset, left: inset, bottom: isInPlayground ? 80 : inset, right: inset) @@ -43,7 +43,7 @@ class CodeOutputView: UIView { textView.isEditable = false textView.isSelectable = true addSubview(textView) - + // Add constraints textView.translatesAutoresizingMaskIntoConstraints = false textView.topAnchor.constraint(equalTo: topAnchor).activate() @@ -52,7 +52,7 @@ class CodeOutputView: UIView { textView.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor, constant: 64).activate() textView.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor, constant: 64).activate() textView.widthAnchor.constraint(greaterThanOrEqualToConstant: 600).activate().setPriority(.defaultLow) - + // Add copy button copyButton.setTitle("Copy", for: .normal) copyButton.setTitleColor(UIColor(white: 0.2, alpha: 1.0), for: .normal) @@ -61,27 +61,27 @@ class CodeOutputView: UIView { copyButton.translatesAutoresizingMaskIntoConstraints = false copyButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -16).activate() copyButton.topAnchor.constraint(equalTo: topAnchor, constant: 8).activate() - + // Render the code render(code: "") } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func render(code: String) { // Get the code self.code = code let code = code.count > 0 ? code : "No assembled code." - + // Show/hide button copyButton.isHidden = code.count == 0 - + // Update attributed text textView.attributedText = stylize(code: code) } - + private func stylize(code: String) -> NSMutableAttributedString { // Process the code let string = NSMutableAttributedString(string: code) @@ -93,7 +93,7 @@ class CodeOutputView: UIView { while let keywordRange = searchCode.range(of: keyword) { // Replace the code to search searchCode = searchCode[keywordRange.upperBound.. Void)? - + /// Called by subclasses of `DisplayableNodeContentView` when the value /// changes. func contentValueChanged() { diff --git a/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift b/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift index 90345af..903a1b1 100644 --- a/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift +++ b/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift @@ -15,53 +15,53 @@ public enum DrawCanvasNodeInputType { public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate { /// Reference to the node. weak var node: Node? - + /// The value from the view. public var value: String = "" { didSet { // Render the value renderValue() - + // Notify change contentValueChanged() } } - + /// Input type for the view. let inputType: DrawCanvasNodeInputType - + /// Label indicating the current value. let valueLabel: UILabel - + /// How much space there is for the view to scroll to the next position. let scrollMarginWidth: CGFloat = 50 - + /// View that holds the canvas. var canvasContainer: UIView - + /// Canvas for drawing. var canvas: DrawingCanvas - + /// Left anchor for the canvas. private var canvasLeftAnchor: NSLayoutConstraint! - + /// Timer for committing shortcuts private var commitDrawingTimer: Timer? - + // Don't allow dragging override var absorbsTouches: Bool { return true } - + public init(node: Node, defaultValue: String, inputType: DrawCanvasNodeInputType, minSize: CGSize = CGSize(width: 250, height: 85)) { self.node = node self.inputType = inputType self.value = defaultValue - + self.canvasContainer = UIView() self.canvas = DrawingCanvas(frame: CGRect.zero) self.valueLabel = UILabel() - + super.init(frame: CGRect.zero) - + // Determine the dataset var dataset: OCRDataset switch inputType { @@ -70,11 +70,11 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate case .alphanum: dataset = OCRDataset.alphanum } - + // Constrain view widthAnchor.constraint(greaterThanOrEqualToConstant: minSize.width).activate() canvasContainer.heightAnchor.constraint(greaterThanOrEqualToConstant: minSize.height).activate() - + // Create the canvas container canvasContainer.layer.masksToBounds = true canvasContainer.layer.borderColor = UIColor(white: 0.9, alpha: 1).cgColor @@ -85,7 +85,7 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate canvasContainer.leftAnchor.constraint(equalTo: leftAnchor).activate() canvasContainer.rightAnchor.constraint(equalTo: rightAnchor).activate() canvasContainer.topAnchor.constraint(equalTo: topAnchor).activate() - + // Add drawing canvas canvasContainer.addSubview(canvas) canvas.translatesAutoresizingMaskIntoConstraints = false @@ -93,7 +93,7 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate canvas.topAnchor.constraint(equalTo: canvasContainer.topAnchor).activate() canvas.bottomAnchor.constraint(equalTo: canvasContainer.bottomAnchor).activate() canvas.widthAnchor.constraint(equalToConstant: 2048).activate() - + // Handle canvas events canvas.brushWidth = 6 canvas.onInputStart = { @@ -110,29 +110,29 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate self.canvasContainer.layoutIfNeeded() } } - + // Start a timer to commit the drawing let timer = Timer(timeInterval: 1.5, repeats: false) { _ in // Remove the timer self.commitDrawingTimer = nil - + // Go back to beginning of scroll UIView.animate(withDuration: 0.1) { self.canvasLeftAnchor.constant = 0 self.canvasContainer.layoutIfNeeded() } - + // Get the drawing guard let output = self.canvas.complete() else { print("Drawing has no image.") return } - + // Process try! OCRRequest(dataset: dataset, image: removeRetinaData(image: output), singleCharacter: false) { (result, breakdown) in // Overlay the breakdown for debug info // self.canvas.overlayOCRBreakdown(breakdown: breakdown) - + // Save the value self.value = result } @@ -140,7 +140,7 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate RunLoop.main.add(timer, forMode: RunLoopMode.defaultRunLoopMode) self.commitDrawingTimer = timer } - + // Display scroll margin let scrollMargin = UIView() scrollMargin.backgroundColor = UIColor(white: 0.5, alpha: 0.1) @@ -151,7 +151,7 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate scrollMargin.topAnchor.constraint(equalTo: canvasContainer.topAnchor).activate() scrollMargin.bottomAnchor.constraint(equalTo: canvasContainer.bottomAnchor).activate() scrollMargin.widthAnchor.constraint(equalToConstant: scrollMarginWidth).activate() - + // Add edit button let editButton = UIButton(type: UIButtonType.detailDisclosure) editButton.tintColor = .black @@ -160,7 +160,7 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate editButton.translatesAutoresizingMaskIntoConstraints = false editButton.rightAnchor.constraint(equalTo: canvasContainer.rightAnchor, constant: -8).activate() editButton.bottomAnchor.constraint(equalTo: canvasContainer.bottomAnchor, constant: -8).activate() - + // Add value view valueLabel.numberOfLines = 0 valueLabel.textAlignment = .center @@ -171,22 +171,22 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate valueLabel.rightAnchor.constraint(equalTo: rightAnchor).activate() valueLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8).activate() valueLabel.topAnchor.constraint(equalTo: canvasContainer.bottomAnchor, constant: 16).activate() - + // Render the new value renderValue() } - + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + @objc func manualEdit(sender: UIButton) { // Get the name var name: String = "Manual Edit" if let node = node { name = type(of: node).name } - + // Create the alert let alert = UIAlertController(title: name, message: nil, preferredStyle: .alert) alert.addTextField { textField in @@ -209,28 +209,28 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate )) parentViewController?.present(alert, animated: true) } - + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { // Allow clearing the text view if string.count == 0 { return true } - + // Only filter text fields with decimal pads guard textField.keyboardType == .decimalPad else { return true } - + guard let newString = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) else { print("Failed to get text to filter digits in field.") return true } - + // Find the number of matches let expression = "^([0-9]+)?(\\.([0-9]{1,2})?)?$" let regex = try? NSRegularExpression(pattern: expression, options: .caseInsensitive) let numberOfMatches = regex?.numberOfMatches(in: newString, options: [], range: NSRange(location: 0, length: newString.count)) return numberOfMatches != 0 } - + private func renderValue() { valueLabel.text = value } diff --git a/VPL/Rendering/Node/Content Views/GenericInputView.swift b/VPL/Rendering/Node/Content Views/GenericInputView.swift index e26ef34..2020142 100644 --- a/VPL/Rendering/Node/Content Views/GenericInputView.swift +++ b/VPL/Rendering/Node/Content Views/GenericInputView.swift @@ -10,60 +10,60 @@ import UIKit public class GenericInputViewField: UIView { public let name: String - + public var value: String - + var valueChangeCallback: (() -> Void)? - + private var valueLabel: UILabel! - + public init(name: String, defaultValue: String) { self.name = name self.value = defaultValue - + super.init(frame: CGRect.zero) - + let fieldLabel = UILabel() fieldLabel.textAlignment = .center fieldLabel.text = "\(name):" fieldLabel.textColor = UIColor(white: 0.3, alpha: 1.0) fieldLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize, weight: .bold) addSubview(fieldLabel) - + valueLabel = UILabel() valueLabel.textAlignment = .center valueLabel.text = value valueLabel.font = UIFont.codeFont() valueLabel.numberOfLines = 0 addSubview(valueLabel) - + let pickButton = UIButton(type: .detailDisclosure) addSubview(pickButton) - + // Add constraints fieldLabel.translatesAutoresizingMaskIntoConstraints = false valueLabel.translatesAutoresizingMaskIntoConstraints = false pickButton.translatesAutoresizingMaskIntoConstraints = false - + fieldLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8).activate() fieldLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).activate() fieldLabel.rightAnchor.constraint(equalTo: pickButton.leftAnchor, constant: -8).activate() - + valueLabel.topAnchor.constraint(equalTo: fieldLabel.bottomAnchor, constant: 8).activate() valueLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8).activate() valueLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).activate() valueLabel.rightAnchor.constraint(equalTo: pickButton.leftAnchor, constant: -8).activate() - + pickButton.centerYAnchor.constraint(equalTo: centerYAnchor).activate() pickButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).activate() - + pickButton.addTarget(self, action: #selector(pickTouched(sender:)), for: .touchUpInside) } - + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + @objc func pickTouched(sender: UIButton) { // Create the alert let alert = UIAlertController(title: name, message: nil, preferredStyle: .alert) @@ -86,12 +86,12 @@ public class GenericInputViewField: UIView { alert.addAction(doneAction) parentViewController?.present(alert, animated: true) } - + func set(value: String) { // Update the value self.value = value self.valueLabel.text = value - + // Value change callback self.valueChangeCallback?() } @@ -100,36 +100,36 @@ public class GenericInputViewField: UIView { public class GenericInputView: DisplayableNodeContentView { /// The owning node. weak var node: Node? - + /// The value from the view. public let fields: [GenericInputViewField] - + init(node: Node, fields: [GenericInputViewField]) { self.node = node self.fields = fields - + super.init(frame: CGRect.zero) - + // Set the callbacks for field in fields { field.valueChangeCallback = { self.contentValueChanged() } } - + // Add the views let stackView = UIStackView(arrangedSubviews: fields) stackView.axis = .vertical stackView.distribution = .fill addSubview(stackView) - + stackView.translatesAutoresizingMaskIntoConstraints = false stackView.leftAnchor.constraint(equalTo: leftAnchor).activate() stackView.rightAnchor.constraint(equalTo: rightAnchor).activate() stackView.topAnchor.constraint(equalTo: topAnchor).activate() stackView.bottomAnchor.constraint(equalTo: bottomAnchor).activate() } - + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/VPL/Rendering/Node/Content Views/ValueChooserView.swift b/VPL/Rendering/Node/Content Views/ValueChooserView.swift index 41e858e..6463f6e 100644 --- a/VPL/Rendering/Node/Content Views/ValueChooserView.swift +++ b/VPL/Rendering/Node/Content Views/ValueChooserView.swift @@ -11,19 +11,19 @@ import UIKit public class ValueChooserView: DisplayableNodeContentView { /// The currently selected item. public var value: T - + /// Returns all of the options for this time chooser. var getValues: () -> [T] - + /// Returns a string label for an item. var valueLabel: (T) -> String - + /// Called when an value is chosen. var chooseCallback: (T) -> Void - + private var selectionLabel: UILabel! private var pickButton: UIButton! - + public init( defaultValue: T, getValues: @escaping () -> [T], @@ -34,35 +34,35 @@ public class ValueChooserView: DisplayableNodeContentView { self.getValues = getValues self.valueLabel = valueLabel self.chooseCallback = chooseCallback - + super.init(frame: CGRect.zero) - + selectionLabel = UILabel(frame: CGRect.zero) selectionLabel.text = valueLabel(value) addSubview(selectionLabel) - + pickButton = UIButton(frame: CGRect.zero) pickButton.setTitle("Pick...", for: .normal) addSubview(pickButton) - + // Add constraints (this is ugly af... ew) selectionLabel.translatesAutoresizingMaskIntoConstraints = false pickButton.translatesAutoresizingMaskIntoConstraints = false - + selectionLabel.topAnchor.constraint(equalTo: topAnchor).activate() selectionLabel.bottomAnchor.constraint(equalTo: pickButton.topAnchor).activate() pickButton.bottomAnchor.constraint(equalTo: bottomAnchor).activate() selectionLabel.centerXAnchor.constraint(equalTo: centerXAnchor).activate() pickButton.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - + // Add action to picked pickButton.addTarget(self, action: #selector(pickTouched(sender:)), for: .touchUpInside) } - + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + @objc func pickTouched(sender: UIButton) { // Create the controller let alert = UIAlertController( @@ -70,12 +70,12 @@ public class ValueChooserView: DisplayableNodeContentView { message: nil, preferredStyle: .actionSheet ) - - + + // Configure the popover alert.popoverPresentationController?.sourceView = sender alert.popoverPresentationController?.sourceRect = sender.bounds - + // Display the nodes for value in getValues() { // Create an action to spawn the node @@ -83,17 +83,17 @@ public class ValueChooserView: DisplayableNodeContentView { let action = UIAlertAction(title: label, style: .default) { _ in // Set selected item self.value = value - + // Update label self.selectionLabel.text = label - + // Callbacks self.chooseCallback(value) self.contentValueChanged() } alert.addAction(action) } - + // Present it parentViewController?.present(alert, animated: true) } diff --git a/VPL/Rendering/Node/DisplayNode.swift b/VPL/Rendering/Node/DisplayNode.swift index 0f54221..90c7079 100644 --- a/VPL/Rendering/Node/DisplayNode.swift +++ b/VPL/Rendering/Node/DisplayNode.swift @@ -11,27 +11,27 @@ import UIKit public class DisplayNode: UIView, UIGestureRecognizerDelegate { /// The underlying node data. public let node: DisplayableNode - + /// Canvas that this node is displayed in. weak var canvas: DisplayNodeCanvas? - + /// List of all sockets on the node. var sockets: [DisplayNodeSocket] = [] - + /// The content view for this node. public var contentView: DisplayableNodeContentView? - + public init(node: DisplayableNode) { // Save the node and canvas self.node = node - + super.init(frame: CGRect(x: 0, y: 0, width: 99999, height: 9999)) // Need large frame so the layout can be made - + // Setup the view backgroundColor = UIColor(white: 0.95, alpha: 1.0) layer.cornerRadius = 8 updateShadow(lifted: false) - + // Add label let titleLabel = UILabel(frame: CGRect.zero) titleLabel.textAlignment = .center @@ -41,26 +41,26 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).activate() titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8).activate() - + // Add content view var panelBottomAnchor = bottomAnchor // Anchor to attatch the panels to if let contentView = node.contentView { // Save the view self.contentView = contentView - + // Add the view addSubview(contentView) - + // Size it contentView.translatesAutoresizingMaskIntoConstraints = false contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).activate() contentView.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).activate() contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8).activate() - + // Position it below the panels panelBottomAnchor = contentView.topAnchor } - + // Create panels let leftPanel = UIStackView(frame: CGRect.zero) let rightPanel = UIStackView(frame: CGRect.zero) @@ -72,21 +72,21 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { rightPanel.distribution = .fill addSubview(leftPanel) addSubview(rightPanel) - + leftPanel.translatesAutoresizingMaskIntoConstraints = false leftPanel.widthAnchor.constraint(greaterThanOrEqualToConstant: 20).activate() leftPanel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8).activate() leftPanel.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).activate() leftPanel.bottomAnchor.constraint(lessThanOrEqualTo: panelBottomAnchor, constant: -8).activate() - + rightPanel.translatesAutoresizingMaskIntoConstraints = false rightPanel.widthAnchor.constraint(greaterThanOrEqualToConstant: 20).activate() rightPanel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8).activate() rightPanel.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).activate() rightPanel.bottomAnchor.constraint(lessThanOrEqualTo: panelBottomAnchor, constant: -8).activate() - + leftPanel.rightAnchor.constraint(equalTo: rightPanel.leftAnchor, constant: -8).activate() - + // Add properties if let trigger = node.inputTrigger { addProperty(parent: leftPanel, leftAlign: true, socket: .inputTrigger(trigger), name: "Previous", type: nil) @@ -107,18 +107,18 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { case .none: break } - + // Add drag gesture let dragGesture = UIPanGestureRecognizer(target: self, action: #selector(panned(sender:))) dragGesture.delegate = self addGestureRecognizer(dragGesture) - + // Add remove gesture let removeGesture = UITapGestureRecognizer(target: self, action: #selector(remove(sender:))) removeGesture.numberOfTapsRequired = 2 removeGesture.delegate = self addGestureRecognizer(removeGesture) - + // Add intro effect layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) transform = CGAffineTransform(scaleX: 0, y: 0) @@ -128,13 +128,13 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { self.alpha = 1 } } - + public required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func addProperty(parent: UIStackView, leftAlign: Bool, socket socketType: DisplayNodeSocketType, name: String, type: String?) { - + let view = UIView(frame: CGRect.zero) view.translatesAutoresizingMaskIntoConstraints = false @@ -161,7 +161,7 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { view.addSubview(typeLabel) typeLabel.translatesAutoresizingMaskIntoConstraints = false typeLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).activate() - + // Add constraints to align the views horizontally var alignedViews = [socket, nameLabel, typeLabel] if !leftAlign { @@ -180,7 +180,7 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { parent.addArrangedSubview(view) } - + @objc func panned(sender: UIPanGestureRecognizer) { // Handle movement if sender.state == .began || sender.state == .changed { @@ -188,11 +188,11 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { let translation = sender.translation(in: self) center = CGPoint(x: center.x + translation.x, y: center.y + translation.y) sender.setTranslation(CGPoint.zero, in: self) - + // Notify the canvas the node was updated canvas?.updated(node: self) } - + // Update shadow switch sender.state { case .began: @@ -203,41 +203,41 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { break } } - + @objc func remove(sender: UIPanGestureRecognizer) { // Remove this node canvas?.remove(node: self) } - + public override func layoutSubviews() { // Size to fit content frame.size = systemLayoutSizeFitting(UILayoutFittingCompressedSize) } - + func updateShadow(lifted: Bool) { // Show shadow layer.shadowOpacity = 0.15 - + // Remove previous animations layer.removeAllAnimations() - + // Animate properties let presentation = layer.presentation() - + let scaleAnim = CABasicAnimation(keyPath: "transform") scaleAnim.fromValue = presentation?.transform ?? CATransform3DIdentity scaleAnim.toValue = lifted ? CATransform3DScale(CATransform3DIdentity, 1.05, 1.05, 1.05) : CATransform3DIdentity - + let offsetAnim = CABasicAnimation(keyPath: "shadowOffset") offsetAnim.fromValue = presentation?.shadowOffset ?? CGSize.zero offsetAnim.toValue = lifted ? CGSize(width: 0, height: 25) : CGSize(width: 0, height: 5) - + let shadowAnim = CABasicAnimation(keyPath: "shadowRadius") shadowAnim.fromValue = presentation?.shadowRadius ?? 0 shadowAnim.toValue = lifted ? 30 : 10 - + let groupAnim = CAAnimationGroup() groupAnim.duration = 0.2 groupAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) @@ -246,17 +246,17 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { groupAnim.isRemovedOnCompletion = false layer.add(groupAnim, forKey: "shadowAnim") } - + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { // If the content view absorbs touches, make sure the touch isn't inside if let contentView = contentView, contentView.absorbsTouches { return !contentView.point(inside: touch.location(in: contentView), with: nil) } - + // Otherwise, carry on return true } - + func updateState() { for socket in sockets { socket.updateState() diff --git a/VPL/Rendering/Node/DisplayNodeCanvas.swift b/VPL/Rendering/Node/DisplayNodeCanvas.swift index f686de7..2426385 100644 --- a/VPL/Rendering/Node/DisplayNodeCanvas.swift +++ b/VPL/Rendering/Node/DisplayNodeCanvas.swift @@ -11,13 +11,13 @@ import UIKit public class DisplayNodeCanvas: UIScrollView, UIScrollViewDelegate { /// List of all nodes in the canvas. public private(set) var nodes: [DisplayNode] - + /// View that is drawn behind all other views. var backgroundView: UIView? { didSet { // Remove the old value oldValue?.removeFromSuperview() - + // Add the new vlaue if let backgroundView = backgroundView { addSubview(backgroundView) @@ -25,22 +25,22 @@ public class DisplayNodeCanvas: UIScrollView, UIScrollViewDelegate { } } } - + /// View that overlays the canvas and draws connections between nodes. private var overlayView: DisplayNodeCanvasOverlay! - + /// Called every time the nodes are updated. var updateCallback: (() -> Void)? - + /// The starting node that all other nodes build off of. public private(set) var baseNode: DisplayNode! - + override init(frame: CGRect) { // Create new node list nodes = [] - + super.init(frame: frame) - + // Configure the scroll view to be large & only allow panning with two // touches delegate = self @@ -56,75 +56,75 @@ public class DisplayNodeCanvas: UIScrollView, UIScrollViewDelegate { recognizer.isEnabled = false } } - + // Style the view clipsToBounds = true backgroundColor = .clear - + // Add the overlay overlayView = DisplayNodeCanvasOverlay(frame: bounds, canvas: self) addSubview(overlayView) - + // Create and insert the display node baseNode = DisplayNode(node: BaseNode()) insert(node: baseNode, at: CGPoint(x: contentSize.width / 2, y: contentSize.height / 2)) - + // Scroll to the center contentOffset = CGPoint(x: contentSize.width / 2 - 200, y: contentSize.height / 2 - 200) } - + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + public override func layoutSubviews() { // Resize all views backgroundView?.frame.size = bounds.size overlayView.frame.size = bounds.size } - + public func scrollViewDidScroll(_ scrollView: UIScrollView) { // Move the background and overlay with the view backgroundView?.frame.origin = scrollView.contentOffset overlayView.frame.origin = scrollView.contentOffset - + // Update overlay updateState() } - + /// Assembles all of the code. public func assemble() -> String { var output = "" - + // Assemble each function for node in nodes { if let node = node.node as? BaseNode { output += node.assemble() - + output += "\n\n" } } - + return output } - + /// Adds a node to the canvas. public func insert(node: DisplayNode, at position: CGPoint, absolutePosition: Bool = false) { assert(!nodes.contains(node)) assert(node.canvas == nil) - + // Set the canvas node.canvas = self - + // Add callabck on content change node.node.contentView?.onChangeCallback = { self.updated(node: node) } - + // Insert into the list and view nodes.append(node) addSubview(node) - + // Position the node node.layoutIfNeeded() node.center = position @@ -132,45 +132,45 @@ public class DisplayNodeCanvas: UIScrollView, UIScrollViewDelegate { node.frame.origin.x += contentOffset.x node.frame.origin.y += contentOffset.y } - + // Perform updated updated(node: node) } - + /// Called when any interaction occurs with the node and it needs to be /// updated. public func updated(node: DisplayNode) { // Bring node to front under overlay bringSubview(toFront: node) bringSubview(toFront: overlayView) - + // Update this canvas' state updateState() - + // Update the state node.updateState() - + // Call update updateCallback?() } - + /// Removes a ndoe from the canvas. public func remove(node: DisplayNode) { assert(nodes.contains(node)) assert(node.canvas == self) - + // Make sure the node is destroyable guard type(of: node.node).destroyable else { return } - + // Remove the node from the list guard let nodeIndex = nodes.index(where: { $0 === node }) else { print("Failed to find node in list.") return } nodes.remove(at: nodeIndex) - + // Add destory animation UIView.animate( withDuration: 0.2, @@ -182,15 +182,15 @@ public class DisplayNodeCanvas: UIScrollView, UIScrollViewDelegate { node.removeFromSuperview() } ) - + // Destroy the node node.node.destroy() - + // Update updateState() updateCallback?() } - + /// Creates a connection between sockets based on the current dragging /// position. func finishConnection(socket: DisplayNodeSocket) { @@ -198,7 +198,7 @@ public class DisplayNodeCanvas: UIScrollView, UIScrollViewDelegate { print("No target for socket.") return } - + // Find a socket dislplay that matches the point nodeLoop: for node in nodes { if node.point(inside: node.convert(target, from: socket), with: nil) { @@ -213,18 +213,18 @@ public class DisplayNodeCanvas: UIScrollView, UIScrollViewDelegate { } } } - + // Remove the target socket.draggingTarget = nil - + // Update updateCallback?() } - + func updateState() { // Update overlay overlayView.setNeedsDisplay() - + // This does not notify the child node's state, since that's an // expensive operatino and should rarely update all at once. } diff --git a/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift b/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift index 64b14d0..dab2824 100644 --- a/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift +++ b/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift @@ -10,20 +10,20 @@ import UIKit class DisplayNodeCanvasOverlay: UIView { weak var canvas: DisplayNodeCanvas? - + init(frame: CGRect, canvas: DisplayNodeCanvas) { self.canvas = canvas - + super.init(frame: frame) - + isUserInteractionEnabled = false backgroundColor = .clear } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func draw(_ rect: CGRect) { guard let canvas = canvas else { print("No canvas to draw overlay.") @@ -33,7 +33,7 @@ class DisplayNodeCanvasOverlay: UIView { print("No graphics context.") return } - + // Draw all node connections for node in canvas.nodes { for socket in node.sockets { @@ -64,7 +64,7 @@ class DisplayNodeCanvasOverlay: UIView { } } } - + // Draw socket caps over where the lines meet; this makes it so it // doesn't feel clunky when multiple lines join at the same position for node in canvas.nodes { @@ -77,14 +77,14 @@ class DisplayNodeCanvasOverlay: UIView { } } } - + /// Finds a display node socket that matches a socket type. func findTarget(forSocketType socketType: DisplayNodeSocketType) -> DisplayNodeSocket? { guard let canvas = canvas else { print("Missing canvas.") return nil } - + // Find a socket that matches the target of this view for node in canvas.nodes { for otherSocket in node.sockets { @@ -112,11 +112,11 @@ class DisplayNodeCanvasOverlay: UIView { } } } - + // No match return nil } - + /// Draws a line between two points indicating a socket position func drawSocketConnection(context ctx: CGContext, fromInput: Bool, from: CGPoint, to: CGPoint, color: UIColor, label: String?) { // Draw the line @@ -127,7 +127,7 @@ class DisplayNodeCanvasOverlay: UIView { let controlDistance: CGFloat = 75 * (fromInput ? -1 : 1) ctx.addCurve(to: to, control1: CGPoint(x: from.x + controlDistance, y: from.y), control2: CGPoint(x: to.x - controlDistance, y: to.y)) ctx.strokePath() - + if let label = label { // Get label metrics let lineCenter = CGPoint(x: (from.x + to.x) / 2, y: (from.y + to.y) / 2) @@ -138,7 +138,7 @@ class DisplayNodeCanvasOverlay: UIView { NSAttributedStringKey.paragraphStyle: paragraphStyle ] let size = (label as NSString).size(withAttributes: attributes) - + // Draw a shape behind the label var paddedSize = CGSize(width: size.width, height: size.height + 8) paddedSize.width += paddedSize.height / 2 // Make the caps go beyond the text @@ -151,7 +151,7 @@ class DisplayNodeCanvasOverlay: UIView { ) ctx.setFillColor(gray: 1.0, alpha: 0.7) roundedRect.fill() - + // Draw the label (label as NSString).draw( in: CGRect(x: lineCenter.x - size.width / 2, y: lineCenter.y - size.height / 2, width: size.width, height: size.height), @@ -159,7 +159,7 @@ class DisplayNodeCanvasOverlay: UIView { ) } } - + /// Draws a cap over the socket. func drawSocketCap(context ctx: CGContext, center: CGPoint, color: UIColor) { // Draw circle over the cap of the base node @@ -171,7 +171,7 @@ class DisplayNodeCanvasOverlay: UIView { ) ctx.fillPath() } - + override func layoutSubviews() { // setNeedsLayout() } diff --git a/VPL/Rendering/Node/DisplayNodeSocket.swift b/VPL/Rendering/Node/DisplayNodeSocket.swift index 9fe5dbe..adbbae5 100644 --- a/VPL/Rendering/Node/DisplayNodeSocket.swift +++ b/VPL/Rendering/Node/DisplayNodeSocket.swift @@ -12,7 +12,7 @@ enum DisplayNodeSocketType: Equatable { case inputTrigger(InputTrigger), outputTrigger(OutputTrigger) case inputValue(InputValue), outputValue(OutputValue) case inputVariable(InputVariable) - + var socketColor: UIColor { switch self { case .inputTrigger(_), .outputTrigger(_): @@ -23,7 +23,7 @@ enum DisplayNodeSocketType: Equatable { return UIColor(red: 0.12, green: 1, blue: 0.59, alpha: 1.0) } } - + var connectionColor: UIColor { switch self { case .inputTrigger(_), .outputTrigger(_): @@ -34,7 +34,7 @@ enum DisplayNodeSocketType: Equatable { return UIColor(red: 0.44, green: 1, blue: 0.74, alpha: 0.35) } } - + var isInput: Bool { switch self { case .inputTrigger(_), .inputValue(_), .inputVariable(_): @@ -43,7 +43,7 @@ enum DisplayNodeSocketType: Equatable { return false } } - + var isConnected: Bool { switch self { case .inputTrigger(let trigger): @@ -58,7 +58,7 @@ enum DisplayNodeSocketType: Equatable { return variable.target != nil } } - + static func == (lhs: DisplayNodeSocketType, rhs: DisplayNodeSocketType) -> Bool { switch lhs { case .inputTrigger(let lhsTrigger): @@ -82,33 +82,33 @@ enum DisplayNodeSocketType: Equatable { return lhsVariable === rhsVariable } } - + return false } } class DisplayNodeSocket: UIView { var type: DisplayNodeSocketType - + weak var node: DisplayNode? - + var draggingTarget: CGPoint? - + var shapeView: UIView = UIView() - + var variablesText: UILabel? - + var triangleShape: CAShapeLayer! - + init(frame: CGRect, type: DisplayNodeSocketType, node: DisplayNode) { self.type = type self.node = node - + super.init(frame: frame) - + // Style the view backgroundColor = .clear - + // Add the shape view shapeView.backgroundColor = type.socketColor addSubview(shapeView) @@ -117,10 +117,10 @@ class DisplayNodeSocket: UIView { shapeView.centerYAnchor.constraint(equalTo: centerYAnchor).activate() shapeView.widthAnchor.constraint(equalToConstant: 22).activate() shapeView.heightAnchor.constraint(equalTo: shapeView.widthAnchor).activate() - + // Add a triangle self.triangleShape = addTriangle(size: CGSize(width: 6, height: 8)) - + // Add variables text if case let .outputTrigger(trigger) = type, trigger.exposedVariables.count > 0 { let variablesText = UILabel() @@ -136,19 +136,19 @@ class DisplayNodeSocket: UIView { variablesText.rightAnchor.constraint(equalTo: rightAnchor).activate() variablesText.bottomAnchor.constraint(equalTo: bottomAnchor).activate() } - + // Add drag gesture let dragGesture = UIPanGestureRecognizer(target: self, action: #selector(panned(sender:))) addGestureRecognizer(dragGesture) - + // Update state updateState() } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + /// If this socket can be connected to another socket. func canConnectTo(socket other: DisplayNodeSocket) -> Bool { switch type { @@ -177,10 +177,10 @@ class DisplayNodeSocket: UIView { } } } - + return false } - + /// Connects this socket to another socket. func connect(to other: DisplayNodeSocket) { // Set the connection. @@ -204,12 +204,12 @@ class DisplayNodeSocket: UIView { case .inputVariable(_): promptVariableConnectino(to: other) } - + // Update the socket updateState() other.updateState() } - + func promptVariableConnectino(to other: DisplayNodeSocket) { guard case let .inputVariable(variable) = type else { print("Cannot propt for variable connection on non-variable types.") @@ -218,20 +218,20 @@ class DisplayNodeSocket: UIView { guard case let .outputTrigger(trigger) = other.type else { return } - + // Create the controller let alert = UIAlertController( title: "Spawn Node", message: nil, preferredStyle: .actionSheet ) - - + + // Configure the popover alert.popoverPresentationController?.sourceView = other alert.popoverPresentationController?.sourceRect = other.bounds alert.popoverPresentationController?.permittedArrowDirections = .left - + // Display the nodes for otherVariable in trigger.exposedVariables { // Make sure the variable can be connected to; this may mean there @@ -240,28 +240,28 @@ class DisplayNodeSocket: UIView { guard variable.canConnect(to: otherVariable) else { continue } - + // Create an action to spawn the node let label = "\(otherVariable.name) (\(otherVariable.type.description))" let action = UIAlertAction(title: label, style: .default) { _ in // Connect the values variable.connect(to: otherVariable) - + // Update the socket self.updateState() other.updateState() - + // Force update the canvas state, since it doesn't know about // this self.node?.canvas?.updateState() } alert.addAction(action) } - + // Present it parentViewController?.present(alert, animated: true) } - + /// Label that will be drawn on the connection. func connectionLabel() -> String? { switch type { @@ -275,13 +275,13 @@ class DisplayNodeSocket: UIView { return nil } } - + @objc func panned(sender: UIPanGestureRecognizer) { guard let node = node, let canvas = node.canvas else { print("Missing node or canvas for socket.") return } - + // Remove the target switch type { case .inputTrigger(let trigger): trigger.reset() @@ -290,7 +290,7 @@ class DisplayNodeSocket: UIView { case .outputValue(let value): value.reset() case .inputVariable(let variable): variable.reset() } - + // Update the dragging to position if sender.state == .began || sender.state == .changed { // Set the translation in this view; otherwise, it will start at 0 @@ -298,34 +298,34 @@ class DisplayNodeSocket: UIView { if draggingTarget == nil { sender.setTranslation(sender.location(ofTouch: 0, in: self), in: self) } - + // Save the translation draggingTarget = sender.translation(in: self) } else { // Finish the connection canvas.finishConnection(socket: self) - + // Remove the target draggingTarget = nil } - + // Notify updates updateState() canvas.updated(node: node) } - + func updateState() { let isConnected = self.type.isConnected || self.draggingTarget != nil - + // Update shape layer UIView.animate(withDuration: 0.2) { self.shapeView.layer.cornerRadius = isConnected ? self.shapeView.frame.width / 2 : 8 } - + // Hide/show triangle triangleShape.isHidden = isConnected } - + func addTriangle(size: CGSize) -> CAShapeLayer { // Create the path let path = CGMutablePath() @@ -333,21 +333,21 @@ class DisplayNodeSocket: UIView { path.addLine(to: CGPoint(x: 0, y: size.height)) path.addLine(to: CGPoint(x: 0, y: 0)) path.addLine(to: CGPoint(x: size.width, y: size.height / 2)) - + // Create a shape let shape = CAShapeLayer() shape.frame = CGRect(origin: CGPoint.zero, size: size) shape.path = path shape.fillColor = UIColor(white: 0, alpha: 0.2).cgColor - + layer.insertSublayer(shape, at: 0) - + return shape } - + override func layoutSublayers(of layer: CALayer) { super.layoutSublayers(of: layer) - + // Determine if input var isInput: Bool switch type { @@ -356,7 +356,7 @@ class DisplayNodeSocket: UIView { case .outputTrigger(_), .outputValue(_): isInput = false } - + // Reposition triangle shape if isInput { triangleShape.frame.origin = CGPoint( diff --git a/VPL/Rendering/Node/Implementations/ArrayNodes.swift b/VPL/Rendering/Node/Implementations/ArrayNodes.swift index 4566182..a01d10c 100644 --- a/VPL/Rendering/Node/Implementations/ArrayNodes.swift +++ b/VPL/Rendering/Node/Implementations/ArrayNodes.swift @@ -9,29 +9,29 @@ import Foundation public class ArrayCreateNode: DisplayableNode { public static let shortcutCharacter: String? = "A" - + public static let id: String = "array-create" public static let name: String = "Create Array" public let output: NodeOutput = .value(OutputValue(type: .array(.unknown))) public var contentView: DisplayableNodeContentView? { return input } - + var input: GenericInputView! - + public required init() { input = GenericInputView(node: self, fields: [ GenericInputViewField(name: "Value Type", defaultValue: "Int") ]) - + self.setupConnections() } - + public func assemble() -> String { return "[\(input.fields[0].value)]()" } } public class ArrayAppendNode: DisplayableNode { public static let shortcutCharacter: String? = "A" - + public static let id: String = "array-appent" public static let name: String = "Append to Array" public let inputTrigger: InputTrigger? = InputTrigger() @@ -40,11 +40,11 @@ public class ArrayAppendNode: DisplayableNode { InputValue(id: "value", name: "Value", type: .unknown) ] public let output: NodeOutput = .triggers([OutputTrigger()]) - + public required init() { self.setupConnections() } - + public func assemble() -> String { var out = "" out !+= "\(inputValues[0].assemble()).append(\(inputValues[1].assemble()))" @@ -53,7 +53,7 @@ public class ArrayAppendNode: DisplayableNode { } public class ArraySetAtNode: DisplayableNode { public static let shortcutCharacter: String? = "A" - + public static let id: String = "array-set-at" public static let name: String = "Set At Index" public let inputTrigger: InputTrigger? = InputTrigger() @@ -63,11 +63,11 @@ public class ArraySetAtNode: DisplayableNode { InputValue(id: "value", name: "Value", type: .unknown) ] public let output: NodeOutput = .triggers([OutputTrigger()]) - + public required init() { self.setupConnections() } - + public func assemble() -> String { var out = "" out !+= "\(inputValues[0].assemble())[\(inputValues[1].assemble())] = \(inputValues[2].assemble())" @@ -76,7 +76,7 @@ public class ArraySetAtNode: DisplayableNode { } public class ArrayGetAtNode: DisplayableNode { public static let shortcutCharacter: String? = "A" - + public static let id: String = "array-get-at" public static let name: String = "Get At Index" public let inputValues: [InputValue] = [ @@ -84,18 +84,18 @@ public class ArrayGetAtNode: DisplayableNode { InputValue(id: "index", name: "Index", type: .int) ] public let output: NodeOutput = .value(OutputValue(type: .unknown)) - + public required init() { self.setupConnections() } - + public func assemble() -> String { return "\(inputValues[0].assemble())[\(inputValues[1].assemble())]" } } public class ArrayRemoveAtNode: DisplayableNode { public static let shortcutCharacter: String? = "A" - + public static let id: String = "array-remove-at" public static let name: String = "Remove At Index" public let inputTrigger: InputTrigger? = InputTrigger() @@ -104,11 +104,11 @@ public class ArrayRemoveAtNode: DisplayableNode { InputValue(id: "index", name: "Index", type: .int) ] public let output: NodeOutput = .triggers([OutputTrigger()]) - + public required init() { self.setupConnections() } - + public func assemble() -> String { var out = "" out !+= "\(inputValues[0].assemble()).remove(at: \(inputValues[1].assemble()))" @@ -117,16 +117,16 @@ public class ArrayRemoveAtNode: DisplayableNode { } public class ArrayCountNode: DisplayableNode { public static let shortcutCharacter: String? = "A" - + public static let id: String = "array-count" public static let name: String = "Value Count" public let inputValues: [InputValue] = [InputValue(id: "array", name: "Array", type: .array(.unknown))] public let output: NodeOutput = .value(OutputValue(type: .int)) - + public required init() { self.setupConnections() } - + public func assemble() -> String { return "\(inputValues[0].assemble()).count" } diff --git a/VPL/Rendering/Node/Implementations/BaseNode.swift b/VPL/Rendering/Node/Implementations/BaseNode.swift index 47b6e56..520ec93 100644 --- a/VPL/Rendering/Node/Implementations/BaseNode.swift +++ b/VPL/Rendering/Node/Implementations/BaseNode.swift @@ -10,15 +10,15 @@ import UIKit class BaseNode: DisplayableNode { static let destroyable: Bool = false - + static let id: String = "start" static let name: String = "Start" var output: NodeOutput = .triggers([OutputTrigger()]) - + required init() { self.setupConnections() } - + func assemble() -> String { return assembleOutputTrigger() } diff --git a/VPL/Rendering/Node/Implementations/ConstNodes.swift b/VPL/Rendering/Node/Implementations/ConstNodes.swift index 75d310b..84880fc 100644 --- a/VPL/Rendering/Node/Implementations/ConstNodes.swift +++ b/VPL/Rendering/Node/Implementations/ConstNodes.swift @@ -10,42 +10,42 @@ import UIKit public class EvalConstNode: DisplayableNode { public static let shortcutCharacter: String? = "C" - + public static let id: String = "eval-const" public static let name: String = "Eval Constant" public var output: NodeOutput = .value(OutputValue(type: .unknown)) public var contentView: DisplayableNodeContentView? { return inputView } - + var inputView: GenericInputView! - + public required init() { inputView = GenericInputView(node: self, fields: [ GenericInputViewField(name: "Swift Code", defaultValue: "nil") ]) - + self.setupConnections() } - + public func assemble() -> String { return "(\(inputView.fields[0].value))" } } public class IntConstNode: DisplayableNode { public static let shortcutCharacter: String? = "C" - + public static let id: String = "int-const" public static let name: String = "Integer Constant" public let output: NodeOutput = .value(OutputValue(type: .int)) public var contentView: DisplayableNodeContentView? { return inputView } - + var inputView: DrawCanvasNodeView! - + public required init() { inputView = DrawCanvasNodeView(node: self, defaultValue: "0", inputType: .digits) - + self.setupConnections() } - + public func assemble() -> String { var rawValue = inputView.value rawValue = rawValue.split(separator: ".").first.map { String($0) } ?? "" // Remove decimal @@ -60,20 +60,20 @@ public class IntConstNode: DisplayableNode { } public class StringConstNode: DisplayableNode { public static let shortcutCharacter: String? = "C" - + public static let id: String = "str-const" public static let name: String = "String Constant" public let output: NodeOutput = .value(OutputValue(type: .string)) public var contentView: DisplayableNodeContentView? { return inputView } - + var inputView: DrawCanvasNodeView! - + public required init() { inputView = DrawCanvasNodeView(node: self, defaultValue: "", inputType: .alphanum) - + self.setupConnections() } - + public func assemble() -> String { var escapedValue = inputView.value escapedValue = escapedValue.replacingOccurrences(of: "\\", with: "\\\\") diff --git a/VPL/Rendering/Node/Implementations/ControlFlowNodes.swift b/VPL/Rendering/Node/Implementations/ControlFlowNodes.swift index 7e078b2..f547b20 100644 --- a/VPL/Rendering/Node/Implementations/ControlFlowNodes.swift +++ b/VPL/Rendering/Node/Implementations/ControlFlowNodes.swift @@ -9,17 +9,17 @@ import UIKit public class IfNode: DisplayableNode { public static let shortcutCharacter: String? = "I" - + public static let id: String = "if" public static let name: String = "If" public let inputTrigger: InputTrigger? = InputTrigger() public let inputValues: [InputValue] = [InputValue(id: "condition", name: "Condition", type: .bool)] public let output: NodeOutput = .triggers([OutputTrigger(), OutputTrigger(id: "true", name: "True"), OutputTrigger(id: "false", name: "False")]) - + public required init() { self.setupConnections() } - + public func assemble() -> String { var out = "" out !+= "if \(inputValues[0].assemble()) {" @@ -32,7 +32,7 @@ public class IfNode: DisplayableNode { } public class ForLoopNode: DisplayableNode { public static let shortcutCharacter: String? = "F" - + public static let id: String = "for" public static let name: String = "For Loop" public let inputTrigger: InputTrigger? = InputTrigger() @@ -41,7 +41,7 @@ public class ForLoopNode: DisplayableNode { OutputTrigger(), OutputTrigger(id: "loop", name: "Loop", exposedVariables: [NodeVariable(name: "Index", type: .int)]) ]) - + var indexVariable: NodeVariable { if case let .triggers(triggers) = output { return triggers[1].exposedVariables[0] @@ -49,11 +49,11 @@ public class ForLoopNode: DisplayableNode { fatalError("Missing exposed variable.") } } - + public required init() { self.setupConnections() } - + public func assemble() -> String { var out = "" out !+= "for \(indexVariable.id) in (\(inputValues[0].assemble()))..<(\(inputValues[1].assemble())) {" @@ -61,6 +61,6 @@ public class ForLoopNode: DisplayableNode { out !+= "}" return out + assembleOutputTrigger() } - - + + } diff --git a/VPL/Rendering/Node/Implementations/DictionaryNodes.swift b/VPL/Rendering/Node/Implementations/DictionaryNodes.swift index 0158a73..76160a3 100644 --- a/VPL/Rendering/Node/Implementations/DictionaryNodes.swift +++ b/VPL/Rendering/Node/Implementations/DictionaryNodes.swift @@ -9,30 +9,30 @@ import UIKit public class DictionaryCreateNode: DisplayableNode { public static let shortcutCharacter: String? = "D" - + public static let id: String = "dict-create" public static let name: String = "Create Dictionary" public let output: NodeOutput = .value(OutputValue(type: .dictionary(.unknown, .unknown))) public var contentView: DisplayableNodeContentView? { return input } - + var input: GenericInputView! - + public required init() { input = GenericInputView(node: self, fields: [ GenericInputViewField(name: "Key Type", defaultValue: "String"), GenericInputViewField(name: "Value Type", defaultValue: "Int") ]) - + self.setupConnections() } - + public func assemble() -> String { return "[\(input.fields[0].value) : \(input.fields[1].value)]()" } } public class DictionarySetAtNode: DisplayableNode { public static let shortcutCharacter: String? = "D" - + public static let id: String = "dict-set-at" public static let name: String = "Set At Key" public let inputTrigger: InputTrigger? = InputTrigger() @@ -42,11 +42,11 @@ public class DictionarySetAtNode: DisplayableNode { InputValue(id: "value", name: "Value", type: .unknown) ] public let output: NodeOutput = .triggers([OutputTrigger()]) - + public required init() { self.setupConnections() } - + public func assemble() -> String { var out = "" out !+= "\(inputValues[0].assemble())[\(inputValues[1].assemble())] = \(inputValues[2].assemble())" @@ -55,7 +55,7 @@ public class DictionarySetAtNode: DisplayableNode { } public class DictionaryGetAtNode: DisplayableNode { public static let shortcutCharacter: String? = "D" - + public static let id: String = "dict-get-at" public static let name: String = "Get At Key" public let inputValues: [InputValue] = [ @@ -63,18 +63,18 @@ public class DictionaryGetAtNode: DisplayableNode { InputValue(id: "key", name: "Key", type: .unknown) ] public let output: NodeOutput = .value(OutputValue(type: .unknown)) - + public required init() { self.setupConnections() } - + public func assemble() -> String { return "\(inputValues[0].assemble())[\(inputValues[1].assemble())]!" } } public class DictionaryContainsKeyNode: DisplayableNode { public static let shortcutCharacter: String? = "D" - + public static let id: String = "dict-get-at" public static let name: String = "Contains Key" public let inputValues: [InputValue] = [ @@ -82,18 +82,18 @@ public class DictionaryContainsKeyNode: DisplayableNode { InputValue(id: "key", name: "Key", type: .unknown) ] public let output: NodeOutput = .value(OutputValue(type: .bool)) - + public required init() { self.setupConnections() } - + public func assemble() -> String { return "(\(inputValues[0].assemble())[\(inputValues[1].assemble())] != nil)" } } public class DictionaryRemoveAtNode: DisplayableNode { public static let shortcutCharacter: String? = "D" - + public static let id: String = "dict-remove-at" public static let name: String = "Remove At Key" public let inputTrigger: InputTrigger? = InputTrigger() @@ -102,11 +102,11 @@ public class DictionaryRemoveAtNode: DisplayableNode { InputValue(id: "key", name: "Key", type: .int) ] public let output: NodeOutput = .triggers([OutputTrigger()]) - + public required init() { self.setupConnections() } - + public func assemble() -> String { var out = "" out !+= "\(inputValues[0].assemble()).remove(at: \(inputValues[1].assemble()))" diff --git a/VPL/Rendering/Node/Implementations/DisplayableNode.swift b/VPL/Rendering/Node/Implementations/DisplayableNode.swift index bd155f2..a589bca 100644 --- a/VPL/Rendering/Node/Implementations/DisplayableNode.swift +++ b/VPL/Rendering/Node/Implementations/DisplayableNode.swift @@ -12,13 +12,13 @@ public let defaultNodes: [DisplayableNode.Type] = [ EvalConstNode.self, IntConstNode.self, StringConstNode.self, - + DeclareVariableNode.self, SetVariableNode.self, GetVariableNode.self, IfNode.self, ForLoopNode.self, - + AddNode.self, SubtractNode.self, MultiplyNode.self, @@ -27,20 +27,20 @@ public let defaultNodes: [DisplayableNode.Type] = [ RandomIntNode.self, RandomFloatNode.self, EqualsNode.self, - + ArrayCreateNode.self, ArrayAppendNode.self, ArraySetAtNode.self, ArrayGetAtNode.self, ArrayRemoveAtNode.self, ArrayCountNode.self, - + DictionaryCreateNode.self, DictionarySetAtNode.self, DictionaryGetAtNode.self, DictionaryContainsKeyNode.self, DictionaryRemoveAtNode.self, - + PrintNode.self, SwapNode.self ] @@ -48,10 +48,10 @@ public let defaultNodes: [DisplayableNode.Type] = [ public protocol DisplayableNode: Node { /// The character that can be drawn to spawn this node. static var shortcutCharacter: String? { get } - + /// If the node is deletable. static var destroyable: Bool { get } - + /// View that can be used to represent the view's interactable content. This /// allows for things like constant nodes to have dynamic content. var contentView: DisplayableNodeContentView? { get } @@ -59,8 +59,8 @@ public protocol DisplayableNode: Node { extension DisplayableNode { public static var shortcutCharacter: String? { return nil } - + public static var destroyable: Bool { return true } - + public var contentView: DisplayableNodeContentView? { return nil } } diff --git a/VPL/Rendering/Node/Implementations/MathNodes.swift b/VPL/Rendering/Node/Implementations/MathNodes.swift index 666c5f8..e2c6a59 100644 --- a/VPL/Rendering/Node/Implementations/MathNodes.swift +++ b/VPL/Rendering/Node/Implementations/MathNodes.swift @@ -9,20 +9,20 @@ import UIKit public class MathNode: DisplayableNode { public static let shortcutCharacter: String? = "M" - + public class var id: String { fatalError("Unimplemented.") } public class var name: String { fatalError("Unimplemented.") } public let inputValues: [InputValue] = [InputValue(id: "a", name: "A", type: .int), InputValue(id: "b", name: "B", type: .int)] public let output: NodeOutput = .value(OutputValue(type: .int)) - + var inputA: InputValue { return inputValues[0] } - + var inputB: InputValue { return inputValues[1] } - + public required init() { self.setupConnections() } - + public func assemble() -> String { fatalError("Unimplemented.") } @@ -30,7 +30,7 @@ public class MathNode: DisplayableNode { public class AddNode: MathNode { public override class var id: String { return "add" } public override class var name: String { return "Add" } - + public override func assemble() -> String { return "(\(inputA.assemble()) + \(inputB.assemble()))" } @@ -38,7 +38,7 @@ public class AddNode: MathNode { public class SubtractNode: MathNode { public override class var id: String { return "subtract" } public override class var name: String { return "Subtract" } - + public override func assemble() -> String { return "(\(inputA.assemble()) - \(inputB.assemble()))" } @@ -46,7 +46,7 @@ public class SubtractNode: MathNode { public class MultiplyNode: MathNode { public override class var id: String { return "multiply" } public override class var name: String { return "Multiply" } - + public override func assemble() -> String { return "(\(inputA.assemble()) * \(inputB.assemble()))" } @@ -54,7 +54,7 @@ public class MultiplyNode: MathNode { public class DivideNode: MathNode { public override class var id: String { return "divide" } public override class var name: String { return "Divide" } - + public override func assemble() -> String { return "(\(inputA.assemble()) / \(inputB.assemble()))" } @@ -62,7 +62,7 @@ public class DivideNode: MathNode { public class ModuloNode: MathNode { public override class var id: String { return "modulo" } public override class var name: String { return "Modulo" } - + public override func assemble() -> String { return "(\(inputA.assemble()) % \(inputB.assemble()))" } @@ -70,15 +70,15 @@ public class ModuloNode: MathNode { public class RandomIntNode: DisplayableNode { public static let shortcutCharacter: String? = "M" - + public static let id: String = "random-in" public static let name: String = "Random Integer" public let output: NodeOutput = .value(OutputValue(type: .int)) - + public required init() { self.setupConnections() } - + public func assemble() -> String { return "(arc4random() as Int)" } @@ -86,15 +86,15 @@ public class RandomIntNode: DisplayableNode { public class RandomFloatNode: DisplayableNode { public static let shortcutCharacter: String? = "M" - + public static let id: String = "random-float" public static let name: String = "Random Float" public let output: NodeOutput = .value(OutputValue(type: .float)) - + public required init() { self.setupConnections() } - + public func assemble() -> String { return "(Float(arc4random()) / Float(UINT32_MAX))" } @@ -102,16 +102,16 @@ public class RandomFloatNode: DisplayableNode { public class EqualsNode: DisplayableNode { public static let shortcutCharacter: String? = "E" - + public static let id: String = "equals" public static let name: String = "Equals" public let inputValues: [InputValue] = [InputValue(id: "a", name: "A", type: .int), InputValue(id: "b", name: "B", type: .int)] public let output: NodeOutput = .value(OutputValue(type: .bool)) - + public required init() { self.setupConnections() } - + public func assemble() -> String { let assembledInputA = inputValues[0].assemble() let assembledInputB = inputValues[1].assemble() diff --git a/VPL/Rendering/Node/Implementations/MiscNodes.swift b/VPL/Rendering/Node/Implementations/MiscNodes.swift index 9293eee..7e1247a 100644 --- a/VPL/Rendering/Node/Implementations/MiscNodes.swift +++ b/VPL/Rendering/Node/Implementations/MiscNodes.swift @@ -9,17 +9,17 @@ import UIKit public class PrintNode: DisplayableNode { public static let shortcutCharacter: String? = "P" - + public static let id: String = "print" public static let name: String = "Print" public let inputTrigger: InputTrigger? = InputTrigger() public let inputValues: [InputValue] = [InputValue(id: "value", name: "Value", type: .unknown)] public let output: NodeOutput = .triggers([OutputTrigger()]) - + public required init() { self.setupConnections() } - + public func assemble() -> String { var out = "" out !+= "print(\(inputValues[0].assemble()))" @@ -32,11 +32,11 @@ public class SwapNode: DisplayableNode { public let inputTrigger: InputTrigger? = InputTrigger() public let inputVariables: [InputVariable] = [InputVariable(id: "a", name: "A", type: .unknown), InputVariable(id: "b", name: "B", type: .unknown)] public let output: NodeOutput = .triggers([OutputTrigger()]) - + public required init() { self.setupConnections() } - + public func assemble() -> String { let tmpVariableId = NodeVariable.variableId var out = "" diff --git a/VPL/Rendering/Node/Implementations/VariableNodes.swift b/VPL/Rendering/Node/Implementations/VariableNodes.swift index 3c1f382..8ddff50 100644 --- a/VPL/Rendering/Node/Implementations/VariableNodes.swift +++ b/VPL/Rendering/Node/Implementations/VariableNodes.swift @@ -10,7 +10,7 @@ import UIKit public class DeclareVariableNode: DisplayableNode { public static let shortcutCharacter: String? = "S" - + public static let id: String = "declare variable" public static let name: String = "Declare Variable" public let inputTrigger: InputTrigger? = InputTrigger() @@ -18,7 +18,7 @@ public class DeclareVariableNode: DisplayableNode { public let output: NodeOutput = .triggers([ OutputTrigger(exposedVariables: [ NodeVariable(name: "Variable", type: .unknown) ]) ]) - + var variable: NodeVariable { if case let .triggers(triggers) = output { return triggers[0].exposedVariables[0] @@ -26,11 +26,11 @@ public class DeclareVariableNode: DisplayableNode { fatalError("Missing exposed variable.") } } - + public required init() { self.setupConnections() } - + public func assemble() -> String { var out = "" out !+= "var \(variable.id) = \(inputValues[0].assemble())" @@ -39,18 +39,18 @@ public class DeclareVariableNode: DisplayableNode { } public class SetVariableNode: DisplayableNode { public static let shortcutCharacter: String? = "S" - + public static let id: String = "set variable" public static let name: String = "Set Variable" public let inputTrigger: InputTrigger? = InputTrigger() public let inputValues: [InputValue] = [ InputValue(id: "set value", name: "Set Value", type: .unknown) ] public let inputVariables: [InputVariable] = [ InputVariable(id: "target", name: "Target", type: .unknown) ] public let output: NodeOutput = .triggers([OutputTrigger()]) - + public required init() { self.setupConnections() } - + public func assemble() -> String { let assembledInput = inputValues[0].assemble() let out = "\(inputVariables[0].target?.id ?? "NO SELECTED VARIABLE") = \(assembledInput)\n" @@ -59,16 +59,16 @@ public class SetVariableNode: DisplayableNode { } public class GetVariableNode: DisplayableNode { public static let shortcutCharacter: String? = "V" - + public static let id: String = "get variable" public static let name: String = "Get Variable" public let inputVariables: [InputVariable] = [ InputVariable(id: "target", name: "Target", type: .unknown) ] public let output: NodeOutput = .value(OutputValue(type: .unknown)) - + public required init() { self.setupConnections() } - + public func assemble() -> String { return "(\(inputVariables[0].target?.id ?? "NO SELECTED VARIABLE"))" } diff --git a/VPL/Utils.swift b/VPL/Utils.swift index 7b547a7..e17ec7d 100644 --- a/VPL/Utils.swift +++ b/VPL/Utils.swift @@ -18,14 +18,14 @@ extension DisplayNodeCanvas { public func insert(node: DisplayableNode, base: DisplayNode, offset: CGPoint) -> DisplayNode { // Create the ndoe let node = DisplayNode(node: node) - + // Insert the node insert( node: node, at: CGPoint(x: base.center.x + offset.x, y: base.center.y + offset.y), absolutePosition: true ) - + return node } } @@ -49,7 +49,7 @@ extension NSLayoutConstraint { self.isActive = true return self } - + @discardableResult func setPriority(_ priority: UILayoutPriority) -> Self { self.priority = priority From 981f30bc470872d64b8be44f03833902b2c42bb9 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 12:42:19 -0700 Subject: [PATCH 02/18] * refactoring --- VPL/OCR/DrawingCanvas.swift | 4 ++-- VPL/OCR/OCRRequest.swift | 2 +- VPL/Rendering/CanvasViewController.swift | 2 +- VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/VPL/OCR/DrawingCanvas.swift b/VPL/OCR/DrawingCanvas.swift index e53976a..e6b4cf4 100644 --- a/VPL/OCR/DrawingCanvas.swift +++ b/VPL/OCR/DrawingCanvas.swift @@ -143,8 +143,8 @@ class DrawingCanvas: UIView { context.clear(bounds) // Draw the images into the new context - imageView.image?.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height), blendMode: .normal, alpha: 1.0) - tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height), blendMode: .normal, alpha: 1.0) + imageView.image?.draw(in: CGRect(origin: .zero, size: frame.size), blendMode: .normal, alpha: 1.0) + tempImageView.image?.draw(in: CGRect(origin: .zero, size: frame.size), blendMode: .normal, alpha: 1.0) // Assign the image imageView.image = UIGraphicsGetImageFromCurrentImageContext() diff --git a/VPL/OCR/OCRRequest.swift b/VPL/OCR/OCRRequest.swift index db065d2..5460205 100644 --- a/VPL/OCR/OCRRequest.swift +++ b/VPL/OCR/OCRRequest.swift @@ -106,7 +106,7 @@ class OCRRequest { } else { // Start the request let handler = VNImageRequestHandler(cgImage: convertedImage.cgImage!) - let request: VNDetectTextRectanglesRequest = VNDetectTextRectanglesRequest(completionHandler: detectTextHandler) + let request = VNDetectTextRectanglesRequest(completionHandler: detectTextHandler) request.reportCharacterBoxes = true try handler.perform([request]) } diff --git a/VPL/Rendering/CanvasViewController.swift b/VPL/Rendering/CanvasViewController.swift index 176c3d4..94ec67e 100644 --- a/VPL/Rendering/CanvasViewController.swift +++ b/VPL/Rendering/CanvasViewController.swift @@ -116,7 +116,7 @@ public class CanvasViewController: UIViewController { } } } - RunLoop.main.add(timer, forMode: RunLoopMode.defaultRunLoopMode) + RunLoop.main.add(timer, forMode: .defaultRunLoopMode) self.commitDrawingTimer = timer } diff --git a/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift b/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift index 903a1b1..794abd1 100644 --- a/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift +++ b/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift @@ -137,7 +137,7 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate self.value = result } } - RunLoop.main.add(timer, forMode: RunLoopMode.defaultRunLoopMode) + RunLoop.main.add(timer, forMode: .defaultRunLoopMode) self.commitDrawingTimer = timer } @@ -153,7 +153,7 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate scrollMargin.widthAnchor.constraint(equalToConstant: scrollMarginWidth).activate() // Add edit button - let editButton = UIButton(type: UIButtonType.detailDisclosure) + let editButton = UIButton(type: .detailDisclosure) editButton.tintColor = .black editButton.addTarget(self, action: #selector(manualEdit(sender:)), for: .touchUpInside) addSubview(editButton) From 48471a2c2c6d18fac9b0637bae573bcdee477f80 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 12:59:00 -0700 Subject: [PATCH 03/18] * refactoring --- VPL/OCR/ImageUtils.swift | 4 +-- VPL/Rendering/CodeOutputView.swift | 8 +++--- .../Content Views/DrawCanvasNodeView.swift | 21 ++++++---------- .../Node/Content Views/GenericInputView.swift | 4 +-- .../Node/Content Views/ValueChooserView.swift | 6 ++--- VPL/Rendering/Node/DisplayNode.swift | 25 +++++++++---------- VPL/Utils.swift | 4 +++ 7 files changed, 35 insertions(+), 37 deletions(-) diff --git a/VPL/OCR/ImageUtils.swift b/VPL/OCR/ImageUtils.swift index 6fb532e..2a80b00 100755 --- a/VPL/OCR/ImageUtils.swift +++ b/VPL/OCR/ImageUtils.swift @@ -108,7 +108,7 @@ func adjustColors(image: UIImage) -> UIImage { } func fixOrientation(image: UIImage) -> UIImage { - if image.imageOrientation == UIImageOrientation.up { + if image.imageOrientation == .up { return image } UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale) @@ -264,7 +264,7 @@ extension UIImage { guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo), let ptr = context.data?.assumingMemoryBound(to: UInt8.self) else { - return CGRect.zero + return .zero } context.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: width, height: height)) diff --git a/VPL/Rendering/CodeOutputView.swift b/VPL/Rendering/CodeOutputView.swift index 0619ea5..57f8472 100644 --- a/VPL/Rendering/CodeOutputView.swift +++ b/VPL/Rendering/CodeOutputView.swift @@ -26,12 +26,12 @@ class CodeOutputView: UIView { var code: String = "" - var textView: UITextView = UITextView(frame: CGRect.zero) + var textView: UITextView = UITextView(frame: .zero) var copyButton: UIButton = UIButton() init() { - super.init(frame: CGRect.zero) + super.init(frame: .zero) // Style the view backgroundColor = UIColor(white: 0.96, alpha: 1.0) @@ -109,12 +109,12 @@ class CodeOutputView: UIView { // Updates the attributes let range = NSRange(keywordRange, in: code) - string.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: range) + string.addAttribute(.foregroundColor, value: color, range: range) } } // Set the font - string.addAttribute(NSAttributedStringKey.font, value: UIFont.codeFont(), range: NSRange(location: 0, length: string.length)) + string.addAttribute(.font, value: UIFont.codeFont(), range: NSRange(location: 0, length: string.length)) return string } diff --git a/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift b/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift index 794abd1..3b2d3c4 100644 --- a/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift +++ b/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift @@ -57,10 +57,10 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate self.value = defaultValue self.canvasContainer = UIView() - self.canvas = DrawingCanvas(frame: CGRect.zero) + self.canvas = DrawingCanvas(frame: .zero) self.valueLabel = UILabel() - super.init(frame: CGRect.zero) + super.init(frame: .zero) // Determine the dataset var dataset: OCRDataset @@ -196,17 +196,12 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate textField.keyboardType = self.inputType == .digits ? .decimalPad : .default } alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) - alert.addAction(UIAlertAction( - title: "Done", - style: .default, - handler: { _ in - let textField = alert.textFields![0] - if let text = textField.text { - // Update the value - self.value = text - } - } - )) + alert.addAction(UIAlertAction(title: "Done", + style: .default) { _ in + // Update the value + alert.textFields![0].text.map { + self.value = $0 + } }) parentViewController?.present(alert, animated: true) } diff --git a/VPL/Rendering/Node/Content Views/GenericInputView.swift b/VPL/Rendering/Node/Content Views/GenericInputView.swift index 2020142..8f1bbd5 100644 --- a/VPL/Rendering/Node/Content Views/GenericInputView.swift +++ b/VPL/Rendering/Node/Content Views/GenericInputView.swift @@ -21,7 +21,7 @@ public class GenericInputViewField: UIView { self.name = name self.value = defaultValue - super.init(frame: CGRect.zero) + super.init(frame: .zero) let fieldLabel = UILabel() fieldLabel.textAlignment = .center @@ -108,7 +108,7 @@ public class GenericInputView: DisplayableNodeContentView { self.node = node self.fields = fields - super.init(frame: CGRect.zero) + super.init(frame: .zero) // Set the callbacks for field in fields { diff --git a/VPL/Rendering/Node/Content Views/ValueChooserView.swift b/VPL/Rendering/Node/Content Views/ValueChooserView.swift index 6463f6e..d28f476 100644 --- a/VPL/Rendering/Node/Content Views/ValueChooserView.swift +++ b/VPL/Rendering/Node/Content Views/ValueChooserView.swift @@ -35,13 +35,13 @@ public class ValueChooserView: DisplayableNodeContentView { self.valueLabel = valueLabel self.chooseCallback = chooseCallback - super.init(frame: CGRect.zero) + super.init(frame: .zero) - selectionLabel = UILabel(frame: CGRect.zero) + selectionLabel = UILabel(frame: .zero) selectionLabel.text = valueLabel(value) addSubview(selectionLabel) - pickButton = UIButton(frame: CGRect.zero) + pickButton = UIButton(frame: .zero) pickButton.setTitle("Pick...", for: .normal) addSubview(pickButton) diff --git a/VPL/Rendering/Node/DisplayNode.swift b/VPL/Rendering/Node/DisplayNode.swift index 90c7079..03a5818 100644 --- a/VPL/Rendering/Node/DisplayNode.swift +++ b/VPL/Rendering/Node/DisplayNode.swift @@ -33,9 +33,9 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { updateShadow(lifted: false) // Add label - let titleLabel = UILabel(frame: CGRect.zero) + let titleLabel = UILabel(frame: .zero) titleLabel.textAlignment = .center - titleLabel.font = UIFont.systemFont(ofSize: 20, weight: .bold) + titleLabel.font = .systemFont(ofSize: 20, weight: .bold) titleLabel.text = type(of: node).name addSubview(titleLabel) titleLabel.translatesAutoresizingMaskIntoConstraints = false @@ -62,8 +62,8 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { } // Create panels - let leftPanel = UIStackView(frame: CGRect.zero) - let rightPanel = UIStackView(frame: CGRect.zero) + let leftPanel = UIStackView(frame: .zero) + let rightPanel = UIStackView(frame: .zero) leftPanel.axis = .vertical rightPanel.axis = .vertical leftPanel.alignment = .leading @@ -135,10 +135,10 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { func addProperty(parent: UIStackView, leftAlign: Bool, socket socketType: DisplayNodeSocketType, name: String, type: String?) { - let view = UIView(frame: CGRect.zero) + let view = UIView(frame: .zero) view.translatesAutoresizingMaskIntoConstraints = false - let socket = DisplayNodeSocket(frame: CGRect.zero, type: socketType, node: self) + let socket = DisplayNodeSocket(frame: .zero, type: socketType, node: self) sockets.append(socket) // Save the socket view.addSubview(socket) socket.translatesAutoresizingMaskIntoConstraints = false @@ -146,7 +146,7 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { socket.bottomAnchor.constraint(equalTo: view.bottomAnchor).activate() socket.widthAnchor.constraint(equalTo: socket.heightAnchor).activate() - let nameLabel = UILabel(frame: CGRect.zero) + let nameLabel = UILabel(frame: .zero) nameLabel.text = name view.addSubview(nameLabel) nameLabel.translatesAutoresizingMaskIntoConstraints = false @@ -154,7 +154,7 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { nameLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8).activate() // ^ nameLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).activate() - let typeLabel = UILabel(frame: CGRect.zero) + let typeLabel = UILabel(frame: .zero) typeLabel.text = type typeLabel.textColor = UIColor(white: 0, alpha: 0.5) typeLabel.font = UIFont.codeFont() @@ -187,7 +187,7 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { // Drag the view let translation = sender.translation(in: self) center = CGPoint(x: center.x + translation.x, y: center.y + translation.y) - sender.setTranslation(CGPoint.zero, in: self) + sender.setTranslation(.zero, in: self) // Notify the canvas the node was updated canvas?.updated(node: self) @@ -225,13 +225,12 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { let presentation = layer.presentation() let scaleAnim = CABasicAnimation(keyPath: "transform") - scaleAnim.fromValue = presentation?.transform ?? CATransform3DIdentity + scaleAnim.fromValue = presentation?.transform ?? .identity scaleAnim.toValue = lifted ? - CATransform3DScale(CATransform3DIdentity, 1.05, 1.05, 1.05) : - CATransform3DIdentity + CATransform3DScale(.identity, 1.05, 1.05, 1.05) : .identity let offsetAnim = CABasicAnimation(keyPath: "shadowOffset") - offsetAnim.fromValue = presentation?.shadowOffset ?? CGSize.zero + offsetAnim.fromValue = presentation?.shadowOffset ?? .zero offsetAnim.toValue = lifted ? CGSize(width: 0, height: 25) : CGSize(width: 0, height: 5) let shadowAnim = CABasicAnimation(keyPath: "shadowRadius") diff --git a/VPL/Utils.swift b/VPL/Utils.swift index e17ec7d..8edf281 100644 --- a/VPL/Utils.swift +++ b/VPL/Utils.swift @@ -30,6 +30,10 @@ extension DisplayNodeCanvas { } } +extension CATransform3D { + static var identity: CATransform3D { return CATransform3DIdentity } +} + // Font extension extension UIFont { static func codeFont(size: CGFloat = UIFont.systemFontSize) -> UIFont { From dfb82dc49c2b29d534a09759f6584bbd6fe7eaed Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 13:06:18 -0700 Subject: [PATCH 04/18] cgrect.center --- VPL/Rendering/CanvasViewController.swift | 5 ++--- VPL/Utils.swift | 6 ++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/VPL/Rendering/CanvasViewController.swift b/VPL/Rendering/CanvasViewController.swift index 94ec67e..c153e96 100644 --- a/VPL/Rendering/CanvasViewController.swift +++ b/VPL/Rendering/CanvasViewController.swift @@ -93,7 +93,6 @@ public class CanvasViewController: UIViewController { print("Could not get char box.") return } - let charCenter = CGPoint(x: charBox.midX, y: charBox.midY) // Overlay the breakdown for debug info // self.drawingCanvas.overlayOCRBreakdown(breakdown: breakdown) @@ -109,7 +108,7 @@ public class CanvasViewController: UIViewController { if availableNodes.count > 1 { self.nodeListPopover(nodes: availableNodes, charBox: charBox, showShortcuts: false) } else if let node = availableNodes.first { - self.create(node: node, position: charCenter) + self.create(node: node, position: charBox.center) } else { print("No nodes with shortcut: \(character)") } @@ -169,7 +168,7 @@ public class CanvasViewController: UIViewController { // Create an action to spawn the node let action = UIAlertAction(title: title, style: .default) { _ in - self.create(node: node, position: CGPoint(x: charBox.midX, y: charBox.midY)) + self.create(node: node, position: charBox.center) } alert.addAction(action) } diff --git a/VPL/Utils.swift b/VPL/Utils.swift index 8edf281..ae72b9c 100644 --- a/VPL/Utils.swift +++ b/VPL/Utils.swift @@ -46,6 +46,12 @@ func randomFloat() -> CGFloat { return CGFloat(Float(arc4random()) / Float(UINT32_MAX)) } +extension CGRect { + public var center: CGPoint { + return CGPoint(x: midX, y: midY) + } +} + // Extension helpers extension NSLayoutConstraint { @discardableResult From 41d68cba45a27320bef07d362ceafd24b014c4d8 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 13:13:11 -0700 Subject: [PATCH 05/18] * refactoring --- VPL/Data/NodeTrigger.swift | 2 -- VPL/Data/NodeValue.swift | 2 -- VPL/Data/NodeVariable.swift | 4 +--- VPL/Data/ValueType.swift | 2 -- VPL/Utils.swift | 13 +++++++++++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/VPL/Data/NodeTrigger.swift b/VPL/Data/NodeTrigger.swift index 8e03bb7..4b4d848 100644 --- a/VPL/Data/NodeTrigger.swift +++ b/VPL/Data/NodeTrigger.swift @@ -6,8 +6,6 @@ // Copyright © 2018 Nathan Flurry. All rights reserved. // -import Foundation - public final class InputTrigger { /// The node that owns this trigger. public internal(set) weak var owner: Node! diff --git a/VPL/Data/NodeValue.swift b/VPL/Data/NodeValue.swift index 183ae68..4a27d0e 100644 --- a/VPL/Data/NodeValue.swift +++ b/VPL/Data/NodeValue.swift @@ -6,8 +6,6 @@ // Copyright © 2018 Nathan Flurry. All rights reserved. // -import Foundation - public final class InputValue { /// The node that owns this value. public internal(set) weak var owner: Node! diff --git a/VPL/Data/NodeVariable.swift b/VPL/Data/NodeVariable.swift index cc2ef18..97fcdd8 100644 --- a/VPL/Data/NodeVariable.swift +++ b/VPL/Data/NodeVariable.swift @@ -6,10 +6,8 @@ // Copyright © 2018 Nathan Flurry. All rights reserved. // -import Foundation - public class NodeVariable { - public static var variableId: String { return String(format: "v%06x", Int(arc4random())) } + public static var variableId: String { return String(format: "v%06x", Int.random()) } /// The trigger that owns this variable. public internal(set) weak var owner: OutputTrigger! diff --git a/VPL/Data/ValueType.swift b/VPL/Data/ValueType.swift index 313ccb8..28d9a90 100644 --- a/VPL/Data/ValueType.swift +++ b/VPL/Data/ValueType.swift @@ -6,8 +6,6 @@ // Copyright © 2018 Nathan Flurry. All rights reserved. // -import Foundation - public indirect enum ValueType: CustomStringConvertible { /// Custom variable type. These correspond to the exact Swift variable type. case type(String) diff --git a/VPL/Utils.swift b/VPL/Utils.swift index ae72b9c..862880a 100644 --- a/VPL/Utils.swift +++ b/VPL/Utils.swift @@ -30,6 +30,12 @@ extension DisplayNodeCanvas { } } +extension Int { + static func random() -> Int { + return Int(arc4random()) + } +} + extension CATransform3D { static var identity: CATransform3D { return CATransform3DIdentity } } @@ -42,8 +48,11 @@ extension UIFont { } // Random float -func randomFloat() -> CGFloat { - return CGFloat(Float(arc4random()) / Float(UINT32_MAX)) + +extension CGFloat { + static func random() -> CGFloat { + return CGFloat(Float(arc4random()) / Float(UINT32_MAX)) + } } extension CGRect { From 838531d1d9dd3d16480682642a43339d94192ec0 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 13:17:25 -0700 Subject: [PATCH 06/18] * refactoring --- VPL/Data/Node.swift | 2 -- VPL/OCR/ImageUtils.swift | 21 +++++++++++-------- .../Node/Implementations/ArrayNodes.swift | 1 - 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/VPL/Data/Node.swift b/VPL/Data/Node.swift index bb7d9a6..c361215 100644 --- a/VPL/Data/Node.swift +++ b/VPL/Data/Node.swift @@ -6,8 +6,6 @@ // Copyright © 2018 Nathan Flurry. All rights reserved. // -import Foundation - public enum NodeOutput { case triggers([OutputTrigger]), value(OutputValue), none diff --git a/VPL/OCR/ImageUtils.swift b/VPL/OCR/ImageUtils.swift index 2a80b00..53dbe3a 100755 --- a/VPL/OCR/ImageUtils.swift +++ b/VPL/OCR/ImageUtils.swift @@ -2,7 +2,6 @@ Some tools adapted from: https://github.com/martinmitrevski/TextRecognizer/blob/master/TextRecognizer/ImageUtils.swift ***/ -import Foundation import UIKit import Vision @@ -21,15 +20,19 @@ public func |> (value: T, function: ((T) -> U)) -> U { return function(value) } -func resize(image: UIImage, targetSize: CGSize) -> UIImage { - let rect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height) - UIGraphicsBeginImageContextWithOptions(targetSize, false, 1.0) - image.draw(in: rect) - let newImage = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return newImage! +extension UIImage { + func resize(to size: CGSize) -> UIImage { + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + UIGraphicsBeginImageContextWithOptions(size, false, 1.0) + draw(in: rect) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return newImage! + } } + + func convertToGrayscale(image: UIImage) -> UIImage { let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceGray() let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) @@ -187,7 +190,7 @@ func preProcess(image: UIImage, size: CGSize, invert shouldInvert: Bool = false, if addInsets { image = insertInsets(image: image, insetWidthDimension: addToWidth2, insetHeightDimension: addToHeight2) } - image = resize(image: image, targetSize: size) + image = image.resize(to: size) image = convertToGrayscale(image: image) return image diff --git a/VPL/Rendering/Node/Implementations/ArrayNodes.swift b/VPL/Rendering/Node/Implementations/ArrayNodes.swift index a01d10c..0fd6ff1 100644 --- a/VPL/Rendering/Node/Implementations/ArrayNodes.swift +++ b/VPL/Rendering/Node/Implementations/ArrayNodes.swift @@ -6,7 +6,6 @@ // Copyright © 2018 Nathan Flurry. All rights reserved. // -import Foundation public class ArrayCreateNode: DisplayableNode { public static let shortcutCharacter: String? = "A" From 6e4c805cf9953d512e3e09b76d0ed136088905b5 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 13:21:39 -0700 Subject: [PATCH 07/18] * refactored getpixelColor --- VPL/OCR/ImageUtils.swift | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/VPL/OCR/ImageUtils.swift b/VPL/OCR/ImageUtils.swift index 53dbe3a..4edd1b3 100755 --- a/VPL/OCR/ImageUtils.swift +++ b/VPL/OCR/ImageUtils.swift @@ -58,10 +58,10 @@ func insertInsets(image: UIImage, insetWidthDimension: CGFloat, insetHeightDimen let upperRightPoint: CGPoint = CGPoint(x: adjustedImage.size.width - 1, y: 0) let lowerRightPoint: CGPoint = CGPoint(x: adjustedImage.size.width - 1, y: adjustedImage.size.height - 1) - let upperLeftColor: UIColor = getPixelColor(fromImage: adjustedImage, pixel: upperLeftPoint) - let lowerLeftColor: UIColor = getPixelColor(fromImage: adjustedImage, pixel: lowerLeftPoint) - let upperRightColor: UIColor = getPixelColor(fromImage: adjustedImage, pixel: upperRightPoint) - let lowerRightColor: UIColor = getPixelColor(fromImage: adjustedImage, pixel: lowerRightPoint) + let upperLeftColor: UIColor = adjustedImage.pixelColor(at: upperLeftPoint) + let lowerLeftColor: UIColor = adjustedImage.pixelColor(at: lowerLeftPoint) + let upperRightColor: UIColor = adjustedImage.pixelColor(at: upperRightPoint) + let lowerRightColor: UIColor = adjustedImage.pixelColor(at: lowerRightPoint) let color = averageColor(fromColors: [upperLeftColor, lowerLeftColor, upperRightColor, lowerRightColor]) let insets = UIEdgeInsets(top: insetHeightDimension, @@ -142,22 +142,24 @@ func convertTransparent(image: UIImage, color: UIColor) -> UIImage { return newImage } -func getPixelColor(fromImage image: UIImage, pixel: CGPoint) -> UIColor { - let pixelData = image.cgImage!.dataProvider!.data - let data: UnsafePointer = CFDataGetBytePtr(pixelData) - let pixelInfo: Int = ((Int(image.size.width) * Int(pixel.y)) + Int(pixel.x)) * 4 - let r = CGFloat(data[pixelInfo]) / CGFloat(255.0) - let g = CGFloat(data[pixelInfo + 1]) / CGFloat(255.0) - let b = CGFloat(data[pixelInfo + 2]) / CGFloat(255.0) - let a = CGFloat(data[pixelInfo + 3]) / CGFloat(255.0) - return UIColor(red: r, green: g, blue: b, alpha: a) +extension UIImage { + func pixelColor(at pixel: CGPoint) -> UIColor { + let pixelData = cgImage!.dataProvider!.data + let data: UnsafePointer = CFDataGetBytePtr(pixelData) + let pixelInfo: Int = ((Int(size.width) * Int(pixel.y)) + Int(pixel.x)) * 4 + let r = CGFloat(data[pixelInfo]) / CGFloat(255.0) + let g = CGFloat(data[pixelInfo + 1]) / CGFloat(255.0) + let b = CGFloat(data[pixelInfo + 2]) / CGFloat(255.0) + let a = CGFloat(data[pixelInfo + 3]) / CGFloat(255.0) + return UIColor(red: r, green: g, blue: b, alpha: a) + } } extension VNRectangleObservation { func applyTo(size: CGSize) -> CGRect { - var t: CGAffineTransform = CGAffineTransform.identity; - t = t.scaledBy(x: size.width, y: -size.height); - t = t.translatedBy(x: 0, y: -1 ); + var t: CGAffineTransform = .identity + t = t.scaledBy(x: size.width, y: -size.height) + t = t.translatedBy(x: 0, y: -1 ) let x = boundingBox.applying(t).origin.x let y = boundingBox.applying(t).origin.y let width = boundingBox.applying(t).width From 2b87e6e078743062587f315a3a552cb4fc400ff0 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 13:24:24 -0700 Subject: [PATCH 08/18] * crop -> cropping --- VPL/OCR/ImageUtils.swift | 11 +++++------ VPL/OCR/OCRRequest.swift | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/VPL/OCR/ImageUtils.swift b/VPL/OCR/ImageUtils.swift index 4edd1b3..6c350fe 100755 --- a/VPL/OCR/ImageUtils.swift +++ b/VPL/OCR/ImageUtils.swift @@ -168,13 +168,12 @@ extension VNRectangleObservation { } } -func crop(image: UIImage, rectangle: CGRect) -> UIImage? { - let drawImage = image.cgImage!.cropping(to: rectangle) - if let drawImage = drawImage { - let uiImage = UIImage(cgImage: drawImage) - return uiImage +extension UIImage { + func cropping(to rect: CGRect) -> UIImage? { + return cgImage!.cropping(to: rect).flatMap { + UIImage(cgImage: $0) + } } - return nil } func preProcess(image: UIImage, size: CGSize, invert shouldInvert: Bool = false, addInsets: Bool = true) -> UIImage { diff --git a/VPL/OCR/OCRRequest.swift b/VPL/OCR/OCRRequest.swift index 5460205..e1f1aa4 100644 --- a/VPL/OCR/OCRRequest.swift +++ b/VPL/OCR/OCRRequest.swift @@ -95,7 +95,7 @@ class OCRRequest { // Get the cropped image for the character let charBox = image.trimWhiteRect() - if let cropped = crop(image: image, rectangle: charBox) { + if let cropped = image.cropping(to: charBox) { // Classify the image let processedImage = dataset.preprocess(input: cropped) self.classifyImage(image: processedImage, charBox: charBox, wordIndex: 0, characterIndex: 0) @@ -151,7 +151,7 @@ class OCRRequest { for (charIndex, charBox) in charBoxes.enumerated() { // Get the cropped image for the character let charBox = charBox.applyTo(size: image.size) - if let cropped = crop(image: image, rectangle: charBox) { + if let cropped = image.cropping(to: charBox) { // Classify the image let processedImage = dataset.preprocess(input: cropped) self.classifyImage(image: processedImage, charBox: charBox, wordIndex: wordIndex, characterIndex: charIndex) From 6ba05e60281de80836c04895d38a4fb5e15f1748 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 13:33:38 -0700 Subject: [PATCH 09/18] * refactoring --- VPL/OCR/DrawingCanvas.swift | 6 ++-- VPL/OCR/ImageUtils.swift | 30 +++++++++++-------- VPL/Rendering/CanvasViewController.swift | 2 +- .../Content Views/DrawCanvasNodeView.swift | 2 +- VPL/Rendering/Node/DisplayNode.swift | 6 ++-- VPL/Utils.swift | 8 +++++ 6 files changed, 33 insertions(+), 21 deletions(-) diff --git a/VPL/OCR/DrawingCanvas.swift b/VPL/OCR/DrawingCanvas.swift index e6b4cf4..c1a2268 100644 --- a/VPL/OCR/DrawingCanvas.swift +++ b/VPL/OCR/DrawingCanvas.swift @@ -143,8 +143,8 @@ class DrawingCanvas: UIView { context.clear(bounds) // Draw the images into the new context - imageView.image?.draw(in: CGRect(origin: .zero, size: frame.size), blendMode: .normal, alpha: 1.0) - tempImageView.image?.draw(in: CGRect(origin: .zero, size: frame.size), blendMode: .normal, alpha: 1.0) + imageView.image?.draw(in: CGRect(size: frame.size), blendMode: .normal, alpha: 1.0) + tempImageView.image?.draw(in: CGRect(size: frame.size), blendMode: .normal, alpha: 1.0) // Assign the image imageView.image = UIGraphicsGetImageFromCurrentImageContext() @@ -198,7 +198,7 @@ class DrawingCanvas: UIView { UIGraphicsEndImageContext() return } - tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)) + tempImageView.image?.draw(in: CGRect(size: frame.size)) // Draw the line context.move(to: fromPoint) diff --git a/VPL/OCR/ImageUtils.swift b/VPL/OCR/ImageUtils.swift index 6c350fe..dbbc507 100755 --- a/VPL/OCR/ImageUtils.swift +++ b/VPL/OCR/ImageUtils.swift @@ -153,6 +153,12 @@ extension UIImage { let a = CGFloat(data[pixelInfo + 3]) / CGFloat(255.0) return UIColor(red: r, green: g, blue: b, alpha: a) } + + func cropping(to rect: CGRect) -> UIImage? { + return cgImage!.cropping(to: rect).flatMap { + UIImage(cgImage: $0) + } + } } extension VNRectangleObservation { @@ -169,11 +175,6 @@ extension VNRectangleObservation { } extension UIImage { - func cropping(to rect: CGRect) -> UIImage? { - return cgImage!.cropping(to: rect).flatMap { - UIImage(cgImage: $0) - } - } } func preProcess(image: UIImage, size: CGSize, invert shouldInvert: Bool = false, addInsets: Bool = true) -> UIImage { @@ -220,15 +221,18 @@ func invert(image: UIImage) -> UIImage { return UIImage(cgImage: imageRef) } -func removeRetinaData(image input: UIImage) -> UIImage { - UIGraphicsBeginImageContextWithOptions(input.size, false, 1.0) - input.draw(in: CGRect(x: 0, y: 0, width: input.size.width, height: input.size.height)) - guard let nonRetinaImage = UIGraphicsGetImageFromCurrentImageContext() else { - print("Failed to construct non-retina image.") - return UIImage() + +extension UIImage { + func removeRetinaData() -> UIImage { + UIGraphicsBeginImageContextWithOptions(size, false, 1.0) + draw(in: CGRect(size: size)) + guard let nonRetinaImage = UIGraphicsGetImageFromCurrentImageContext() else { + print("Failed to construct non-retina image.") + return UIImage() + } + UIGraphicsEndImageContext() + return nonRetinaImage } - UIGraphicsEndImageContext() - return nonRetinaImage } // From: https://gist.github.com/marchinram/3675efc96bf1cc2c02a5 diff --git a/VPL/Rendering/CanvasViewController.swift b/VPL/Rendering/CanvasViewController.swift index c153e96..f9576c3 100644 --- a/VPL/Rendering/CanvasViewController.swift +++ b/VPL/Rendering/CanvasViewController.swift @@ -81,7 +81,7 @@ public class CanvasViewController: UIViewController { } // Process - try! OCRRequest(dataset: .alphanum, image: removeRetinaData(image: output), singleCharacter: true) { (result, breakdown) in + try! OCRRequest(dataset: .alphanum, image: output.removeRetinaData(), singleCharacter: true) { (result, breakdown) in assert(breakdown.count == 1) // Get the character's center diff --git a/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift b/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift index 3b2d3c4..fae2fac 100644 --- a/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift +++ b/VPL/Rendering/Node/Content Views/DrawCanvasNodeView.swift @@ -129,7 +129,7 @@ public class DrawCanvasNodeView: DisplayableNodeContentView, UITextFieldDelegate } // Process - try! OCRRequest(dataset: dataset, image: removeRetinaData(image: output), singleCharacter: false) { (result, breakdown) in + try! OCRRequest(dataset: dataset, image: output.removeRetinaData(), singleCharacter: false) { (result, breakdown) in // Overlay the breakdown for debug info // self.canvas.overlayOCRBreakdown(breakdown: breakdown) diff --git a/VPL/Rendering/Node/DisplayNode.swift b/VPL/Rendering/Node/DisplayNode.swift index 03a5818..3719e04 100644 --- a/VPL/Rendering/Node/DisplayNode.swift +++ b/VPL/Rendering/Node/DisplayNode.swift @@ -25,7 +25,7 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { // Save the node and canvas self.node = node - super.init(frame: CGRect(x: 0, y: 0, width: 99999, height: 9999)) // Need large frame so the layout can be made + super.init(frame: CGRect(square: 99999)) // Need large frame so the layout can be made // Setup the view backgroundColor = UIColor(white: 0.95, alpha: 1.0) @@ -109,12 +109,12 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { } // Add drag gesture - let dragGesture = UIPanGestureRecognizer(target: self, action: #selector(panned(sender:))) + let dragGesture = UIPanGestureRecognizer(target: self, action: #selector(panned)) dragGesture.delegate = self addGestureRecognizer(dragGesture) // Add remove gesture - let removeGesture = UITapGestureRecognizer(target: self, action: #selector(remove(sender:))) + let removeGesture = UITapGestureRecognizer(target: self, action: #selector(remove)) removeGesture.numberOfTapsRequired = 2 removeGesture.delegate = self addGestureRecognizer(removeGesture) diff --git a/VPL/Utils.swift b/VPL/Utils.swift index 862880a..73d662d 100644 --- a/VPL/Utils.swift +++ b/VPL/Utils.swift @@ -59,6 +59,14 @@ extension CGRect { public var center: CGPoint { return CGPoint(x: midX, y: midY) } + + public init(square: CGFloat) { + self.init(size: CGSize(width: square, height: square)) + } + + public init(size: CGSize) { + self.init(origin: .zero, size: size) + } } // Extension helpers From 977ad3b348dc14331558147e25b832a373770524 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 13:41:31 -0700 Subject: [PATCH 10/18] * refactoring --- VPL/OCR/ImageUtils.swift | 109 ++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 58 deletions(-) diff --git a/VPL/OCR/ImageUtils.swift b/VPL/OCR/ImageUtils.swift index dbbc507..5b5eaef 100755 --- a/VPL/OCR/ImageUtils.swift +++ b/VPL/OCR/ImageUtils.swift @@ -75,7 +75,7 @@ func insertInsets(image: UIImage, insetWidthDimension: CGFloat, insetHeightDimen adjustedImage.draw(at: origin) let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - return convertTransparent(image: imageWithInsets!, color: color) + return imageWithInsets!.convertTransparent(color: color) } func averageColor(fromColors colors: [UIColor]) -> UIColor { @@ -110,39 +110,37 @@ func adjustColors(image: UIImage) -> UIImage { return image } -func fixOrientation(image: UIImage) -> UIImage { - if image.imageOrientation == .up { - return image +extension UIImage { + func fixOrientation() -> UIImage { + if imageOrientation == .up { + return self + } + UIGraphicsBeginImageContextWithOptions(size, false, scale) + draw(in: CGRect(size: size)) + if let normalizedImage = UIGraphicsGetImageFromCurrentImageContext() { + UIGraphicsEndImageContext() + return normalizedImage + } else { + return self + } } - UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale) - image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) - if let normalizedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext() { + + func convertTransparent(color: UIColor) -> UIImage { + UIGraphicsBeginImageContextWithOptions(size, false, scale) + let imageRect = CGRect(size: size) + let ctx: CGContext = UIGraphicsGetCurrentContext()! + let redValue = CGFloat(color.cgColor.components![0]) + let greenValue = CGFloat(color.cgColor.components![1]) + let blueValue = CGFloat(color.cgColor.components![2]) + let alphaValue = CGFloat(color.cgColor.components![3]) + ctx.setFillColor(red: redValue, green: greenValue, blue: blueValue, alpha: alphaValue) + ctx.fill(imageRect) + draw(in: imageRect) + let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() - return normalizedImage - } else { - return image + return newImage } -} - -func convertTransparent(image: UIImage, color: UIColor) -> UIImage { - UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale) - let width = image.size.width - let height = image.size.height - let imageRect: CGRect = CGRect(x: 0.0, y: 0.0, width: width, height: height) - let ctx: CGContext = UIGraphicsGetCurrentContext()! - let redValue = CGFloat(color.cgColor.components![0]) - let greenValue = CGFloat(color.cgColor.components![1]) - let blueValue = CGFloat(color.cgColor.components![2]) - let alphaValue = CGFloat(color.cgColor.components![3]) - ctx.setFillColor(red: redValue, green: greenValue, blue: blueValue, alpha: alphaValue) - ctx.fill(imageRect) - image.draw(in: imageRect) - let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()! - UIGraphicsEndImageContext() - return newImage -} -extension UIImage { func pixelColor(at pixel: CGPoint) -> UIColor { let pixelData = cgImage!.dataProvider!.data let data: UnsafePointer = CFDataGetBytePtr(pixelData) @@ -174,9 +172,6 @@ extension VNRectangleObservation { } } -extension UIImage { -} - func preProcess(image: UIImage, size: CGSize, invert shouldInvert: Bool = false, addInsets: Bool = true) -> UIImage { // Calculate properties let width = image.size.width @@ -187,7 +182,7 @@ func preProcess(image: UIImage, size: CGSize, invert shouldInvert: Bool = false, // Process the image var image = image if shouldInvert { - image = invert(image: image) + image = image.invert() } if addInsets { image = insertInsets(image: image, insetWidthDimension: addToWidth2, insetHeightDimension: addToHeight2) @@ -198,31 +193,31 @@ func preProcess(image: UIImage, size: CGSize, invert shouldInvert: Bool = false, return image } -func invert(image: UIImage) -> UIImage { - // Get the filter and image - guard let filter = CIFilter(name: "CIColorInvert") else { - print("Failed to find CIColorInvert.") - return UIImage() - } - guard let cgImage = image.cgImage else { - print("Failed to get CGImage.") - return UIImage() - } - // Invert the image - let img = CIImage(cgImage: cgImage) - filter.setDefaults() - filter.setValue(img, forKey: kCIInputImageKey) - let ctx = CIContext(options: nil) - guard let imageRef = ctx.createCGImage(filter.outputImage!, from: img.extent) else { - print("Failed to get CGImage from CoreImage.") - return UIImage() - } - return UIImage(cgImage: imageRef) -} +extension UIImage { + func invert() -> UIImage { + // Get the filter and image + guard let filter = CIFilter(name: "CIColorInvert") else { + print("Failed to find CIColorInvert.") + return UIImage() + } + guard let cgImage = cgImage else { + print("Failed to get CGImage.") + return UIImage() + } + // Invert the image + let img = CIImage(cgImage: cgImage) + filter.setDefaults() + filter.setValue(img, forKey: kCIInputImageKey) + let ctx = CIContext(options: nil) + guard let imageRef = ctx.createCGImage(filter.outputImage!, from: img.extent) else { + print("Failed to get CGImage from CoreImage.") + return UIImage() + } + return UIImage(cgImage: imageRef) + } -extension UIImage { func removeRetinaData() -> UIImage { UIGraphicsBeginImageContextWithOptions(size, false, 1.0) draw(in: CGRect(size: size)) @@ -233,10 +228,8 @@ extension UIImage { UIGraphicsEndImageContext() return nonRetinaImage } -} // From: https://gist.github.com/marchinram/3675efc96bf1cc2c02a5 -extension UIImage { subscript (x: Int, y: Int) -> UIColor? { if x < 0 || x > Int(size.width) || y < 0 || y > Int(size.height) { return nil From 14330ab62dfeef0557b09ece2a35bd3f82ca2b04 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 13:49:45 -0700 Subject: [PATCH 11/18] * adjustcolors --- VPL/OCR/ImageUtils.swift | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/VPL/OCR/ImageUtils.swift b/VPL/OCR/ImageUtils.swift index 5b5eaef..f88d5e6 100755 --- a/VPL/OCR/ImageUtils.swift +++ b/VPL/OCR/ImageUtils.swift @@ -52,7 +52,7 @@ func convertToGrayscale(image: UIImage) -> UIImage { func insertInsets(image: UIImage, insetWidthDimension: CGFloat, insetHeightDimension: CGFloat) -> UIImage { - let adjustedImage = adjustColors(image: image) + let adjustedImage = image.adjustColors() let upperLeftPoint: CGPoint = CGPoint(x: 0, y: 0) let lowerLeftPoint: CGPoint = CGPoint(x: 0, y: adjustedImage.size.height - 1) let upperRightPoint: CGPoint = CGPoint(x: adjustedImage.size.width - 1, y: 0) @@ -94,23 +94,22 @@ func averageColor(fromColors colors: [UIColor]) -> UIColor { } func adjustColors(image: UIImage) -> UIImage { - let context = CIContext(options: nil) - if let currentFilter = CIFilter(name: "CIColorControls") { - let beginImage = CIImage(image: image) + return image.adjustColors() +} + +extension UIImage { + func adjustColors() -> UIImage { + let context = CIContext(options: nil) + guard let currentFilter = CIFilter(name: "CIColorControls") else { return self } + let beginImage = CIImage(image: self) currentFilter.setValue(beginImage, forKey: kCIInputImageKey) currentFilter.setValue(0, forKey: kCIInputSaturationKey) currentFilter.setValue(1.45, forKey: kCIInputContrastKey) //previous 1.5 - if let output = currentFilter.outputImage { - if let cgimg = context.createCGImage(output, from: output.extent) { - let processedImage = UIImage(cgImage: cgimg) - return processedImage - } - } + guard let output = currentFilter.outputImage, + let cgimg = context.createCGImage(output, from: output.extent) else { return self } + return UIImage(cgImage: cgimg) } - return image -} -extension UIImage { func fixOrientation() -> UIImage { if imageOrientation == .up { return self @@ -136,7 +135,7 @@ extension UIImage { ctx.setFillColor(red: redValue, green: greenValue, blue: blueValue, alpha: alphaValue) ctx.fill(imageRect) draw(in: imageRect) - let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()! + let newImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return newImage } From ae9d059990d33988e70082c43342041096cb6abc Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 14:09:44 -0700 Subject: [PATCH 12/18] * refactoring --- VPL/Rendering/CodeOutputView.swift | 4 ++-- VPL/Rendering/Node/Content Views/ValueChooserView.swift | 2 +- VPL/Utils.swift | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/VPL/Rendering/CodeOutputView.swift b/VPL/Rendering/CodeOutputView.swift index 57f8472..1bec808 100644 --- a/VPL/Rendering/CodeOutputView.swift +++ b/VPL/Rendering/CodeOutputView.swift @@ -22,7 +22,7 @@ class CodeOutputView: UIView { "willSet" ] - let splitCharacters: [String] = [ "\n", "at ", "{", "}", "(", ")", "[", "]" ] + let splitCharacters: Set = [ "\n", "at ", "{", "}", "(", ")", "[", "]" ] var code: String = "" @@ -114,7 +114,7 @@ class CodeOutputView: UIView { } // Set the font - string.addAttribute(.font, value: UIFont.codeFont(), range: NSRange(location: 0, length: string.length)) + string.add(attribute: .font, value: UIFont.codeFont()) return string } diff --git a/VPL/Rendering/Node/Content Views/ValueChooserView.swift b/VPL/Rendering/Node/Content Views/ValueChooserView.swift index d28f476..fd0bac9 100644 --- a/VPL/Rendering/Node/Content Views/ValueChooserView.swift +++ b/VPL/Rendering/Node/Content Views/ValueChooserView.swift @@ -56,7 +56,7 @@ public class ValueChooserView: DisplayableNodeContentView { pickButton.centerXAnchor.constraint(equalTo: centerXAnchor).activate() // Add action to picked - pickButton.addTarget(self, action: #selector(pickTouched(sender:)), for: .touchUpInside) + pickButton.addTarget(self, action: #selector(pickTouched), for: .touchUpInside) } public required init?(coder aDecoder: NSCoder) { diff --git a/VPL/Utils.swift b/VPL/Utils.swift index 73d662d..ce7cce2 100644 --- a/VPL/Utils.swift +++ b/VPL/Utils.swift @@ -47,6 +47,12 @@ extension UIFont { } } +extension NSMutableAttributedString { + open func add(attribute: NSAttributedStringKey, value: Any) { + addAttribute(attribute, value: value, range: NSRange(location: 0, length: length)) + } +} + // Random float extension CGFloat { From bd9db8fa8e524ef83d635009b82552777d918cf2 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 14:11:33 -0700 Subject: [PATCH 13/18] * reactoring --- VPL/Rendering/Node/Implementations/MathNodes.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VPL/Rendering/Node/Implementations/MathNodes.swift b/VPL/Rendering/Node/Implementations/MathNodes.swift index e2c6a59..151498e 100644 --- a/VPL/Rendering/Node/Implementations/MathNodes.swift +++ b/VPL/Rendering/Node/Implementations/MathNodes.swift @@ -80,7 +80,7 @@ public class RandomIntNode: DisplayableNode { } public func assemble() -> String { - return "(arc4random() as Int)" + return "(Int.random())" } } @@ -96,7 +96,7 @@ public class RandomFloatNode: DisplayableNode { } public func assemble() -> String { - return "(Float(arc4random()) / Float(UINT32_MAX))" + return "(Float.random())" } } From afb0697a8d93d0ce5990d4003787dc43202cbb40 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 14:26:34 -0700 Subject: [PATCH 14/18] * refactoring --- VPL/OCR/OCRRequest.swift | 4 +- .../Node/Content Views/GenericInputView.swift | 2 +- VPL/Rendering/Node/DisplayNodeCanvas.swift | 39 +++++++++++++++++++ .../Node/DisplayNodeCanvasOverlay.swift | 38 ++---------------- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/VPL/OCR/OCRRequest.swift b/VPL/OCR/OCRRequest.swift index e1f1aa4..b87da0f 100644 --- a/VPL/OCR/OCRRequest.swift +++ b/VPL/OCR/OCRRequest.swift @@ -286,8 +286,8 @@ extension DrawingCanvas { with: charBox, options: .usesLineFragmentOrigin, attributes: [ - NSAttributedStringKey.font: UIFont.systemFont(ofSize: 36), - NSAttributedStringKey.paragraphStyle: paragraphStyle + .font: UIFont.systemFont(ofSize: 36), + .paragraphStyle: paragraphStyle ], context: nil ) diff --git a/VPL/Rendering/Node/Content Views/GenericInputView.swift b/VPL/Rendering/Node/Content Views/GenericInputView.swift index 8f1bbd5..8944879 100644 --- a/VPL/Rendering/Node/Content Views/GenericInputView.swift +++ b/VPL/Rendering/Node/Content Views/GenericInputView.swift @@ -27,7 +27,7 @@ public class GenericInputViewField: UIView { fieldLabel.textAlignment = .center fieldLabel.text = "\(name):" fieldLabel.textColor = UIColor(white: 0.3, alpha: 1.0) - fieldLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize, weight: .bold) + fieldLabel.font = .systemFont(ofSize: UIFont.smallSystemFontSize, weight: .bold) addSubview(fieldLabel) valueLabel = UILabel() diff --git a/VPL/Rendering/Node/DisplayNodeCanvas.swift b/VPL/Rendering/Node/DisplayNodeCanvas.swift index 2426385..b4539f5 100644 --- a/VPL/Rendering/Node/DisplayNodeCanvas.swift +++ b/VPL/Rendering/Node/DisplayNodeCanvas.swift @@ -8,6 +8,45 @@ import UIKit +extension Array where Element == DisplayNode { + + /// Finds a display node socket that matches a socket type. + func target(for socketType: DisplayNodeSocketType) -> DisplayNodeSocket? { + // Find a socket that matches the target of this view + for node in self { + for otherSocket in node.sockets { + switch (socketType, otherSocket.type) { + case let (.inputTrigger(trigger), .outputTrigger(otherTrigger)): + if trigger.target === otherTrigger { + return otherSocket + } + case let (.outputTrigger(trigger), .inputTrigger(otherTrigger)): + if trigger.target === otherTrigger { + return otherSocket + } + case let (.inputValue(value), .outputValue(otherValue)): + if value.target === otherValue { + return otherSocket + } + case let (.outputValue(value), .inputValue(otherValue)): + if value.target === otherValue { + return otherSocket + } + case let (.inputVariable(variable), .outputTrigger(trigger)): + if variable.target?.owner === trigger { + return otherSocket + } + default: + break + } + } + } + + // No match + return nil + } +} + public class DisplayNodeCanvas: UIScrollView, UIScrollViewDelegate { /// List of all nodes in the canvas. public private(set) var nodes: [DisplayNode] diff --git a/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift b/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift index dab2824..9dbaaa9 100644 --- a/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift +++ b/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift @@ -49,7 +49,7 @@ class DisplayNodeCanvasOverlay: UIView { color: socket.type.connectionColor, label: nil ) - } else if let targetSocket = findTarget(forSocketType: socket.type) { + } else if let targetSocket = target(for: socket.type) { // Draw a line between the sockets let startPosition = socket.convert(CGPoint.zero, to: self) let endPosition = targetSocket.convert(CGPoint.zero, to: self) @@ -70,7 +70,7 @@ class DisplayNodeCanvasOverlay: UIView { for node in canvas.nodes { for socket in node.sockets { // Draw the cap if it has a connection - if socket.draggingTarget != nil || findTarget(forSocketType: socket.type) != nil { + if socket.draggingTarget != nil || target(for: socket.type) != nil { let centerPosition = socket.convert(CGPoint(x: socket.frame.width / 2, y: socket.frame.height / 2), to: self) drawSocketCap(context: ctx, center: centerPosition, color: socket.type.connectionColor) } @@ -79,42 +79,12 @@ class DisplayNodeCanvasOverlay: UIView { } /// Finds a display node socket that matches a socket type. - func findTarget(forSocketType socketType: DisplayNodeSocketType) -> DisplayNodeSocket? { + func target(for socketType: DisplayNodeSocketType) -> DisplayNodeSocket? { guard let canvas = canvas else { print("Missing canvas.") return nil } - - // Find a socket that matches the target of this view - for node in canvas.nodes { - for otherSocket in node.sockets { - switch socketType { - case .inputTrigger(let trigger): - if case let .outputTrigger(otherTrigger) = otherSocket.type { - if trigger.target === otherTrigger { return otherSocket } - } - case .outputTrigger(let trigger): - if case let .inputTrigger(otherTrigger) = otherSocket.type { - if trigger.target === otherTrigger { return otherSocket } - } - case .inputValue(let value): - if case let .outputValue(otherValue) = otherSocket.type { - if value.target === otherValue { return otherSocket } - } - case .outputValue(let value): - if case let .inputValue(otherValue) = otherSocket.type { - if value.target === otherValue { return otherSocket } - } - case .inputVariable(let variable): - if case let .outputTrigger(trigger) = otherSocket.type { - if variable.target?.owner === trigger { return otherSocket } - } - } - } - } - - // No match - return nil + return canvas.nodes.target(for: socketType) } /// Draws a line between two points indicating a socket position From 8c033b809648332f8d9331e11f4b8c2af52e2956 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 14:32:21 -0700 Subject: [PATCH 15/18] * removed (_) --- VPL/Rendering/Node/DisplayNode.swift | 2 +- .../Node/DisplayNodeCanvasOverlay.swift | 6 ++--- VPL/Rendering/Node/DisplayNodeSocket.swift | 22 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/VPL/Rendering/Node/DisplayNode.swift b/VPL/Rendering/Node/DisplayNode.swift index 3719e04..1224118 100644 --- a/VPL/Rendering/Node/DisplayNode.swift +++ b/VPL/Rendering/Node/DisplayNode.swift @@ -231,7 +231,7 @@ public class DisplayNode: UIView, UIGestureRecognizerDelegate { let offsetAnim = CABasicAnimation(keyPath: "shadowOffset") offsetAnim.fromValue = presentation?.shadowOffset ?? .zero - offsetAnim.toValue = lifted ? CGSize(width: 0, height: 25) : CGSize(width: 0, height: 5) + offsetAnim.toValue = CGSize(width: 0, height: lifted ? 25 : 5) let shadowAnim = CABasicAnimation(keyPath: "shadowRadius") shadowAnim.fromValue = presentation?.shadowRadius ?? 0 diff --git a/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift b/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift index 9dbaaa9..4aa580c 100644 --- a/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift +++ b/VPL/Rendering/Node/DisplayNodeCanvasOverlay.swift @@ -103,9 +103,9 @@ class DisplayNodeCanvasOverlay: UIView { let lineCenter = CGPoint(x: (from.x + to.x) / 2, y: (from.y + to.y) / 2) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center - let attributes = [ - NSAttributedStringKey.font: UIFont.codeFont(), - NSAttributedStringKey.paragraphStyle: paragraphStyle + let attributes: [NSAttributedStringKey: Any] = [ + .font: UIFont.codeFont(), + .paragraphStyle: paragraphStyle ] let size = (label as NSString).size(withAttributes: attributes) diff --git a/VPL/Rendering/Node/DisplayNodeSocket.swift b/VPL/Rendering/Node/DisplayNodeSocket.swift index adbbae5..1977730 100644 --- a/VPL/Rendering/Node/DisplayNodeSocket.swift +++ b/VPL/Rendering/Node/DisplayNodeSocket.swift @@ -15,31 +15,31 @@ enum DisplayNodeSocketType: Equatable { var socketColor: UIColor { switch self { - case .inputTrigger(_), .outputTrigger(_): + case .inputTrigger, .outputTrigger: return UIColor(red: 1, green: 0.74, blue: 0.24, alpha: 1.0) - case .inputValue(_), .outputValue(_): + case .inputValue, .outputValue: return UIColor(red: 0.11, green: 0.84, blue: 1.0, alpha: 1.0) - case .inputVariable(_): + case .inputVariable: return UIColor(red: 0.12, green: 1, blue: 0.59, alpha: 1.0) } } var connectionColor: UIColor { switch self { - case .inputTrigger(_), .outputTrigger(_): + case .inputTrigger, .outputTrigger: return UIColor(red: 1, green: 0.85, blue: 0.56, alpha: 1.0) - case .inputValue(_), .outputValue(_): + case .inputValue, .outputValue: return UIColor(red: 0.65, green: 0.93, blue: 1.0, alpha: 1.0) - case .inputVariable(_): + case .inputVariable: return UIColor(red: 0.44, green: 1, blue: 0.74, alpha: 0.35) } } var isInput: Bool { switch self { - case .inputTrigger(_), .inputValue(_), .inputVariable(_): + case .inputTrigger, .inputValue, .inputVariable: return true - case .outputValue(_), .outputTrigger(_): + case .outputValue, .outputTrigger: return false } } @@ -201,7 +201,7 @@ class DisplayNodeSocket: UIView { if case let .inputValue(otherValue) = other.type { value.connect(to: otherValue) } - case .inputVariable(_): + case .inputVariable: promptVariableConnectino(to: other) } @@ -351,9 +351,9 @@ class DisplayNodeSocket: UIView { // Determine if input var isInput: Bool switch type { - case .inputTrigger(_), .inputValue(_), .inputVariable(_): + case .inputTrigger, .inputValue, .inputVariable: isInput = true - case .outputTrigger(_), .outputValue(_): + case .outputTrigger, .outputValue: isInput = false } From 672bd6718c3aa69bb1c3abcdaedc537d64dfeb24 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 14:37:20 -0700 Subject: [PATCH 16/18] * refactoring of socket type reset --- VPL/Rendering/Node/DisplayNodeSocket.swift | 33 +++++++++++----------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/VPL/Rendering/Node/DisplayNodeSocket.swift b/VPL/Rendering/Node/DisplayNodeSocket.swift index 1977730..6c1ac76 100644 --- a/VPL/Rendering/Node/DisplayNodeSocket.swift +++ b/VPL/Rendering/Node/DisplayNodeSocket.swift @@ -85,6 +85,16 @@ enum DisplayNodeSocketType: Equatable { return false } + + func reset() { + switch self { + case let .inputTrigger(trigger): trigger.reset() + case let .outputTrigger(trigger): trigger.reset() + case let .inputValue(value): value.reset() + case let .outputValue(value): value.reset() + case let .inputVariable(variable): variable.reset() + } + } } class DisplayNodeSocket: UIView { @@ -264,16 +274,11 @@ class DisplayNodeSocket: UIView { /// Label that will be drawn on the connection. func connectionLabel() -> String? { - switch type { - case .inputVariable(let variable): - if let target = variable.target { - return "\(target.name) (\(target.type.description))" - } else { - return nil - } - default: - return nil + if case let .inputVariable(variable) = type, + let target = variable.target { + return "\(target.name) (\(target.type.description))" } + return nil } @objc func panned(sender: UIPanGestureRecognizer) { @@ -283,13 +288,7 @@ class DisplayNodeSocket: UIView { } // Remove the target - switch type { - case .inputTrigger(let trigger): trigger.reset() - case .outputTrigger(let trigger): trigger.reset() - case .inputValue(let value): value.reset() - case .outputValue(let value): value.reset() - case .inputVariable(let variable): variable.reset() - } + type.reset() // Update the dragging to position if sender.state == .began || sender.state == .changed { @@ -331,7 +330,7 @@ class DisplayNodeSocket: UIView { let path = CGMutablePath() path.move(to: CGPoint(x: size.width, y: size.height / 2)) path.addLine(to: CGPoint(x: 0, y: size.height)) - path.addLine(to: CGPoint(x: 0, y: 0)) + path.addLine(to: .zero) path.addLine(to: CGPoint(x: size.width, y: size.height / 2)) // Create a shape From 0103a0598177a942aea2a1dbc46f44331771ece1 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 14:43:52 -0700 Subject: [PATCH 17/18] * refactoring --- VPL/Rendering/Node/DisplayNodeSocket.swift | 70 ++++++++-------------- 1 file changed, 25 insertions(+), 45 deletions(-) diff --git a/VPL/Rendering/Node/DisplayNodeSocket.swift b/VPL/Rendering/Node/DisplayNodeSocket.swift index 6c1ac76..f422e50 100644 --- a/VPL/Rendering/Node/DisplayNodeSocket.swift +++ b/VPL/Rendering/Node/DisplayNodeSocket.swift @@ -161,58 +161,38 @@ class DisplayNodeSocket: UIView { /// If this socket can be connected to another socket. func canConnectTo(socket other: DisplayNodeSocket) -> Bool { - switch type { - case .inputTrigger(let trigger): - if case let .outputTrigger(otherTrigger) = other.type { - return trigger.canConnect(to: otherTrigger) - } - case .outputTrigger(let trigger): - if case let .inputTrigger(otherTrigger) = other.type { - return trigger.canConnect(to: otherTrigger) - } - case .inputValue(let value): - if case let .outputValue(otherValue) = other.type { - return value.canConnect(to: otherValue) - } - case .outputValue(let value): - if case let .inputValue(otherValue) = other.type { - return value.canConnect(to: otherValue) - } - case .inputVariable(let variable): - if case let .outputTrigger(trigger) = other.type { - for otherVariable in trigger.exposedVariables { - if variable.canConnect(to: otherVariable) { - return true - } - } - } + switch (type, other.type) { + case let (.inputTrigger(trigger), .outputTrigger(otherTrigger)): + return trigger.canConnect(to: otherTrigger) + case let (.outputTrigger(trigger), .inputTrigger(otherTrigger)): + return trigger.canConnect(to: otherTrigger) + case let (.inputValue(value), .outputValue(otherValue)): + return value.canConnect(to: otherValue) + case let (.outputValue(value), .inputValue(otherValue)): + return value.canConnect(to: otherValue) + case let (.inputVariable(variable), .outputTrigger(trigger)): + return trigger.exposedVariables.contains { variable.canConnect(to: $0) } + default: + return false } - - return false } /// Connects this socket to another socket. func connect(to other: DisplayNodeSocket) { // Set the connection. - switch type { - case .inputTrigger(let trigger): - if case let .outputTrigger(otherTrigger) = other.type { - trigger.connect(to: otherTrigger) - } - case .outputTrigger(let trigger): - if case let .inputTrigger(otherTrigger) = other.type { - trigger.connect(to: otherTrigger) - } - case .inputValue(let value): - if case let .outputValue(otherValue) = other.type { - value.connect(to: otherValue) - } - case .outputValue(let value): - if case let .inputValue(otherValue) = other.type { - value.connect(to: otherValue) - } - case .inputVariable: + switch (type, other.type) { + case let (.inputTrigger(trigger), .outputTrigger(otherTrigger)): + trigger.connect(to: otherTrigger) + case let (.outputTrigger(trigger), .inputTrigger(otherTrigger)): + trigger.connect(to: otherTrigger) + case let (.inputValue(value), .outputValue(otherValue)): + value.connect(to: otherValue) + case let (.outputValue(value), .inputValue(otherValue)): + value.connect(to: otherValue) + case (.inputVariable, _): promptVariableConnectino(to: other) + default: + break } // Update the socket From 8a565f5afa5e2b734c47bb2043ef606f210d761a Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Tue, 24 Apr 2018 14:48:55 -0700 Subject: [PATCH 18/18] * refactoring --- VPL/Data/Node.swift | 9 ++---- VPL/Rendering/Node/DisplayNodeSocket.swift | 37 ++++++++-------------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/VPL/Data/Node.swift b/VPL/Data/Node.swift index c361215..52f24c0 100644 --- a/VPL/Data/Node.swift +++ b/VPL/Data/Node.swift @@ -13,18 +13,16 @@ public enum NodeOutput { public var triggers: [OutputTrigger]? { if case let .triggers(triggers) = self { return triggers - } else { - return nil } + return nil } /// Returns the value, if a value type. public var value: OutputValue? { if case let .value(value) = self { return value - } else { - return nil } + return nil } } @@ -57,9 +55,8 @@ extension Node { return trigger } else if case let .value(value) = output { return value.target?.owner.nearestControlNode - } else { - return nil } + return nil } /// Variables that this node can use. diff --git a/VPL/Rendering/Node/DisplayNodeSocket.swift b/VPL/Rendering/Node/DisplayNodeSocket.swift index f422e50..45e19f1 100644 --- a/VPL/Rendering/Node/DisplayNodeSocket.swift +++ b/VPL/Rendering/Node/DisplayNodeSocket.swift @@ -59,31 +59,22 @@ enum DisplayNodeSocketType: Equatable { } } - static func == (lhs: DisplayNodeSocketType, rhs: DisplayNodeSocketType) -> Bool { - switch lhs { - case .inputTrigger(let lhsTrigger): - if case let .inputTrigger(rhsTrigger) = rhs { - return lhsTrigger === rhsTrigger - } - case .outputTrigger(let lhsTrigger): - if case let .outputTrigger(rhsTrigger) = rhs { - return lhsTrigger === rhsTrigger - } - case .inputValue(let lhsValue): - if case let .inputValue(rhsValue) = rhs { - return lhsValue === rhsValue - } - case .outputValue(let lhsValue): - if case let .outputValue(rhsValue) = rhs { - return lhsValue === rhsValue - } - case .inputVariable(let lhsVariable): - if case let .inputVariable(rhsVariable) = rhs { - return lhsVariable === rhsVariable - } + static func ==(lhs: DisplayNodeSocketType, rhs: DisplayNodeSocketType) -> Bool { + switch (lhs, rhs) { + case let (.inputTrigger(l), .inputTrigger(r)): + return l === r + case let (.outputTrigger(l), .outputTrigger(r)): + return l === r + case let (.inputValue(l), .inputValue(r)): + return l === r + case let (.outputValue(l), .outputValue(r)): + return l === r + case let (.inputVariable(l), .inputVariable(r)): + return l === r + default: + return false } - return false } func reset() {