Merge branch 'charlesmchen/imageEditor2'

pull/1/head
Matthew Chen 6 years ago
commit 3c68e29834

@ -311,9 +311,15 @@
/* Label for generic done button. */ /* Label for generic done button. */
"BUTTON_DONE" = "Done"; "BUTTON_DONE" = "Done";
/* Label for redo button. */
"BUTTON_REDO" = "Redo";
/* Button text to enable batch selection mode */ /* Button text to enable batch selection mode */
"BUTTON_SELECT" = "Select"; "BUTTON_SELECT" = "Select";
/* Label for undo button. */
"BUTTON_UNDO" = "Undo";
/* Label for button that lets users call a contact again. */ /* Label for button that lets users call a contact again. */
"CALL_AGAIN_BUTTON_TITLE" = "Call Again"; "CALL_AGAIN_BUTTON_TITLE" = "Call Again";

@ -857,8 +857,6 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
private(set) var contentContainer: UIView! private(set) var contentContainer: UIView!
private(set) var playVideoButton: UIView? private(set) var playVideoButton: UIView?
private var imageEditorView: ImageEditorView?
// MARK: - Initializers // MARK: - Initializers
init(attachmentItem: SignalAttachmentItem) { init(attachmentItem: SignalAttachmentItem) {
@ -954,7 +952,8 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
imageMediaView.isUserInteractionEnabled = true imageMediaView.isUserInteractionEnabled = true
imageMediaView.addSubview(imageEditorView) imageMediaView.addSubview(imageEditorView)
imageEditorView.autoPinEdgesToSuperviewEdges() imageEditorView.autoPinEdgesToSuperviewEdges()
self.imageEditorView = imageEditorView
imageEditorView.addControls(to: self.mediaMessageView)
} }
#endif #endif

@ -139,6 +139,10 @@ public class OrderedDictionary<ValueType>: NSObject {
return OrderedDictionary(keyValueMap: keyValueMap, orderedKeys: orderedKeys) return OrderedDictionary(keyValueMap: keyValueMap, orderedKeys: orderedKeys)
} }
public func value(forKey key: KeyType) -> ValueType? {
return keyValueMap[key]
}
public func append(key: KeyType, value: ValueType) { public func append(key: KeyType, value: ValueType) {
if keyValueMap[key] != nil { if keyValueMap[key] != nil {
owsFailDebug("Unexpected duplicate key in key map: \(key)") owsFailDebug("Unexpected duplicate key in key map: \(key)")
@ -239,6 +243,11 @@ public class ImageEditorContents: NSObject {
return ImageEditorContents(itemMap: itemMap.clone()) return ImageEditorContents(itemMap: itemMap.clone())
} }
@objc
public func item(forId itemId: String) -> ImageEditorItem? {
return itemMap.value(forKey: itemId)
}
@objc @objc
public func append(item: ImageEditorItem) { public func append(item: ImageEditorItem) {
Logger.verbose("\(item.itemId)") Logger.verbose("\(item.itemId)")
@ -300,6 +309,7 @@ private class ImageEditorOperation: NSObject {
@objc @objc
public protocol ImageEditorModelDelegate: class { public protocol ImageEditorModelDelegate: class {
func imageEditorModelDidChange() func imageEditorModelDidChange()
func imageEditorModelDidChange(changedItemIds: [String])
} }
// MARK: - // MARK: -
@ -360,6 +370,11 @@ public class ImageEditorModel: NSObject {
return contents.items() return contents.items()
} }
@objc
public func item(forId itemId: String) -> ImageEditorItem? {
return contents.item(forId: itemId)
}
@objc @objc
public func canUndo() -> Bool { public func canUndo() -> Bool {
return !undoStack.isEmpty return !undoStack.isEmpty
@ -382,6 +397,7 @@ public class ImageEditorModel: NSObject {
self.contents = undoOperation.contents self.contents = undoOperation.contents
// We could diff here and yield a more narrow change event.
delegate?.imageEditorModelDidChange() delegate?.imageEditorModelDidChange()
} }
@ -397,39 +413,46 @@ public class ImageEditorModel: NSObject {
self.contents = redoOperation.contents self.contents = redoOperation.contents
// We could diff here and yield a more narrow change event.
delegate?.imageEditorModelDidChange() delegate?.imageEditorModelDidChange()
} }
@objc @objc
public func append(item: ImageEditorItem) { public func append(item: ImageEditorItem) {
performAction { (newContents) in performAction({ (newContents) in
newContents.append(item: item) newContents.append(item: item)
} }, changedItemIds: [item.itemId])
} }
@objc @objc
public func replace(item: ImageEditorItem) { public func replace(item: ImageEditorItem,
performAction { (newContents) in suppressUndo: Bool = false) {
performAction({ (newContents) in
newContents.replace(item: item) newContents.replace(item: item)
} }, changedItemIds: [item.itemId],
suppressUndo: suppressUndo)
} }
@objc @objc
public func remove(item: ImageEditorItem) { public func remove(item: ImageEditorItem) {
performAction { (newContents) in performAction({ (newContents) in
newContents.remove(item: item) newContents.remove(item: item)
} }, changedItemIds: [item.itemId])
} }
private func performAction(action: (ImageEditorContents) -> Void) { private func performAction(_ action: (ImageEditorContents) -> Void,
let undoOperation = ImageEditorOperation(contents: contents) changedItemIds: [String],
undoStack.append(undoOperation) suppressUndo: Bool = false) {
redoStack.removeAll() if !suppressUndo {
let undoOperation = ImageEditorOperation(contents: contents)
undoStack.append(undoOperation)
redoStack.removeAll()
}
let newContents = contents.clone() let newContents = contents.clone()
action(newContents) action(newContents)
contents = newContents contents = newContents
delegate?.imageEditorModelDidChange() delegate?.imageEditorModelDidChange(changedItemIds: changedItemIds)
} }
} }

@ -29,8 +29,70 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
notImplemented() notImplemented()
} }
// MARK: - Buttons
private let undoButton = UIButton(type: .custom)
private let redoButton = UIButton(type: .custom)
@objc
public func addControls(to containerView: UIView) {
configure(button: undoButton,
label: NSLocalizedString("BUTTON_UNDO", comment: "Label for undo button."),
selector: #selector(didTapUndo(sender:)))
configure(button: redoButton,
label: NSLocalizedString("BUTTON_REDO", comment: "Label for redo button."),
selector: #selector(didTapRedo(sender:)))
let stackView = UIStackView(arrangedSubviews: [undoButton, redoButton])
stackView.axis = .vertical
stackView.alignment = .center
stackView.spacing = 10
containerView.addSubview(stackView)
stackView.autoAlignAxis(toSuperviewAxis: .horizontal)
stackView.autoPinTrailingToSuperviewMargin(withInset: 10)
updateButtons()
}
private func configure(button: UIButton,
label: String,
selector: Selector) {
button.setTitle(label, for: .normal)
button.setTitleColor(.white,
for: .normal)
button.setTitleColor(.gray,
for: .disabled)
button.titleLabel?.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight()
button.addTarget(self, action: selector, for: .touchUpInside)
}
private func updateButtons() {
undoButton.isEnabled = model.canUndo()
redoButton.isEnabled = model.canRedo()
}
// MARK: - Actions // MARK: - Actions
@objc func didTapUndo(sender: UIButton) {
Logger.verbose("")
guard model.canUndo() else {
owsFailDebug("Can't undo.")
return
}
model.undo()
}
@objc func didTapRedo(sender: UIButton) {
Logger.verbose("")
guard model.canRedo() else {
owsFailDebug("Can't redo.")
return
}
model.redo()
}
// These properties are non-empty while drawing a stroke. // These properties are non-empty while drawing a stroke.
private var currentStroke: ImageEditorStrokeItem? private var currentStroke: ImageEditorStrokeItem?
private var currentStrokeSamples = [ImageEditorStrokeItem.StrokeSample]() private var currentStrokeSamples = [ImageEditorStrokeItem.StrokeSample]()
@ -39,8 +101,6 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
public func handleTouchGesture(_ gestureRecognizer: UIGestureRecognizer) { public func handleTouchGesture(_ gestureRecognizer: UIGestureRecognizer) {
AssertIsOnMainThread() AssertIsOnMainThread()
Logger.verbose("\(NSStringForUIGestureRecognizerState(gestureRecognizer.state))")
let removeCurrentStroke = { let removeCurrentStroke = {
if let stroke = self.currentStroke { if let stroke = self.currentStroke {
self.model.remove(item: stroke) self.model.remove(item: stroke)
@ -69,9 +129,9 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
currentStrokeSamples.append(unitSampleForGestureLocation()) currentStrokeSamples.append(unitSampleForGestureLocation())
let stroke = ImageEditorStrokeItem(color: strokeColor, unitSamples: self.currentStrokeSamples, unitStrokeWidth: unitStrokeWidth) let stroke = ImageEditorStrokeItem(color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth)
self.model.append(item: stroke) model.append(item: stroke)
self.currentStroke = stroke currentStroke = stroke
case .changed, .ended: case .changed, .ended:
currentStrokeSamples.append(unitSampleForGestureLocation()) currentStrokeSamples.append(unitSampleForGestureLocation())
@ -84,13 +144,14 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
// Model items are immutable; we _replace_ the // Model items are immutable; we _replace_ the
// stroke item rather than modify it. // stroke item rather than modify it.
let stroke = ImageEditorStrokeItem(itemId: lastStroke.itemId, color: strokeColor, unitSamples: self.currentStrokeSamples, unitStrokeWidth: unitStrokeWidth) let stroke = ImageEditorStrokeItem(itemId: lastStroke.itemId, color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth)
self.model.replace(item: stroke) model.replace(item: stroke, suppressUndo: true)
self.currentStroke = stroke
if gestureRecognizer.state == .ended { if gestureRecognizer.state == .ended {
self.currentStroke = nil currentStroke = nil
self.currentStrokeSamples.removeAll() currentStrokeSamples.removeAll()
} else {
currentStroke = stroke
} }
default: default:
removeCurrentStroke() removeCurrentStroke()
@ -100,9 +161,15 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
// MARK: - ImageEditorModelDelegate // MARK: - ImageEditorModelDelegate
public func imageEditorModelDidChange() { public func imageEditorModelDidChange() {
// TODO: We eventually want to narrow our change events
// to reflect the specific item(s) which changed.
updateAllContent() updateAllContent()
updateButtons()
}
public func imageEditorModelDidChange(changedItemIds: [String]) {
updateContent(changedItemIds: changedItemIds)
updateButtons()
} }
// MARK: - Accessor Overrides // MARK: - Accessor Overrides
@ -125,33 +192,71 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
// MARK: - Content // MARK: - Content
var contentLayers = [CALayer]() var contentLayerMap = [String: CALayer]()
internal func updateAllContent() { internal func updateAllContent() {
AssertIsOnMainThread() AssertIsOnMainThread()
for layer in contentLayers { // Don't animate changes.
CATransaction.begin()
CATransaction.setDisableActions(true)
for layer in contentLayerMap.values {
layer.removeFromSuperlayer() layer.removeFromSuperlayer()
} }
contentLayers.removeAll() contentLayerMap.removeAll()
guard bounds.width > 0, if bounds.width > 0,
bounds.height > 0 else { bounds.height > 0 {
return
for item in model.items() {
guard let layer = ImageEditorView.layerForItem(item: item,
viewSize: bounds.size) else {
continue
}
self.layer.addSublayer(layer)
contentLayerMap[item.itemId] = layer
}
} }
CATransaction.commit()
}
internal func updateContent(changedItemIds: [String]) {
AssertIsOnMainThread()
// Don't animate changes. // Don't animate changes.
CATransaction.begin() CATransaction.begin()
CATransaction.setDisableActions(true) CATransaction.setDisableActions(true)
for item in model.items() { // Remove all changed items.
guard let layer = ImageEditorView.layerForItem(item: item, for itemId in changedItemIds {
viewSize: bounds.size) else { if let layer = contentLayerMap[itemId] {
continue layer.removeFromSuperlayer()
} }
contentLayerMap.removeValue(forKey: itemId)
}
if bounds.width > 0,
bounds.height > 0 {
// Create layers for inserted and updated items.
for itemId in changedItemIds {
guard let item = model.item(forId: itemId) else {
// Item was deleted.
continue
}
self.layer.addSublayer(layer) // Item was inserted or updated.
contentLayers.append(layer) guard let layer = ImageEditorView.layerForItem(item: item,
viewSize: bounds.size) else {
continue
}
self.layer.addSublayer(layer)
contentLayerMap[item.itemId] = layer
}
} }
CATransaction.commit() CATransaction.commit()

Loading…
Cancel
Save