Sketch out image editor undo/redo.

pull/1/head
Matthew Chen 7 years ago
parent 57232683fc
commit 8704ffe93c

@ -7,7 +7,7 @@
<key>CarthageVersion</key>
<string>0.31.2</string>
<key>OSXVersion</key>
<string>10.14.1</string>
<string>10.14.2</string>
<key>WebRTCCommit</key>
<string>ca71024b4993ba95e3e6b8d0758004cffc54ddaf M70</string>
</dict>

@ -6,6 +6,14 @@ import XCTest
@testable import Signal
@testable import SignalMessaging
extension ImageEditorModel {
func itemIds() -> [String] {
return items().map { (item) in
item.itemId
}
}
}
class ImageEditorTest: SignalBaseTest {
override func setUp() {
@ -19,7 +27,10 @@ class ImageEditorTest: SignalBaseTest {
func testImageEditorContents() {
let contents = ImageEditorContents()
let item = ImageEditorItem()
XCTAssertEqual(0, contents.itemMap.count)
XCTAssertEqual(0, contents.itemIds.count)
let item = ImageEditorItem(itemType: .test)
contents.append(item: item)
XCTAssertEqual(1, contents.itemMap.count)
XCTAssertEqual(1, contents.itemIds.count)
@ -36,7 +47,7 @@ class ImageEditorTest: SignalBaseTest {
XCTAssertEqual(0, contentsCopy.itemMap.count)
XCTAssertEqual(0, contentsCopy.itemIds.count)
let modifiedItem = ImageEditorItem(itemId: item.itemId)
let modifiedItem = ImageEditorItem(itemId: item.itemId, itemType: item.itemType)
contents.replace(item: modifiedItem)
XCTAssertEqual(1, contents.itemMap.count)
XCTAssertEqual(1, contents.itemIds.count)
@ -71,11 +82,12 @@ class ImageEditorTest: SignalBaseTest {
XCTAssertFalse(imageEditor.canRedo())
XCTAssertEqual(0, imageEditor.itemCount())
let itemA = ImageEditorItem()
let itemA = ImageEditorItem(itemType: .test)
imageEditor.append(item: itemA)
XCTAssertTrue(imageEditor.canUndo())
XCTAssertFalse(imageEditor.canRedo())
XCTAssertEqual(1, imageEditor.itemCount())
XCTAssertEqual([itemA.itemId], imageEditor.itemIds())
imageEditor.undo()
XCTAssertFalse(imageEditor.canUndo())
@ -86,27 +98,31 @@ class ImageEditorTest: SignalBaseTest {
XCTAssertTrue(imageEditor.canUndo())
XCTAssertFalse(imageEditor.canRedo())
XCTAssertEqual(1, imageEditor.itemCount())
XCTAssertEqual([itemA.itemId], imageEditor.itemIds())
imageEditor.undo()
XCTAssertFalse(imageEditor.canUndo())
XCTAssertTrue(imageEditor.canRedo())
XCTAssertEqual(0, imageEditor.itemCount())
let itemB = ImageEditorItem()
let itemB = ImageEditorItem(itemType: .test)
imageEditor.append(item: itemB)
XCTAssertTrue(imageEditor.canUndo())
XCTAssertFalse(imageEditor.canRedo())
XCTAssertEqual(1, imageEditor.itemCount())
XCTAssertEqual([itemB.itemId], imageEditor.itemIds())
let itemC = ImageEditorItem()
let itemC = ImageEditorItem(itemType: .test)
imageEditor.append(item: itemC)
XCTAssertTrue(imageEditor.canUndo())
XCTAssertFalse(imageEditor.canRedo())
XCTAssertEqual(2, imageEditor.itemCount())
XCTAssertEqual([itemB.itemId, itemC.itemId], imageEditor.itemIds())
imageEditor.undo()
XCTAssertTrue(imageEditor.canUndo())
XCTAssertTrue(imageEditor.canRedo())
XCTAssertEqual(1, imageEditor.itemCount())
XCTAssertEqual([itemB.itemId], imageEditor.itemIds())
}
}

@ -9,23 +9,36 @@ import UIKit
case invalidInput
}
@objc
public enum ImageEditorItemType: Int {
case test
}
// MARK: -
// Instances of ImageEditorItem should be treated
// as immutable, once configured.
@objc
public class ImageEditorItem: NSObject {
@objc
public let itemId: String
@objc
public override required init() {
public let itemType: ImageEditorItemType
@objc
public required init(itemType: ImageEditorItemType) {
self.itemId = UUID().uuidString
self.itemType = itemType
super.init()
}
@objc
public init(itemId: String) {
public required init(itemId: String,
itemType: ImageEditorItemType) {
self.itemId = itemId
self.itemType = itemType
super.init()
}
@ -33,9 +46,14 @@ public class ImageEditorItem: NSObject {
// MARK: -
// Instances of ImageEditorContents should be treated
// as immutable, once configured.
public class ImageEditorContents: NSObject {
// This represents the current state of each item.
var itemMap = [String: ImageEditorItem]()
// This represents the back-to-front ordering of the items.
var itemIds = [String]()
@objc
@ -50,6 +68,8 @@ public class ImageEditorContents: NSObject {
self.itemIds = itemIds
}
// Since the contents are immutable, we only modify copies
// made with this method.
@objc
public func clone() -> ImageEditorContents {
return ImageEditorContents(itemMap: itemMap, itemIds: itemIds)
@ -120,11 +140,29 @@ public class ImageEditorContents: NSObject {
}
return itemIds.count
}
@objc
public func items() -> [ImageEditorItem] {
var items = [ImageEditorItem]()
for itemId in itemIds {
guard let item = self.itemMap[itemId] else {
owsFailDebug("Missing item")
continue
}
items.append(item)
}
return items
}
}
// MARK: -
// Used to represent undo/redo operations.
//
// Because the image editor's "contents" and "items"
// are immutable, these operations simply take a
// snapshot of the current contents which can be used
// (multiple times) to preserve/restore editor state.
private class ImageEditorOperation: NSObject {
let contents: ImageEditorContents
@ -149,6 +187,10 @@ public class ImageEditorModel: NSObject {
private var undoStack = [ImageEditorOperation]()
private var redoStack = [ImageEditorOperation]()
// We don't want to allow editing of images if:
//
// * They are invalid.
// * We can't determine their size / aspect-ratio.
@objc
public required init(srcImagePath: String) throws {
self.srcImagePath = srcImagePath
@ -179,6 +221,11 @@ public class ImageEditorModel: NSObject {
return contents.itemCount()
}
@objc
public func items() -> [ImageEditorItem] {
return contents.items()
}
@objc
public func canUndo() -> Bool {
return !undoStack.isEmpty

Loading…
Cancel
Save