mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Stickers editor
This commit is contained in:
parent
c7035b2621
commit
ccb2415473
@ -156,6 +156,12 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.eachView { entityView in
|
||||||
|
entityView.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override func layoutSubviews() {
|
public override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
@ -501,6 +507,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
self.hasSelectionChanged(false)
|
self.hasSelectionChanged(false)
|
||||||
view.selectionView?.removeFromSuperview()
|
view.selectionView?.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
view.reset()
|
||||||
if animated {
|
if animated {
|
||||||
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in
|
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in
|
||||||
view?.removeFromSuperview()
|
view?.removeFromSuperview()
|
||||||
@ -539,6 +546,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
if view.entity.isMedia {
|
if view.entity.isMedia {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
view.reset()
|
||||||
if let selectionView = view.selectionView {
|
if let selectionView = view.selectionView {
|
||||||
selectionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak selectionView] _ in
|
selectionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak selectionView] _ in
|
||||||
selectionView?.removeFromSuperview()
|
selectionView?.removeFromSuperview()
|
||||||
@ -557,6 +565,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
if view.entity.isMedia {
|
if view.entity.isMedia {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
view.reset()
|
||||||
view.selectionView?.removeFromSuperview()
|
view.selectionView?.removeFromSuperview()
|
||||||
view.removeFromSuperview()
|
view.removeFromSuperview()
|
||||||
}
|
}
|
||||||
@ -946,6 +955,12 @@ public class DrawingEntityView: UIView {
|
|||||||
return self.bounds
|
return self.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reset() {
|
||||||
|
self.onSnapUpdated = { _, _ in }
|
||||||
|
self.onPositionUpdated = { _ in }
|
||||||
|
self.onInteractionUpdated = { _ in }
|
||||||
|
}
|
||||||
|
|
||||||
func animateInsertion() {
|
func animateInsertion() {
|
||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
|
||||||
|
@ -110,7 +110,6 @@ public final class DrawingLocationEntityView: DrawingEntityView, UITextViewDeleg
|
|||||||
|
|
||||||
private var textSize: CGSize = .zero
|
private var textSize: CGSize = .zero
|
||||||
public override func sizeThatFits(_ size: CGSize) -> CGSize {
|
public override func sizeThatFits(_ size: CGSize) -> CGSize {
|
||||||
self.textView.setNeedsLayersUpdate()
|
|
||||||
var result = self.textView.sizeThatFits(CGSize(width: self.locationEntity.width, height: .greatestFiniteMagnitude))
|
var result = self.textView.sizeThatFits(CGSize(width: self.locationEntity.width, height: .greatestFiniteMagnitude))
|
||||||
self.textSize = result
|
self.textSize = result
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
return self.entity as! DrawingTextEntity
|
return self.entity as! DrawingTextEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
let blurredBackgroundView: BlurredBackgroundView
|
// let blurredBackgroundView: BlurredBackgroundView
|
||||||
let textView: DrawingTextView
|
let textView: DrawingTextView
|
||||||
var customEmojiContainerView: CustomEmojiContainerView?
|
var customEmojiContainerView: CustomEmojiContainerView?
|
||||||
var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
|
var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
|
||||||
@ -37,9 +37,9 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
var replaceWithAnimatedImage: (Data, UIImage) -> Void = { _, _ in }
|
var replaceWithAnimatedImage: (Data, UIImage) -> Void = { _, _ in }
|
||||||
|
|
||||||
init(context: AccountContext, entity: DrawingTextEntity) {
|
init(context: AccountContext, entity: DrawingTextEntity) {
|
||||||
self.blurredBackgroundView = BlurredBackgroundView(color: UIColor(white: 0.0, alpha: 0.25), enableBlur: true)
|
// self.blurredBackgroundView = BlurredBackgroundView(color: UIColor(white: 0.0, alpha: 0.25), enableBlur: true)
|
||||||
self.blurredBackgroundView.clipsToBounds = true
|
// self.blurredBackgroundView.clipsToBounds = true
|
||||||
self.blurredBackgroundView.isHidden = true
|
// self.blurredBackgroundView.isHidden = true
|
||||||
|
|
||||||
self.textView = DrawingTextView(frame: .zero)
|
self.textView = DrawingTextView(frame: .zero)
|
||||||
self.textView.clipsToBounds = false
|
self.textView.clipsToBounds = false
|
||||||
@ -62,7 +62,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
super.init(context: context, entity: entity)
|
super.init(context: context, entity: entity)
|
||||||
|
|
||||||
self.textView.delegate = self
|
self.textView.delegate = self
|
||||||
self.addSubview(self.blurredBackgroundView)
|
// self.addSubview(self.blurredBackgroundView)
|
||||||
self.addSubview(self.textView)
|
self.addSubview(self.textView)
|
||||||
|
|
||||||
self.emojiViewProvider = { emoji in
|
self.emojiViewProvider = { emoji in
|
||||||
@ -420,7 +420,6 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override func sizeThatFits(_ size: CGSize) -> CGSize {
|
public override func sizeThatFits(_ size: CGSize) -> CGSize {
|
||||||
self.textView.setNeedsLayersUpdate()
|
|
||||||
var result = self.textView.sizeThatFits(CGSize(width: self.textEntity.width, height: .greatestFiniteMagnitude))
|
var result = self.textView.sizeThatFits(CGSize(width: self.textEntity.width, height: .greatestFiniteMagnitude))
|
||||||
result.width = max(224.0, ceil(result.width) + 20.0)
|
result.width = max(224.0, ceil(result.width) + 20.0)
|
||||||
result.height = ceil(result.height);
|
result.height = ceil(result.height);
|
||||||
@ -458,7 +457,6 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
let range = NSMakeRange(0, text.length)
|
let range = NSMakeRange(0, text.length)
|
||||||
let fontSize = self.displayFontSize
|
let fontSize = self.displayFontSize
|
||||||
|
|
||||||
self.textView.hasTextLayers = [.typing, .wiggle].contains(self.textEntity.animation)
|
|
||||||
self.textView.drawingLayoutManager.textContainers.first?.lineFragmentPadding = floor(fontSize * 0.24)
|
self.textView.drawingLayoutManager.textContainers.first?.lineFragmentPadding = floor(fontSize * 0.24)
|
||||||
|
|
||||||
if let (font, name) = availableFonts[text.string.lowercased()] {
|
if let (font, name) = availableFonts[text.string.lowercased()] {
|
||||||
@ -498,11 +496,8 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
guard let visualText = text.mutableCopy() as? NSMutableAttributedString else {
|
guard let visualText = text.mutableCopy() as? NSMutableAttributedString else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.textView.hasTextLayers {
|
|
||||||
text.addAttribute(.foregroundColor, value: UIColor.clear, range: range)
|
|
||||||
} else {
|
|
||||||
text.addAttribute(.foregroundColor, value: textColor, range: range)
|
text.addAttribute(.foregroundColor, value: textColor, range: range)
|
||||||
}
|
|
||||||
visualText.addAttribute(.foregroundColor, value: textColor, range: range)
|
visualText.addAttribute(.foregroundColor, value: textColor, range: range)
|
||||||
|
|
||||||
text.enumerateAttributes(in: range) { attributes, subrange, _ in
|
text.enumerateAttributes(in: range) { attributes, subrange, _ in
|
||||||
@ -522,65 +517,6 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
if keepSelectedRange {
|
if keepSelectedRange {
|
||||||
self.textView.selectedRange = previousRange
|
self.textView.selectedRange = previousRange
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.textView.hasTextLayers {
|
|
||||||
self.textView.onLayersUpdate = { [weak self] in
|
|
||||||
self?.updateTextAnimations()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.updateTextAnimations()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateTextAnimations() {
|
|
||||||
for layer in self.textView.characterLayers {
|
|
||||||
layer.removeAllAnimations()
|
|
||||||
}
|
|
||||||
self.textView.layer.removeAllAnimations()
|
|
||||||
|
|
||||||
guard self.textView.characterLayers.count > 0 || self.textEntity.animation == .zoomIn else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch self.textEntity.animation {
|
|
||||||
case .typing:
|
|
||||||
let delta: CGFloat = 1.0 / CGFloat(self.textView.characterLayers.count + 3)
|
|
||||||
let duration = Double(self.textView.characterLayers.count + 3) * 0.28
|
|
||||||
var offset = delta
|
|
||||||
for layer in self.textView.characterLayers {
|
|
||||||
let animation = CAKeyframeAnimation(keyPath: "opacity")
|
|
||||||
animation.calculationMode = .discrete
|
|
||||||
animation.values = [0.0, 1.0]
|
|
||||||
animation.keyTimes = [0.0, offset as NSNumber, 1.0]
|
|
||||||
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
|
||||||
animation.duration = duration
|
|
||||||
animation.repeatCount = .infinity
|
|
||||||
layer.add(animation, forKey: "opacity")
|
|
||||||
offset += delta
|
|
||||||
}
|
|
||||||
case .wiggle:
|
|
||||||
for layer in self.textView.characterLayers {
|
|
||||||
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
|
|
||||||
animation.fromValue = (-.pi / 10.0) as NSNumber
|
|
||||||
animation.toValue = (.pi / 10.0) as NSNumber
|
|
||||||
animation.autoreverses = true
|
|
||||||
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
|
||||||
animation.duration = 0.6
|
|
||||||
animation.repeatCount = .infinity
|
|
||||||
layer.add(animation, forKey: "transform.rotation.z")
|
|
||||||
}
|
|
||||||
case .zoomIn:
|
|
||||||
let animation = CABasicAnimation(keyPath: "transform.scale")
|
|
||||||
animation.fromValue = 0.001 as NSNumber
|
|
||||||
animation.toValue = 1.0 as NSNumber
|
|
||||||
animation.autoreverses = true
|
|
||||||
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
|
||||||
animation.duration = 0.8
|
|
||||||
animation.repeatCount = .infinity
|
|
||||||
self.textView.layer.add(animation, forKey: "transform.scale")
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func update(animated: Bool = false) {
|
public override func update(animated: Bool = false) {
|
||||||
@ -687,30 +623,6 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
|||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPresentationRenderImage() -> UIImage? {
|
|
||||||
let rect = self.bounds
|
|
||||||
UIGraphicsBeginImageContextWithOptions(rect.size, false, 1.0)
|
|
||||||
if let context = UIGraphicsGetCurrentContext() {
|
|
||||||
for layer in self.textView.characterLayers {
|
|
||||||
if let presentation = layer.presentation() {
|
|
||||||
context.saveGState()
|
|
||||||
context.translateBy(x: presentation.position.x - presentation.bounds.width / 2.0, y: 0.0)
|
|
||||||
if let rotation = (presentation.value(forKeyPath: "transform.rotation.z") as? NSNumber)?.floatValue {
|
|
||||||
context.translateBy(x: presentation.bounds.width / 2.0, y: presentation.bounds.height)
|
|
||||||
context.rotate(by: CGFloat(rotation))
|
|
||||||
context.translateBy(x: -presentation.bounds.width / 2.0, y: -presentation.bounds.height)
|
|
||||||
}
|
|
||||||
presentation.render(in: context)
|
|
||||||
context.restoreGState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//self.textView.drawHierarchy(in: rect, afterScreenUpdates: true)
|
|
||||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
|
||||||
UIGraphicsEndImageContext()
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRenderSubEntities() -> [DrawingEntity] {
|
func getRenderSubEntities() -> [DrawingEntity] {
|
||||||
let textSize = self.textView.bounds.size
|
let textSize = self.textView.bounds.size
|
||||||
let textPosition = self.textEntity.position
|
let textPosition = self.textEntity.position
|
||||||
@ -1236,8 +1148,6 @@ final class SimpleTextLayer: CATextLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class DrawingTextView: UITextView, NSLayoutManagerDelegate {
|
final class DrawingTextView: UITextView, NSLayoutManagerDelegate {
|
||||||
var characterLayers: [CALayer] = []
|
|
||||||
|
|
||||||
var drawingLayoutManager: DrawingTextLayoutManager {
|
var drawingLayoutManager: DrawingTextLayoutManager {
|
||||||
return self.layoutManager as! DrawingTextLayoutManager
|
return self.layoutManager as! DrawingTextLayoutManager
|
||||||
}
|
}
|
||||||
@ -1307,7 +1217,6 @@ final class DrawingTextView: UITextView, NSLayoutManagerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasTextLayers = false
|
|
||||||
var visualText: NSAttributedString?
|
var visualText: NSAttributedString?
|
||||||
|
|
||||||
init(frame: CGRect) {
|
init(frame: CGRect) {
|
||||||
@ -1357,15 +1266,8 @@ final class DrawingTextView: UITextView, NSLayoutManagerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var onLayoutUpdate: (() -> Void)?
|
var onLayoutUpdate: (() -> Void)?
|
||||||
var onLayersUpdate: (() -> Void)?
|
|
||||||
|
|
||||||
private var needsLayersUpdate = false
|
|
||||||
func setNeedsLayersUpdate() {
|
|
||||||
self.needsLayersUpdate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) {
|
func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) {
|
||||||
self.updateCharLayers()
|
|
||||||
if layoutFinishedFlag {
|
if layoutFinishedFlag {
|
||||||
if let onLayoutUpdate = self.onLayoutUpdate {
|
if let onLayoutUpdate = self.onLayoutUpdate {
|
||||||
self.onLayoutUpdate = nil
|
self.onLayoutUpdate = nil
|
||||||
@ -1374,42 +1276,6 @@ final class DrawingTextView: UITextView, NSLayoutManagerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCharLayers() {
|
|
||||||
for layer in self.characterLayers {
|
|
||||||
layer.removeFromSuperlayer()
|
|
||||||
}
|
|
||||||
self.characterLayers = []
|
|
||||||
|
|
||||||
guard let attributedString = self.visualText, self.hasTextLayers else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let wordRange = NSMakeRange(0, attributedString.length)
|
|
||||||
|
|
||||||
var index = wordRange.location
|
|
||||||
while index < wordRange.location + wordRange.length {
|
|
||||||
let glyphRange = NSMakeRange(index, 1)
|
|
||||||
let characterRange = self.layoutManager.characterRange(forGlyphRange: glyphRange, actualGlyphRange:nil)
|
|
||||||
var glyphRect = self.layoutManager.boundingRect(forGlyphRange: glyphRange, in: self.textContainer)
|
|
||||||
//let location = self.layoutManager.location(forGlyphAt: index)
|
|
||||||
|
|
||||||
glyphRect.origin.y += glyphRect.height / 2.0 //location.y - (glyphRect.height / 2.0);
|
|
||||||
let textLayer = SimpleTextLayer()
|
|
||||||
textLayer.contentsScale = 1.0
|
|
||||||
textLayer.frame = glyphRect
|
|
||||||
textLayer.string = attributedString.attributedSubstring(from: characterRange)
|
|
||||||
textLayer.anchorPoint = CGPoint(x: 0.5, y: 1.0)
|
|
||||||
|
|
||||||
self.layer.addSublayer(textLayer)
|
|
||||||
self.characterLayers.append(textLayer)
|
|
||||||
|
|
||||||
let stepGlyphRange = self.layoutManager.glyphRange(forCharacterRange: characterRange, actualCharacterRange:nil)
|
|
||||||
index += stepGlyphRange.length
|
|
||||||
}
|
|
||||||
|
|
||||||
self.onLayersUpdate?()
|
|
||||||
}
|
|
||||||
|
|
||||||
var onPaste: () -> Bool = { return true }
|
var onPaste: () -> Bool = { return true }
|
||||||
override func paste(_ sender: Any?) {
|
override func paste(_ sender: Any?) {
|
||||||
if !self.text.isEmpty || self.onPaste() {
|
if !self.text.isEmpty || self.onPaste() {
|
||||||
|
@ -2225,6 +2225,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let camera = self.controllerNode.modernCamera {
|
||||||
|
if let cameraView = self.controllerNode.modernCameraView {
|
||||||
|
cameraView.isEnabled = false
|
||||||
|
}
|
||||||
|
camera.stopCapture(invalidate: true)
|
||||||
|
}
|
||||||
|
|
||||||
super.dismiss(completion: completion)
|
super.dismiss(completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -578,7 +578,9 @@ final class EmojiStickerAccessoryNode: SparseNode, PeekControllerAccessoryNode {
|
|||||||
self.reactionContextNode.updateLayout(size: size, insets: UIEdgeInsets(top: 64.0, left: 0.0, bottom: 0.0, right: 0.0), anchorRect: anchorRect, centerAligned: true, isCoveredByInput: false, isAnimatingOut: false, forceUpdate: forceUpdate, transition: transition)
|
self.reactionContextNode.updateLayout(size: size, insets: UIEdgeInsets(top: 64.0, left: 0.0, bottom: 0.0, right: 0.0), anchorRect: anchorRect, centerAligned: true, isCoveredByInput: false, isAnimatingOut: false, forceUpdate: forceUpdate, transition: transition)
|
||||||
|
|
||||||
if isFirstTime {
|
if isFirstTime {
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
self.reactionContextNode.animateIn(from: anchorRect)
|
self.reactionContextNode.animateIn(from: anchorRect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6498,6 +6498,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isVideo {
|
||||||
|
self.stickerResultController?.disappeared = nil
|
||||||
|
}
|
||||||
|
|
||||||
let _ = (imagesReady.get()
|
let _ = (imagesReady.get()
|
||||||
|> filter { $0 }
|
|> filter { $0 }
|
||||||
|> take(1)
|
|> take(1)
|
||||||
@ -6508,7 +6512,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
if isVideo {
|
if isVideo {
|
||||||
self.uploadSticker(file, action: .send)
|
self.uploadSticker(file, action: .send)
|
||||||
} else {
|
} else {
|
||||||
self.stickerResultController?.disappeared = nil
|
|
||||||
self.completion(MediaEditorScreen.Result(
|
self.completion(MediaEditorScreen.Result(
|
||||||
media: .sticker(file: file, emoji: self.effectiveStickerEmoji()),
|
media: .sticker(file: file, emoji: self.effectiveStickerEmoji()),
|
||||||
mediaAreas: [],
|
mediaAreas: [],
|
||||||
|
@ -1726,6 +1726,8 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func openStickerEditor() {
|
func openStickerEditor() {
|
||||||
|
self.chatDisplayNode.dismissInput()
|
||||||
|
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
let mainController = self.context.sharedContext.makeStickerMediaPickerScreen(
|
let mainController = self.context.sharedContext.makeStickerMediaPickerScreen(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user