mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			269 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Swift
		
	
		
		
			
		
	
	
			269 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Swift
		
	
| 
											7 years ago
										 | // | ||
|  | //  Copyright (c) 2019 Open Whisper Systems. All rights reserved. | ||
|  | // | ||
|  | 
 | ||
|  | import UIKit | ||
| 
											5 years ago
										 | import SessionUIKit | ||
| 
											7 years ago
										 | 
 | ||
|  | @objc | ||
|  | public protocol ImageEditorBrushViewControllerDelegate: class { | ||
| 
											7 years ago
										 |     func brushDidComplete(currentColor: ImageEditorColor) | ||
| 
											7 years ago
										 | } | ||
|  | 
 | ||
|  | // MARK: - | ||
|  | 
 | ||
|  | public class ImageEditorBrushViewController: OWSViewController { | ||
|  | 
 | ||
|  |     private weak var delegate: ImageEditorBrushViewControllerDelegate? | ||
|  | 
 | ||
|  |     private let model: ImageEditorModel | ||
|  | 
 | ||
|  |     private let canvasView: ImageEditorCanvasView | ||
|  | 
 | ||
| 
											7 years ago
										 |     private let paletteView: ImageEditorPaletteView | ||
| 
											7 years ago
										 | 
 | ||
| 
											7 years ago
										 |     // We only want to let users undo changes made in this view. | ||
|  |     // So we snapshot any older "operation id" and prevent | ||
|  |     // users from undoing it. | ||
|  |     private let firstUndoOperationId: String? | ||
|  | 
 | ||
| 
											7 years ago
										 |     init(delegate: ImageEditorBrushViewControllerDelegate, | ||
| 
											7 years ago
										 |          model: ImageEditorModel, | ||
|  |          currentColor: ImageEditorColor) { | ||
| 
											7 years ago
										 |         self.delegate = delegate | ||
|  |         self.model = model | ||
|  |         self.canvasView = ImageEditorCanvasView(model: model) | ||
| 
											7 years ago
										 |         self.paletteView = ImageEditorPaletteView(currentColor: currentColor) | ||
| 
											7 years ago
										 |         self.firstUndoOperationId = model.currentUndoOperationId() | ||
| 
											7 years ago
										 | 
 | ||
|  |         super.init(nibName: nil, bundle: nil) | ||
|  | 
 | ||
|  |         model.add(observer: self) | ||
|  |     } | ||
|  | 
 | ||
|  |     @available(*, unavailable, message: "use other init() instead.") | ||
|  |     required public init?(coder aDecoder: NSCoder) { | ||
|  |         notImplemented() | ||
|  |     } | ||
|  | 
 | ||
|  |     // MARK: - View Lifecycle | ||
|  | 
 | ||
|  |     public override func loadView() { | ||
|  |         self.view = UIView() | ||
| 
											6 years ago
										 |         self.view.backgroundColor = Colors.navigationBarBackground | ||
| 
											7 years ago
										 |         self.view.isOpaque = true | ||
| 
											7 years ago
										 | 
 | ||
|  |         canvasView.configureSubviews() | ||
|  |         self.view.addSubview(canvasView) | ||
|  |         canvasView.autoPinEdgesToSuperviewEdges() | ||
|  | 
 | ||
|  |         paletteView.delegate = self | ||
|  |         self.view.addSubview(paletteView) | ||
|  |         paletteView.autoVCenterInSuperview() | ||
| 
											7 years ago
										 |         paletteView.autoPinEdge(toSuperviewEdge: .trailing, withInset: 0) | ||
| 
											7 years ago
										 | 
 | ||
|  |         self.view.isUserInteractionEnabled = true | ||
|  | 
 | ||
|  |         let brushGestureRecognizer = ImageEditorPanGestureRecognizer(target: self, action: #selector(handleBrushGesture(_:))) | ||
|  |         brushGestureRecognizer.maximumNumberOfTouches = 1 | ||
|  |         brushGestureRecognizer.referenceView = canvasView.gestureReferenceView | ||
| 
											7 years ago
										 |         brushGestureRecognizer.delegate = self | ||
| 
											7 years ago
										 |         self.view.addGestureRecognizer(brushGestureRecognizer) | ||
|  | 
 | ||
|  |         updateNavigationBar() | ||
|  |     } | ||
|  | 
 | ||
|  |     public override func viewWillAppear(_ animated: Bool) { | ||
|  |         super.viewWillAppear(animated) | ||
|  | 
 | ||
|  |         self.view.layoutSubviews() | ||
|  |     } | ||
|  | 
 | ||
|  |     public override func viewDidAppear(_ animated: Bool) { | ||
|  |         super.viewDidAppear(animated) | ||
|  | 
 | ||
|  |         self.view.layoutSubviews() | ||
|  |     } | ||
|  | 
 | ||
| 
											7 years ago
										 |     private func updateNavigationBar() { | ||
|  |         // Hide controls during stroke. | ||
|  |         let hasStroke = currentStroke != nil | ||
|  |         guard !hasStroke else { | ||
|  |             updateNavigationBar(navigationBarItems: []) | ||
|  |             return | ||
|  |         } | ||
|  | 
 | ||
| 
											7 years ago
										 |         let undoButton = navigationBarButton(imageName: "image_editor_undo", | ||
|  |                                              selector: #selector(didTapUndo(sender:))) | ||
|  |         let doneButton = navigationBarButton(imageName: "image_editor_checkmark_full", | ||
| 
											7 years ago
										 |                                              selector: #selector(didTapDone(sender:))) | ||
| 
											7 years ago
										 | 
 | ||
| 
											7 years ago
										 |         // Prevent users from undo any changes made before entering the view. | ||
|  |         let canUndo = model.canUndo() && firstUndoOperationId != model.currentUndoOperationId() | ||
| 
											7 years ago
										 |         var navigationBarItems = [UIView]() | ||
| 
											7 years ago
										 |         if canUndo { | ||
| 
											7 years ago
										 |             navigationBarItems = [undoButton, doneButton] | ||
|  |         } else { | ||
|  |             navigationBarItems = [doneButton] | ||
|  |         } | ||
|  |         updateNavigationBar(navigationBarItems: navigationBarItems) | ||
|  |     } | ||
|  | 
 | ||
| 
											7 years ago
										 |     private func updateControls() { | ||
|  |         // Hide controls during stroke. | ||
|  |         let hasStroke = currentStroke != nil | ||
|  |         paletteView.isHidden = hasStroke | ||
|  |     } | ||
|  | 
 | ||
| 
											7 years ago
										 |     @objc | ||
|  |     public override var prefersStatusBarHidden: Bool { | ||
|  |         return true | ||
|  |     } | ||
|  | 
 | ||
| 
											7 years ago
										 |     @objc | ||
|  |     override public var canBecomeFirstResponder: Bool { | ||
|  |         return true | ||
|  |     } | ||
|  | 
 | ||
| 
											7 years ago
										 |     // MARK: - Actions | ||
|  | 
 | ||
|  |     @objc func didTapUndo(sender: UIButton) { | ||
|  |         Logger.verbose("") | ||
|  |         guard model.canUndo() else { | ||
|  |             owsFailDebug("Can't undo.") | ||
|  |             return | ||
|  |         } | ||
|  |         model.undo() | ||
|  |     } | ||
|  | 
 | ||
|  |     @objc func didTapDone(sender: UIButton) { | ||
|  |         Logger.verbose("") | ||
|  | 
 | ||
|  |         completeAndDismiss() | ||
|  |     } | ||
|  | 
 | ||
|  |     private func completeAndDismiss() { | ||
| 
											7 years ago
										 |         self.delegate?.brushDidComplete(currentColor: paletteView.selectedValue) | ||
| 
											7 years ago
										 | 
 | ||
|  |         self.dismiss(animated: false) { | ||
|  |             // Do nothing. | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     // MARK: - Brush | ||
|  | 
 | ||
|  |     // These properties are non-empty while drawing a stroke. | ||
| 
											7 years ago
										 |     private var currentStroke: ImageEditorStrokeItem? { | ||
|  |         didSet { | ||
|  |             updateControls() | ||
|  |             updateNavigationBar() | ||
|  |         } | ||
|  |     } | ||
| 
											7 years ago
										 |     private var currentStrokeSamples = [ImageEditorStrokeItem.StrokeSample]() | ||
|  | 
 | ||
|  |     @objc | ||
| 
											7 years ago
										 |     public func handleBrushGesture(_ gestureRecognizer: ImageEditorPanGestureRecognizer) { | ||
| 
											7 years ago
										 |         AssertIsOnMainThread() | ||
|  | 
 | ||
|  |         let removeCurrentStroke = { | ||
|  |             if let stroke = self.currentStroke { | ||
|  |                 self.model.remove(item: stroke) | ||
|  |             } | ||
|  |             self.currentStroke = nil | ||
|  |             self.currentStrokeSamples.removeAll() | ||
|  |         } | ||
| 
											7 years ago
										 |         let tryToAppendStrokeSample = { (locationInView: CGPoint) in | ||
| 
											7 years ago
										 |             let view = self.canvasView.gestureReferenceView | ||
|  |             let viewBounds = view.bounds | ||
|  |             let newSample = ImageEditorCanvasView.locationImageUnit(forLocationInView: locationInView, | ||
| 
											7 years ago
										 |                                                                     viewBounds: viewBounds, | ||
|  |                                                                     model: self.model, | ||
|  |                                                                     transform: self.model.currentTransform()) | ||
| 
											7 years ago
										 | 
 | ||
|  |             if let prevSample = self.currentStrokeSamples.last, | ||
|  |                 prevSample == newSample { | ||
|  |                 // Ignore duplicate samples. | ||
|  |                 return | ||
|  |             } | ||
|  |             self.currentStrokeSamples.append(newSample) | ||
|  |         } | ||
|  | 
 | ||
| 
											7 years ago
										 |         let strokeColor = paletteView.selectedValue.color | ||
| 
											7 years ago
										 |         let unitStrokeWidth = ImageEditorStrokeItem.defaultUnitStrokeWidth() / self.model.currentTransform().scaling | ||
| 
											7 years ago
										 | 
 | ||
|  |         switch gestureRecognizer.state { | ||
|  |         case .began: | ||
|  |             removeCurrentStroke() | ||
|  | 
 | ||
| 
											7 years ago
										 |             // Apply the location history of the gesture so that the stroke reflects | ||
|  |             // the touch's movement before the gesture recognized. | ||
| 
											7 years ago
										 |             for location in gestureRecognizer.locationHistory { | ||
| 
											7 years ago
										 |                 tryToAppendStrokeSample(location) | ||
|  |             } | ||
|  | 
 | ||
|  |             let locationInView = gestureRecognizer.location(in: canvasView.gestureReferenceView) | ||
|  |             tryToAppendStrokeSample(locationInView) | ||
| 
											7 years ago
										 | 
 | ||
|  |             let stroke = ImageEditorStrokeItem(color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth) | ||
|  |             model.append(item: stroke) | ||
|  |             currentStroke = stroke | ||
|  | 
 | ||
|  |         case .changed, .ended: | ||
| 
											7 years ago
										 |             let locationInView = gestureRecognizer.location(in: canvasView.gestureReferenceView) | ||
|  |             tryToAppendStrokeSample(locationInView) | ||
| 
											7 years ago
										 | 
 | ||
|  |             guard let lastStroke = self.currentStroke else { | ||
|  |                 owsFailDebug("Missing last stroke.") | ||
|  |                 removeCurrentStroke() | ||
|  |                 return | ||
|  |             } | ||
|  | 
 | ||
|  |             // Model items are immutable; we _replace_ the | ||
|  |             // stroke item rather than modify it. | ||
|  |             let stroke = ImageEditorStrokeItem(itemId: lastStroke.itemId, color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth) | ||
|  |             model.replace(item: stroke, suppressUndo: true) | ||
|  | 
 | ||
|  |             if gestureRecognizer.state == .ended { | ||
|  |                 currentStroke = nil | ||
|  |                 currentStrokeSamples.removeAll() | ||
|  |             } else { | ||
|  |                 currentStroke = stroke | ||
|  |             } | ||
|  |         default: | ||
|  |             removeCurrentStroke() | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | // MARK: - | ||
|  | 
 | ||
|  | extension ImageEditorBrushViewController: ImageEditorModelObserver { | ||
|  | 
 | ||
|  |     public func imageEditorModelDidChange(before: ImageEditorContents, | ||
|  |                                           after: ImageEditorContents) { | ||
|  |         updateNavigationBar() | ||
|  |     } | ||
|  | 
 | ||
|  |     public func imageEditorModelDidChange(changedItemIds: [String]) { | ||
|  |         updateNavigationBar() | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | // MARK: - | ||
|  | 
 | ||
|  | extension ImageEditorBrushViewController: ImageEditorPaletteViewDelegate { | ||
|  |     public func selectedColorDidChange() { | ||
|  |         // TODO: | ||
|  |     } | ||
|  | } | ||
| 
											7 years ago
										 | 
 | ||
|  | // MARK: - | ||
|  | 
 | ||
|  | extension ImageEditorBrushViewController: UIGestureRecognizerDelegate { | ||
|  |     @objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { | ||
|  |         // Ignore touches that begin inside the palette. | ||
|  |         let location = touch.location(in: paletteView) | ||
|  |         return !paletteView.bounds.contains(location) | ||
|  |     } | ||
|  | } |