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.
		
		
		
		
		
			
		
			
				
	
	
		
			268 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			268 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Swift
		
	
| //
 | |
| //  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| import UIKit
 | |
| 
 | |
| @objc
 | |
| public protocol ImageEditorBrushViewControllerDelegate: class {
 | |
|     func brushDidComplete(currentColor: ImageEditorColor)
 | |
| }
 | |
| 
 | |
| // MARK: -
 | |
| 
 | |
| public class ImageEditorBrushViewController: OWSViewController {
 | |
| 
 | |
|     private weak var delegate: ImageEditorBrushViewControllerDelegate?
 | |
| 
 | |
|     private let model: ImageEditorModel
 | |
| 
 | |
|     private let canvasView: ImageEditorCanvasView
 | |
| 
 | |
|     private let paletteView: ImageEditorPaletteView
 | |
| 
 | |
|     // 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?
 | |
| 
 | |
|     init(delegate: ImageEditorBrushViewControllerDelegate,
 | |
|          model: ImageEditorModel,
 | |
|          currentColor: ImageEditorColor) {
 | |
|         self.delegate = delegate
 | |
|         self.model = model
 | |
|         self.canvasView = ImageEditorCanvasView(model: model)
 | |
|         self.paletteView = ImageEditorPaletteView(currentColor: currentColor)
 | |
|         self.firstUndoOperationId = model.currentUndoOperationId()
 | |
| 
 | |
|         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()
 | |
|         self.view.backgroundColor = Colors.navigationBarBackground
 | |
|         self.view.isOpaque = true
 | |
| 
 | |
|         canvasView.configureSubviews()
 | |
|         self.view.addSubview(canvasView)
 | |
|         canvasView.autoPinEdgesToSuperviewEdges()
 | |
| 
 | |
|         paletteView.delegate = self
 | |
|         self.view.addSubview(paletteView)
 | |
|         paletteView.autoVCenterInSuperview()
 | |
|         paletteView.autoPinEdge(toSuperviewEdge: .trailing, withInset: 0)
 | |
| 
 | |
|         self.view.isUserInteractionEnabled = true
 | |
| 
 | |
|         let brushGestureRecognizer = ImageEditorPanGestureRecognizer(target: self, action: #selector(handleBrushGesture(_:)))
 | |
|         brushGestureRecognizer.maximumNumberOfTouches = 1
 | |
|         brushGestureRecognizer.referenceView = canvasView.gestureReferenceView
 | |
|         brushGestureRecognizer.delegate = self
 | |
|         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()
 | |
|     }
 | |
| 
 | |
|     private func updateNavigationBar() {
 | |
|         // Hide controls during stroke.
 | |
|         let hasStroke = currentStroke != nil
 | |
|         guard !hasStroke else {
 | |
|             updateNavigationBar(navigationBarItems: [])
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         let undoButton = navigationBarButton(imageName: "image_editor_undo",
 | |
|                                              selector: #selector(didTapUndo(sender:)))
 | |
|         let doneButton = navigationBarButton(imageName: "image_editor_checkmark_full",
 | |
|                                              selector: #selector(didTapDone(sender:)))
 | |
| 
 | |
|         // Prevent users from undo any changes made before entering the view.
 | |
|         let canUndo = model.canUndo() && firstUndoOperationId != model.currentUndoOperationId()
 | |
|         var navigationBarItems = [UIView]()
 | |
|         if canUndo {
 | |
|             navigationBarItems = [undoButton, doneButton]
 | |
|         } else {
 | |
|             navigationBarItems = [doneButton]
 | |
|         }
 | |
|         updateNavigationBar(navigationBarItems: navigationBarItems)
 | |
|     }
 | |
| 
 | |
|     private func updateControls() {
 | |
|         // Hide controls during stroke.
 | |
|         let hasStroke = currentStroke != nil
 | |
|         paletteView.isHidden = hasStroke
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     public override var prefersStatusBarHidden: Bool {
 | |
|         return true
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     override public var canBecomeFirstResponder: Bool {
 | |
|         return true
 | |
|     }
 | |
| 
 | |
|     // 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() {
 | |
|         self.delegate?.brushDidComplete(currentColor: paletteView.selectedValue)
 | |
| 
 | |
|         self.dismiss(animated: false) {
 | |
|             // Do nothing.
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // MARK: - Brush
 | |
| 
 | |
|     // These properties are non-empty while drawing a stroke.
 | |
|     private var currentStroke: ImageEditorStrokeItem? {
 | |
|         didSet {
 | |
|             updateControls()
 | |
|             updateNavigationBar()
 | |
|         }
 | |
|     }
 | |
|     private var currentStrokeSamples = [ImageEditorStrokeItem.StrokeSample]()
 | |
| 
 | |
|     @objc
 | |
|     public func handleBrushGesture(_ gestureRecognizer: ImageEditorPanGestureRecognizer) {
 | |
|         AssertIsOnMainThread()
 | |
| 
 | |
|         let removeCurrentStroke = {
 | |
|             if let stroke = self.currentStroke {
 | |
|                 self.model.remove(item: stroke)
 | |
|             }
 | |
|             self.currentStroke = nil
 | |
|             self.currentStrokeSamples.removeAll()
 | |
|         }
 | |
|         let tryToAppendStrokeSample = { (locationInView: CGPoint) in
 | |
|             let view = self.canvasView.gestureReferenceView
 | |
|             let viewBounds = view.bounds
 | |
|             let newSample = ImageEditorCanvasView.locationImageUnit(forLocationInView: locationInView,
 | |
|                                                                     viewBounds: viewBounds,
 | |
|                                                                     model: self.model,
 | |
|                                                                     transform: self.model.currentTransform())
 | |
| 
 | |
|             if let prevSample = self.currentStrokeSamples.last,
 | |
|                 prevSample == newSample {
 | |
|                 // Ignore duplicate samples.
 | |
|                 return
 | |
|             }
 | |
|             self.currentStrokeSamples.append(newSample)
 | |
|         }
 | |
| 
 | |
|         let strokeColor = paletteView.selectedValue.color
 | |
|         let unitStrokeWidth = ImageEditorStrokeItem.defaultUnitStrokeWidth() / self.model.currentTransform().scaling
 | |
| 
 | |
|         switch gestureRecognizer.state {
 | |
|         case .began:
 | |
|             removeCurrentStroke()
 | |
| 
 | |
|             // Apply the location history of the gesture so that the stroke reflects
 | |
|             // the touch's movement before the gesture recognized.
 | |
|             for location in gestureRecognizer.locationHistory {
 | |
|                 tryToAppendStrokeSample(location)
 | |
|             }
 | |
| 
 | |
|             let locationInView = gestureRecognizer.location(in: canvasView.gestureReferenceView)
 | |
|             tryToAppendStrokeSample(locationInView)
 | |
| 
 | |
|             let stroke = ImageEditorStrokeItem(color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth)
 | |
|             model.append(item: stroke)
 | |
|             currentStroke = stroke
 | |
| 
 | |
|         case .changed, .ended:
 | |
|             let locationInView = gestureRecognizer.location(in: canvasView.gestureReferenceView)
 | |
|             tryToAppendStrokeSample(locationInView)
 | |
| 
 | |
|             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:
 | |
|     }
 | |
| }
 | |
| 
 | |
| // 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)
 | |
|     }
 | |
| }
 |