Merge branch 'charlesmchen/imageEditor2'

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

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

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

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

@ -29,8 +29,70 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
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
@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.
private var currentStroke: ImageEditorStrokeItem?
private var currentStrokeSamples = [ImageEditorStrokeItem.StrokeSample]()
@ -39,8 +101,6 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
public func handleTouchGesture(_ gestureRecognizer: UIGestureRecognizer) {
AssertIsOnMainThread()
Logger.verbose("\(NSStringForUIGestureRecognizerState(gestureRecognizer.state))")
let removeCurrentStroke = {
if let stroke = self.currentStroke {
self.model.remove(item: stroke)
@ -69,9 +129,9 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
currentStrokeSamples.append(unitSampleForGestureLocation())
let stroke = ImageEditorStrokeItem(color: strokeColor, unitSamples: self.currentStrokeSamples, unitStrokeWidth: unitStrokeWidth)
self.model.append(item: stroke)
self.currentStroke = stroke
let stroke = ImageEditorStrokeItem(color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth)
model.append(item: stroke)
currentStroke = stroke
case .changed, .ended:
currentStrokeSamples.append(unitSampleForGestureLocation())
@ -84,13 +144,14 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
// Model items are immutable; we _replace_ the
// stroke item rather than modify it.
let stroke = ImageEditorStrokeItem(itemId: lastStroke.itemId, color: strokeColor, unitSamples: self.currentStrokeSamples, unitStrokeWidth: unitStrokeWidth)
self.model.replace(item: stroke)
self.currentStroke = stroke
let stroke = ImageEditorStrokeItem(itemId: lastStroke.itemId, color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth)
model.replace(item: stroke, suppressUndo: true)
if gestureRecognizer.state == .ended {
self.currentStroke = nil
self.currentStrokeSamples.removeAll()
currentStroke = nil
currentStrokeSamples.removeAll()
} else {
currentStroke = stroke
}
default:
removeCurrentStroke()
@ -100,9 +161,15 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
// MARK: - ImageEditorModelDelegate
public func imageEditorModelDidChange() {
// TODO: We eventually want to narrow our change events
// to reflect the specific item(s) which changed.
updateAllContent()
updateButtons()
}
public func imageEditorModelDidChange(changedItemIds: [String]) {
updateContent(changedItemIds: changedItemIds)
updateButtons()
}
// MARK: - Accessor Overrides
@ -125,33 +192,71 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
// MARK: - Content
var contentLayers = [CALayer]()
var contentLayerMap = [String: CALayer]()
internal func updateAllContent() {
AssertIsOnMainThread()
for layer in contentLayers {
// Don't animate changes.
CATransaction.begin()
CATransaction.setDisableActions(true)
for layer in contentLayerMap.values {
layer.removeFromSuperlayer()
}
contentLayers.removeAll()
contentLayerMap.removeAll()
guard bounds.width > 0,
bounds.height > 0 else {
return
if bounds.width > 0,
bounds.height > 0 {
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.
CATransaction.begin()
CATransaction.setDisableActions(true)
for item in model.items() {
guard let layer = ImageEditorView.layerForItem(item: item,
viewSize: bounds.size) else {
continue
// Remove all changed items.
for itemId in changedItemIds {
if let layer = contentLayerMap[itemId] {
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)
contentLayers.append(layer)
// Item was inserted or updated.
guard let layer = ImageEditorView.layerForItem(item: item,
viewSize: bounds.size) else {
continue
}
self.layer.addSublayer(layer)
contentLayerMap[item.itemId] = layer
}
}
CATransaction.commit()

Loading…
Cancel
Save