Pinch to change text size in image editor text tool.

This commit is contained in:
Matthew Chen 2019-03-13 10:15:23 -04:00
parent b86ff1425e
commit 88c07fc534
8 changed files with 143 additions and 34 deletions

View File

@ -15,6 +15,7 @@
34080F0022282C880087E99F /* AttachmentCaptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34080EFF22282C880087E99F /* AttachmentCaptionViewController.swift */; };
34080F02222853E30087E99F /* ImageEditorBrushViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34080F01222853E30087E99F /* ImageEditorBrushViewController.swift */; };
34080F04222858DC0087E99F /* OWSViewController+ImageEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34080F03222858DC0087E99F /* OWSViewController+ImageEditor.swift */; };
340872BF22393CFA00CB25B0 /* UIGestureRecognizer+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872BE22393CF900CB25B0 /* UIGestureRecognizer+OWS.swift */; };
340B02BA1FA0D6C700F9CFEC /* ConversationViewItemTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 340B02B91FA0D6C700F9CFEC /* ConversationViewItemTest.m */; };
340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */; };
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */; };
@ -644,6 +645,7 @@
34080EFF22282C880087E99F /* AttachmentCaptionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentCaptionViewController.swift; sourceTree = "<group>"; };
34080F01222853E30087E99F /* ImageEditorBrushViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageEditorBrushViewController.swift; sourceTree = "<group>"; };
34080F03222858DC0087E99F /* OWSViewController+ImageEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OWSViewController+ImageEditor.swift"; sourceTree = "<group>"; };
340872BE22393CF900CB25B0 /* UIGestureRecognizer+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+OWS.swift"; sourceTree = "<group>"; };
340B02B61F9FD31800F9CFEC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = translations/he.lproj/Localizable.strings; sourceTree = "<group>"; };
340B02B91FA0D6C700F9CFEC /* ConversationViewItemTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversationViewItemTest.m; sourceTree = "<group>"; };
340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsOptionsViewController.m; sourceTree = "<group>"; };
@ -1585,12 +1587,12 @@
4C948FF62146EB4800349F0D /* BlockListCache.swift */,
343D3D991E9283F100165CA4 /* BlockListUIUtils.h */,
343D3D9A1E9283F100165CA4 /* BlockListUIUtils.m */,
451777C71FD61554001225FF /* FullTextSearcher.swift */,
3466087120E550F300AFFE73 /* ConversationStyle.swift */,
34480B4D1FD0A7A300BC14EF /* DebugLogger.h */,
34480B4E1FD0A7A300BC14EF /* DebugLogger.m */,
348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */,
344F248C2007CCD600CFB4F4 /* DisplayableText.swift */,
451777C71FD61554001225FF /* FullTextSearcher.swift */,
346129AC1FD1F34E00532771 /* ImageCache.swift */,
34BEDB1421C80BC9007B0EAE /* OWSAnyTouchGestureRecognizer.h */,
34BEDB1521C80BCA007B0EAE /* OWSAnyTouchGestureRecognizer.m */,
@ -1617,6 +1619,7 @@
45360B8C1F9521F800FA666C /* Searcher.swift */,
346129BD1FD2068600532771 /* ThreadUtil.h */,
346129BE1FD2068600532771 /* ThreadUtil.m */,
340872BE22393CF900CB25B0 /* UIGestureRecognizer+OWS.swift */,
4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */,
B97940251832BD2400BD66CB /* UIUtil.h */,
B97940261832BD2400BD66CB /* UIUtil.m */,
@ -3416,6 +3419,7 @@
346941A3215D2EE400B5BFAD /* Theme.m in Sources */,
4C23A5F2215C4ADE00534937 /* SheetViewController.swift in Sources */,
34BBC84D220B2D0800857249 /* ImageEditorPinchGestureRecognizer.swift in Sources */,
340872BF22393CFA00CB25B0 /* UIGestureRecognizer+OWS.swift in Sources */,
34080F02222853E30087E99F /* ImageEditorBrushViewController.swift in Sources */,
34AC0A14211B39EA00997B47 /* ContactCellView.m in Sources */,
34AC0A15211B39EA00997B47 /* ContactsViewHelper.m in Sources */,

View File

@ -214,7 +214,6 @@ class ImageEditorCropViewController: OWSViewController {
}
public func updateNavigationBar() {
// TODO: Change this asset.
let resetButton = navigationBarButton(imageName: "image_editor_undo",
selector: #selector(didTapReset(sender:)))
let doneButton = navigationBarButton(imageName: "image_editor_checkmark_full",

View File

@ -54,6 +54,10 @@ public class ImageEditorColor: NSObject {
return color.cgColor
})
}
static func ==(left: ImageEditorColor, right: ImageEditorColor) -> Bool {
return left.palettePhase.fuzzyEquals(right.palettePhase)
}
}
// MARK: -

View File

@ -131,42 +131,54 @@ public class ImageEditorTextItem: ImageEditorItem {
}
@objc
public func copy(withUnitCenter newUnitCenter: CGPoint) -> ImageEditorTextItem {
public func copy(unitCenter: CGPoint) -> ImageEditorTextItem {
return ImageEditorTextItem(itemId: itemId,
text: text,
color: color,
font: font,
fontReferenceImageWidth: fontReferenceImageWidth,
unitCenter: newUnitCenter,
unitCenter: unitCenter,
unitWidth: unitWidth,
rotationRadians: rotationRadians,
scaling: scaling)
}
@objc
public func copy(withUnitCenter newUnitCenter: CGPoint,
scaling newScaling: CGFloat,
rotationRadians newRotationRadians: CGFloat) -> ImageEditorTextItem {
public func copy(scaling: CGFloat,
rotationRadians: CGFloat) -> ImageEditorTextItem {
return ImageEditorTextItem(itemId: itemId,
text: text,
color: color,
font: font,
fontReferenceImageWidth: fontReferenceImageWidth,
unitCenter: newUnitCenter,
unitCenter: unitCenter,
unitWidth: unitWidth,
rotationRadians: newRotationRadians,
scaling: newScaling)
rotationRadians: rotationRadians,
scaling: scaling)
}
@objc
public func copy(withUnitCenter newUnitCenter: CGPoint, unitWidth newUnitWidth: CGFloat) -> ImageEditorTextItem {
public func copy(unitWidth: CGFloat) -> ImageEditorTextItem {
return ImageEditorTextItem(itemId: itemId,
text: text,
color: color,
font: font,
fontReferenceImageWidth: fontReferenceImageWidth,
unitCenter: newUnitCenter,
unitWidth: newUnitWidth,
unitCenter: unitCenter,
unitWidth: unitWidth,
rotationRadians: rotationRadians,
scaling: scaling)
}
@objc
public func copy(font: UIFont) -> ImageEditorTextItem {
return ImageEditorTextItem(itemId: itemId,
text: text,
color: color,
font: font,
fontReferenceImageWidth: fontReferenceImageWidth,
unitCenter: unitCenter,
unitWidth: unitWidth,
rotationRadians: rotationRadians,
scaling: scaling)
}
@ -174,4 +186,16 @@ public class ImageEditorTextItem: ImageEditorItem {
public override func outputScale() -> CGFloat {
return scaling
}
static func ==(left: ImageEditorTextItem, right: ImageEditorTextItem) -> Bool {
return (left.text == right.text &&
left.color == right.color &&
left.font.fontName == right.font.fontName &&
left.font.pointSize.fuzzyEquals(right.font.pointSize) &&
left.fontReferenceImageWidth.fuzzyEquals(right.fontReferenceImageWidth) &&
left.unitCenter.fuzzyEquals(right.unitCenter) &&
left.unitWidth.fuzzyEquals(right.unitWidth) &&
left.rotationRadians.fuzzyEquals(right.rotationRadians) &&
left.scaling.fuzzyEquals(right.scaling))
}
}

View File

@ -92,7 +92,8 @@ private class VAlignTextView: UITextView {
@objc
public protocol ImageEditorTextViewControllerDelegate: class {
func textEditDidComplete(textItem: ImageEditorTextItem, text: String?, color: ImageEditorColor)
func textEditDidComplete(textItem: ImageEditorTextItem)
func textEditDidDelete(textItem: ImageEditorTextItem)
func textEditDidCancel()
}
@ -195,6 +196,10 @@ public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDel
// This will determine the text view's size.
paletteView.autoPinEdge(.leading, to: .trailing, of: textView, withOffset: 0)
let pinchGestureRecognizer = ImageEditorPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture(_:)))
pinchGestureRecognizer.referenceView = view
view.addGestureRecognizer(pinchGestureRecognizer)
updateNavigationBar()
}
@ -230,6 +235,35 @@ public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDel
updateNavigationBar(navigationBarItems: navigationBarItems)
}
// MARK: - Pinch Gesture
private var pinchFontStart: UIFont?
@objc
public func handlePinchGesture(_ gestureRecognizer: ImageEditorPinchGestureRecognizer) {
AssertIsOnMainThread()
switch gestureRecognizer.state {
case .began:
pinchFontStart = textView.font
case .changed, .ended:
guard let pinchFontStart = pinchFontStart else {
return
}
var pointSize: CGFloat = pinchFontStart.pointSize
if gestureRecognizer.pinchStateLast.distance > 0 {
pointSize *= gestureRecognizer.pinchStateLast.distance / gestureRecognizer.pinchStateStart.distance
}
let minPointSize: CGFloat = 12
let maxPointSize: CGFloat = 64
pointSize = max(minPointSize, min(maxPointSize, pointSize))
let font = pinchFontStart.withSize(pointSize)
textView.font = font
default:
pinchFontStart = nil
}
}
// MARK: - Events
@objc func didTapUndo(sender: UIButton) {
@ -268,14 +302,39 @@ public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDel
imageSize: model.srcImageSizePixels,
transform: model.currentTransform())
let unitWidth = textView.width() / imageFrame.width
newTextItem = textItem.copy(withUnitCenter: textCenterImageUnit, unitWidth: unitWidth)
newTextItem = textItem.copy(unitCenter: textCenterImageUnit).copy(unitWidth: unitWidth)
}
var font = textItem.font
if let newFont = textView.font {
font = newFont
} else {
owsFailDebug("Missing font.")
}
newTextItem = newTextItem.copy(font: font)
guard let text = textView.text?.ows_stripped(),
text.count > 0 else {
self.delegate?.textEditDidDelete(textItem: textItem)
self.dismiss(animated: false) {
// Do nothing.
}
return
}
newTextItem = newTextItem.copy(withText: text, color: paletteView.selectedValue)
// Hide the text view immediately to avoid animation glitches in the dismiss transition.
textView.isHidden = true
self.delegate?.textEditDidComplete(textItem: newTextItem, text: textView.text, color: paletteView.selectedValue)
if textItem == newTextItem {
// No changes were made. Cancel to avoid dirtying the undo stack.
self.delegate?.textEditDidCancel()
} else {
self.delegate?.textEditDidComplete(textItem: newTextItem)
}
self.dismiss(animated: false) {
// Do nothing.

View File

@ -260,9 +260,8 @@ public class ImageEditorView: UIView {
let newRotationRadians = textItem.rotationRadians + gestureRecognizer.pinchStateLast.angleRadians - gestureRecognizer.pinchStateStart.angleRadians
let newItem = textItem.copy(withUnitCenter: unitCenter,
scaling: newScaling,
rotationRadians: newRotationRadians)
let newItem = textItem.copy(unitCenter: unitCenter).copy(scaling: newScaling,
rotationRadians: newRotationRadians)
if pinchHasChanged {
model.replace(item: newItem, suppressUndo: true)
@ -348,7 +347,7 @@ public class ImageEditorView: UIView {
transform: self.model.currentTransform())
let gestureDeltaImageUnit = gestureNowImageUnit.minus(gestureStartImageUnit)
let unitCenter = CGPointClamp01(movingTextStartUnitCenter.plus(gestureDeltaImageUnit))
let newItem = textItem.copy(withUnitCenter: unitCenter)
let newItem = textItem.copy(unitCenter: unitCenter)
if movingTextHasMoved {
model.replace(item: newItem, suppressUndo: true)
@ -515,26 +514,25 @@ extension ImageEditorView: ImageEditorModelObserver {
extension ImageEditorView: ImageEditorTextViewControllerDelegate {
public func textEditDidComplete(textItem: ImageEditorTextItem, text: String?, color: ImageEditorColor) {
public func textEditDidComplete(textItem: ImageEditorTextItem) {
AssertIsOnMainThread()
guard let text = text?.ows_stripped(),
text.count > 0 else {
if model.has(itemForId: textItem.itemId) {
model.remove(item: textItem)
}
return
}
// Model items are immutable; we _replace_ the item rather than modify it.
let newItem = textItem.copy(withText: text, color: color)
if model.has(itemForId: textItem.itemId) {
model.replace(item: newItem, suppressUndo: false)
model.replace(item: textItem, suppressUndo: false)
} else {
model.append(item: newItem)
model.append(item: textItem)
}
self.currentColor = color
self.currentColor = textItem.color
}
public func textEditDidDelete(textItem: ImageEditorTextItem) {
AssertIsOnMainThread()
if model.has(itemForId: textItem.itemId) {
model.remove(item: textItem)
}
}
public func textEditDidCancel() {

View File

@ -145,6 +145,10 @@ public extension CGFloat {
}
public static let halfPi: CGFloat = CGFloat.pi * 0.5
public func fuzzyEquals(_ other: CGFloat, tolerance: CGFloat = 0.001) -> Bool {
return abs(self - other) < tolerance
}
}
public extension Int {
@ -213,6 +217,11 @@ public extension CGPoint {
public func applyingInverse(_ transform: CGAffineTransform) -> CGPoint {
return applying(transform.inverted())
}
public func fuzzyEquals(_ other: CGPoint, tolerance: CGFloat = 0.001) -> Bool {
return (x.fuzzyEquals(other.x, tolerance: tolerance) &&
y.fuzzyEquals(other.y, tolerance: tolerance))
}
}
public extension CGRect {

View File

@ -0,0 +1,12 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
extension UIGestureRecognizer {
@objc
public var stateString: String {
return NSStringForUIGestureRecognizerState(state)
}
}