Initial spoiler implementation

This commit is contained in:
Ilya Laktyushin 2021-12-02 04:19:11 +04:00
parent dfe2f5b92d
commit bd2e843086
9 changed files with 298 additions and 15 deletions

View File

@ -15,6 +15,16 @@ private final class TextNodeStrikethrough {
}
}
private final class TextNodeSpoiler {
let range: NSRange
let frame: CGRect
init(range: NSRange, frame: CGRect) {
self.range = range
self.frame = frame
}
}
public struct TextRangeRectEdge: Equatable {
public var x: CGFloat
public var y: CGFloat
@ -33,13 +43,15 @@ private final class TextNodeLine {
let range: NSRange
let isRTL: Bool
let strikethroughs: [TextNodeStrikethrough]
let spoilers: [TextNodeSpoiler]
init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool, strikethroughs: [TextNodeStrikethrough]) {
init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool, strikethroughs: [TextNodeStrikethrough], spoilers: [TextNodeSpoiler]) {
self.line = line
self.frame = frame
self.range = range
self.isRTL = isRTL
self.strikethroughs = strikethroughs
self.spoilers = spoilers
}
}
@ -117,8 +129,9 @@ public final class TextNodeLayoutArguments {
public let lineColor: UIColor?
public let textShadowColor: UIColor?
public let textStroke: (UIColor, CGFloat)?
public let displaySpoilers: Bool
public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, minimumNumberOfLines: Int = 0, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, verticalAlignment: TextVerticalAlignment = .top, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil, textShadowColor: UIColor? = nil, textStroke: (UIColor, CGFloat)? = nil) {
public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, minimumNumberOfLines: Int = 0, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, verticalAlignment: TextVerticalAlignment = .top, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil, textShadowColor: UIColor? = nil, textStroke: (UIColor, CGFloat)? = nil, displaySpoilers: Bool = false) {
self.attributedString = attributedString
self.backgroundColor = backgroundColor
self.minimumNumberOfLines = minimumNumberOfLines
@ -133,6 +146,7 @@ public final class TextNodeLayoutArguments {
self.lineColor = lineColor
self.textShadowColor = textShadowColor
self.textStroke = textStroke
self.displaySpoilers = displaySpoilers
}
}
@ -157,9 +171,11 @@ public final class TextNodeLayout: NSObject {
fileprivate let lineColor: UIColor?
fileprivate let textShadowColor: UIColor?
fileprivate let textStroke: (UIColor, CGFloat)?
fileprivate let displaySpoilers: Bool
public let hasRTL: Bool
public let spoilers: [CGRect]
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, explicitAlignment: NSTextAlignment, resolvedAlignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?) {
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, explicitAlignment: NSTextAlignment, resolvedAlignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool) {
self.attributedString = attributedString
self.maximumNumberOfLines = maximumNumberOfLines
self.truncationType = truncationType
@ -180,13 +196,17 @@ public final class TextNodeLayout: NSObject {
self.lineColor = lineColor
self.textShadowColor = textShadowColor
self.textStroke = textStroke
self.displaySpoilers = displaySpoilers
var hasRTL = false
var spoilers: [CGRect] = []
for line in lines {
if line.isRTL {
hasRTL = true
}
spoilers.append(contentsOf: line.spoilers.map { $0.frame.offsetBy(dx: line.frame.minX, dy: line.frame.minY) })
}
self.hasRTL = hasRTL
self.spoilers = spoilers
}
public func areLinesEqual(to other: TextNodeLayout) -> Bool {
@ -851,7 +871,7 @@ public class TextNode: ASDisplayNode {
}
}
private class func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?) -> TextNodeLayout {
private class func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool) -> TextNodeLayout {
if let attributedString = attributedString {
let stringLength = attributedString.length
@ -890,7 +910,7 @@ public class TextNode: ASDisplayNode {
var maybeTypesetter: CTTypesetter?
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
if maybeTypesetter == nil {
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke)
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke, displaySpoilers: displaySpoilers)
}
let typesetter = maybeTypesetter!
@ -931,6 +951,7 @@ public class TextNode: ASDisplayNode {
var first = true
while true {
var strikethroughs: [TextNodeStrikethrough] = []
var spoilers: [TextNodeSpoiler] = []
var lineConstrainedWidth = constrainedSize.width
var lineConstrainedWidthDelta: CGFloat = 0.0
@ -996,7 +1017,16 @@ public class TextNode: ASDisplayNode {
var headIndent: CGFloat = 0.0
attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in
if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
if let _ = attributes[NSAttributedString.Key.init(rawValue: "TelegramSpoiler")] {
var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
spoilers.append(TextNodeSpoiler(range: range, frame: CGRect(x: x, y: -descent, width: abs(upperX - lowerX), height: ascent + descent)))
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
@ -1025,7 +1055,7 @@ public class TextNode: ASDisplayNode {
}
}
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs))
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers))
break
} else {
if lineCharacterCount > 0 {
@ -1048,7 +1078,16 @@ public class TextNode: ASDisplayNode {
var headIndent: CGFloat = 0.0
attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in
if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
if let _ = attributes[NSAttributedString.Key.init(rawValue: "TelegramSpoiler")] {
var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
spoilers.append(TextNodeSpoiler(range: range, frame: CGRect(x: x, y: descent - (ascent + descent), width: abs(upperX - lowerX), height: ascent + descent)))
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let x = lowerX < upperX ? lowerX : upperX
@ -1076,7 +1115,7 @@ public class TextNode: ASDisplayNode {
}
}
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs))
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers))
} else {
if !lines.isEmpty {
layoutSize.height += fontLineSpacing
@ -1109,9 +1148,9 @@ public class TextNode: ASDisplayNode {
}
}
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke)
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: resolvedAlignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke, displaySpoilers: displaySpoilers)
} else {
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke)
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke, displaySpoilers: displaySpoilers)
}
}
@ -1137,6 +1176,7 @@ public class TextNode: ASDisplayNode {
context.setAllowsFontSubpixelQuantization(true)
context.setShouldSubpixelQuantizeFonts(true)
var clearRects: [CGRect] = []
if let layout = parameters as? TextNodeLayout {
if !isRasterizing || layout.backgroundColor != nil {
context.setBlendMode(.copy)
@ -1189,6 +1229,15 @@ public class TextNode: ASDisplayNode {
}
context.textPosition = CGPoint(x: lineFrame.minX, y: lineFrame.minY)
if layout.displaySpoilers && !line.spoilers.isEmpty {
context.saveGState()
var clipRects: [CGRect] = []
for spoiler in line.spoilers {
clipRects.append(spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY))
}
context.clip(to: clipRects)
}
let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray
if glyphRuns.count != 0 {
for run in glyphRuns {
@ -1213,6 +1262,16 @@ public class TextNode: ASDisplayNode {
context.fill(CGRect(x: frame.minX, y: frame.minY - 5.0, width: frame.width, height: 1.0))
}
}
if !line.spoilers.isEmpty {
if layout.displaySpoilers {
context.restoreGState()
} else {
for spoiler in line.spoilers {
clearRects.append(spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY))
}
}
}
}
var blockQuoteFrames: [CGRect] = []
@ -1248,6 +1307,10 @@ public class TextNode: ASDisplayNode {
}
context.setBlendMode(.normal)
for rect in clearRects {
context.clear(rect)
}
}
public static func asyncLayout(_ maybeNode: TextNode?) -> (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode) {
@ -1282,11 +1345,11 @@ public class TextNode: ASDisplayNode {
if stringMatch {
layout = existingLayout
} else {
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textStroke: arguments.textStroke)
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers)
updated = true
}
} else {
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textStroke: arguments.textStroke)
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textStroke: arguments.textStroke, displaySpoilers: arguments.displaySpoilers)
updated = true
}

View File

@ -0,0 +1,21 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "InvisibleInkDustNode",
module_name = "InvisibleInkDustNode",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AppBundle:AppBundle",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,121 @@
import Foundation
import UIKit
import UIKit.UIGestureRecognizerSubclass
import SwiftSignalKit
import AsyncDisplayKit
import Display
import AppBundle
private func createEmitterBehavior(type: String) -> NSObject {
let selector = ["behaviorWith", "Type:"].joined(separator: "")
let behaviorClass = NSClassFromString(["CA", "Emitter", "Behavior"].joined(separator: "")) as! NSObject.Type
let behaviorWithType = behaviorClass.method(for: NSSelectorFromString(selector))!
let castedBehaviorWithType = unsafeBitCast(behaviorWithType, to:(@convention(c)(Any?, Selector, Any?) -> NSObject).self)
return castedBehaviorWithType(behaviorClass, NSSelectorFromString(selector), type)
}
public class InvisibleInkDustNode: ASDisplayNode {
private var currentParams: (size: CGSize, color: UIColor, rects: [CGRect])?
private var emitter: CAEmitterCell?
private var emitterLayer: CAEmitterLayer?
public override init() {
super.init()
}
public var isRevealedUpdated: (Bool) -> Void = { _ in }
public override func didLoad() {
super.didLoad()
let emitter = CAEmitterCell()
emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage
emitter.birthRate = 1600.0
emitter.setValue(1.8, forKey: "contentsScale")
emitter.emissionRange = .pi * 2.0
emitter.setValue(3.0, forKey: "mass")
emitter.setValue(2.0, forKey: "massRange")
emitter.lifetime = 1.0
emitter.scale = 0.5
emitter.velocityRange = 20.0
emitter.name = "dustCell"
emitter.setValue("point", forKey: "particleType")
emitter.color = UIColor.white.cgColor //?alpha
emitter.alphaRange = 1.0
self.emitter = emitter
let fingerAttractor = createEmitterBehavior(type: "simpleAttractor")
fingerAttractor.setValue("fingerAttractor", forKey: "name")
let alphaBehavior = createEmitterBehavior(type: "valueOverLife")
alphaBehavior.setValue("alphaBehavior", forKey: "name")
alphaBehavior.setValue("color.alpha", forKey: "keyPath")
alphaBehavior.setValue([1.0, 0.0], forKey: "values")
// alphaBehavior.setValue(true, forKey: "additive")
let behaviors = [fingerAttractor, alphaBehavior]
let emitterLayer = CAEmitterLayer()
emitterLayer.masksToBounds = true
emitterLayer.allowsGroupOpacity = true
emitterLayer.lifetime = 1
emitterLayer.emitterCells = [emitter]
emitterLayer.setValue(behaviors, forKey: "emitterBehaviors")
emitterLayer.emitterPosition = CGPoint(x: 0, y: 0)
emitterLayer.seed = arc4random()
emitterLayer.setValue("rectangles", forKey: "emitterShape")
emitterLayer.emitterSize = CGSize(width: 1, height: 1)
emitterLayer.setValue(0.0322, forKey: "updateInterval")
// layer.setValue(-100, forKeyPath: "emitterBehaviors.fingerAttractor.stiffness")
emitterLayer.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
self.emitterLayer = emitterLayer
self.layer.addSublayer(emitterLayer)
self.updateEmitter()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap)))
}
@objc private func tap() {
self.isRevealedUpdated(true)
Queue.mainQueue().after(4.0) {
self.isRevealedUpdated(false)
}
}
private func updateEmitter() {
guard let (size, color, rects) = self.currentParams else {
return
}
self.emitter?.color = color.cgColor
self.emitterLayer?.setValue(rects, forKey: "emitterRects")
self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size)
}
public func update(size: CGSize, color: UIColor, rects: [CGRect]) {
self.currentParams = (size, color, rects)
if self.isNodeLoaded {
self.updateEmitter()
}
}
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if let (_, _, rects) = self.currentParams {
for rect in rects {
if rect.contains(point) {
return true
}
}
return false
} else {
return false
}
}
}

View File

@ -248,6 +248,7 @@ swift_library(
"//submodules/DirectMediaImageCache:DirectMediaImageCache",
"//submodules/CodeInputView:CodeInputView",
"//submodules/Components/ReactionButtonListComponent:ReactionButtonListComponent",
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "textSpeckle_Normal.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -8,6 +8,7 @@ import TextFormat
import UrlEscaping
import TelegramUniversalVideoContent
import TextSelectionNode
import InvisibleInkDustNode
private final class CachedChatMessageText {
let text: String
@ -37,6 +38,9 @@ private final class CachedChatMessageText {
class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
private let textNode: TextNode
private var spoilerTextNode: TextNode?
private var dustNode: InvisibleInkDustNode?
private let textAccessibilityOverlayNode: TextAccessibilityOverlayNode
private let statusNode: ChatMessageDateAndStatusNode
private var linkHighlightingNode: LinkHighlightingNode?
@ -80,6 +84,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
let textLayout = TextNode.asyncLayout(self.textNode)
let spoilerTextLayout = TextNode.asyncLayout(self.spoilerTextNode)
let statusLayout = self.statusNode.asyncLayout()
let currentCachedChatMessageText = self.cachedChatMessageText
@ -273,6 +278,13 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets, lineColor: messageTheme.accentControlColor))
let spoilerTextLayoutAndApply: (TextNodeLayout, () -> TextNode)?
if !textLayout.spoilers.isEmpty {
spoilerTextLayoutAndApply = spoilerTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets, lineColor: messageTheme.accentControlColor, displaySpoilers: true))
} else {
spoilerTextLayoutAndApply = nil
}
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
if let statusType = statusType {
var isReplyThread = false
@ -353,8 +365,50 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.textNode.displaysAsynchronously = !item.presentationData.isPreview && !item.presentationData.theme.theme.forceSync
let _ = textApply()
strongSelf.textNode.frame = textFrame
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
let spoilerTextNode = spoilerTextApply()
if strongSelf.spoilerTextNode == nil {
spoilerTextNode.isUserInteractionEnabled = false
spoilerTextNode.contentMode = .topLeft
spoilerTextNode.contentsScale = UIScreenScale
spoilerTextNode.displaysAsynchronously = false
strongSelf.insertSubnode(spoilerTextNode, aboveSubnode: strongSelf.textAccessibilityOverlayNode)
strongSelf.spoilerTextNode = spoilerTextNode
}
strongSelf.spoilerTextNode?.frame = textFrame
strongSelf.spoilerTextNode?.isHidden = false
strongSelf.spoilerTextNode?.alpha = 0.0
let dustNode: InvisibleInkDustNode
if let current = strongSelf.dustNode {
dustNode = current
} else {
dustNode = InvisibleInkDustNode()
dustNode.isRevealedUpdated = { [weak self] revealed in
if let strongSelf = self {
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
if let dustNode = strongSelf.dustNode {
transition.updateAlpha(node: dustNode, alpha: revealed ? 0.0 : 1.0)
}
if let spoilerTextNode = strongSelf.spoilerTextNode {
transition.updateAlpha(node: spoilerTextNode, alpha: revealed ? 1.0 : 0.0)
}
}
}
strongSelf.dustNode = dustNode
strongSelf.insertSubnode(dustNode, aboveSubnode: spoilerTextNode)
}
dustNode.update(size: textFrame.size, color: messageTheme.primaryTextColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: -2.0, dy: 0.0) })
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
} else if let spoilerTextNode = strongSelf.spoilerTextNode {
strongSelf.spoilerTextNode = nil
spoilerTextNode.removeFromSupernode()
}
if let textSelectionNode = strongSelf.textSelectionNode {
let shouldUpdateLayout = textSelectionNode.frame.size != textFrame.size
textSelectionNode.frame = textFrame

View File

@ -137,7 +137,8 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
}
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention), value: nsString!.substring(with: range), range: range)
case .Strikethrough:
string.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true as NSNumber, range: range)
// string.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
case .Underline:
string.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
case let .TextMention(peerId):

View File

@ -41,4 +41,5 @@ public struct TelegramTextAttributes {
public static let Timecode = "TelegramTimecode"
public static let BlockQuote = "TelegramBlockQuote"
public static let Pre = "TelegramPre"
public static let Spoiler = "TelegramSpoiler"
}