mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Display text selection tip in message context menu
This commit is contained in:
@@ -34,12 +34,12 @@ private func cancelScrollViewGestures(view: UIView?) {
|
||||
}
|
||||
}
|
||||
|
||||
private func generateKnobImage(color: UIColor, inverted: Bool = false) -> UIImage? {
|
||||
private func generateKnobImage(color: UIColor, diameter: CGFloat, inverted: Bool = false) -> UIImage? {
|
||||
let f: (CGSize, CGContext) -> Void = { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(x: (size.width - 2.0) / 2.0, y: size.width / 2.0), size: CGSize(width: 2.0, height: size.height - size.width / 2.0 - 1.0)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: floor((size.width - diameter) / 2.0), y: floor((size.width - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: (size.width - 2.0) / 2.0, y: size.width + 2.0), size: CGSize(width: 2.0, height: 2.0)))
|
||||
}
|
||||
let size = CGSize(width: 12.0, height: 12.0 + 2.0 + 2.0)
|
||||
@@ -53,10 +53,12 @@ private func generateKnobImage(color: UIColor, inverted: Bool = false) -> UIImag
|
||||
public final class TextSelectionTheme {
|
||||
public let selection: UIColor
|
||||
public let knob: UIColor
|
||||
public let knobDiameter: CGFloat
|
||||
|
||||
public init(selection: UIColor, knob: UIColor) {
|
||||
public init(selection: UIColor, knob: UIColor, knobDiameter: CGFloat = 12.0) {
|
||||
self.selection = selection
|
||||
self.knob = knob
|
||||
self.knobDiameter = knobDiameter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +206,9 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
|
||||
public let highlightAreaNode: ASDisplayNode
|
||||
|
||||
private var recognizer: TextSelectionGetureRecognizer?
|
||||
private var displayLinkAnimator: DisplayLinkAnimator?
|
||||
|
||||
public init(theme: TextSelectionTheme, strings: PresentationStrings, textNode: TextNode, updateIsActive: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, rootNode: ASDisplayNode, performAction: @escaping (String, TextSelectionAction) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
@@ -214,13 +219,13 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
self.performAction = performAction
|
||||
self.leftKnob = ASImageNode()
|
||||
self.leftKnob.isUserInteractionEnabled = false
|
||||
self.leftKnob.image = generateKnobImage(color: theme.knob)
|
||||
self.leftKnob.image = generateKnobImage(color: theme.knob, diameter: theme.knobDiameter)
|
||||
self.leftKnob.displaysAsynchronously = false
|
||||
self.leftKnob.displayWithoutProcessing = true
|
||||
self.leftKnob.alpha = 0.0
|
||||
self.rightKnob = ASImageNode()
|
||||
self.rightKnob.isUserInteractionEnabled = false
|
||||
self.rightKnob.image = generateKnobImage(color: theme.knob, inverted: true)
|
||||
self.rightKnob.image = generateKnobImage(color: theme.knob, diameter: theme.knobDiameter, inverted: true)
|
||||
self.rightKnob.displaysAsynchronously = false
|
||||
self.rightKnob.displayWithoutProcessing = true
|
||||
self.rightKnob.alpha = 0.0
|
||||
@@ -255,7 +260,6 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
|
||||
let mappedPoint = strongSelf.view.convert(point, to: strongSelf.textNode.view)
|
||||
if let stringIndex = strongSelf.textNode.attributesAtPoint(mappedPoint, orNearest: true)?.0 {
|
||||
//let string = attributedString.string as NSString
|
||||
var updatedMin = currentRange.0
|
||||
var updatedMax = currentRange.1
|
||||
switch knob {
|
||||
@@ -324,6 +328,7 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
self?.dismissSelection()
|
||||
self?.updateIsActive(false)
|
||||
}
|
||||
self.recognizer = recognizer
|
||||
self.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
@@ -337,6 +342,60 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func pretendInitiateSelection() {
|
||||
guard let cachedLayout = self.textNode.cachedLayout, let attributedString = cachedLayout.attributedString else {
|
||||
return
|
||||
}
|
||||
|
||||
var resultRange: NSRange?
|
||||
let stringIndex = 0
|
||||
let string = attributedString.string as NSString
|
||||
|
||||
let inputRange = CFRangeMake(0, string.length)
|
||||
let flag = UInt(kCFStringTokenizerUnitWord)
|
||||
let locale = CFLocaleCopyCurrent()
|
||||
let tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, string as CFString, inputRange, flag, locale)
|
||||
var tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)
|
||||
|
||||
while !tokenType.isEmpty {
|
||||
let currentTokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer)
|
||||
if currentTokenRange.location <= stringIndex && currentTokenRange.location + currentTokenRange.length > stringIndex {
|
||||
resultRange = NSRange(location: currentTokenRange.location, length: currentTokenRange.length)
|
||||
break
|
||||
}
|
||||
tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)
|
||||
}
|
||||
if resultRange == nil {
|
||||
resultRange = NSRange(location: stringIndex, length: 1)
|
||||
}
|
||||
|
||||
self.currentRange = resultRange.flatMap {
|
||||
($0.lowerBound, $0.upperBound)
|
||||
}
|
||||
self.updateSelection(range: resultRange, animateIn: true)
|
||||
self.updateIsActive(true)
|
||||
}
|
||||
|
||||
public func pretendExtendSelection(to index: Int) {
|
||||
guard let cachedLayout = self.textNode.cachedLayout, let attributedString = cachedLayout.attributedString, let endRangeRect = cachedLayout.rangeRects(in: NSRange(location: index, length: 1))?.first else {
|
||||
return
|
||||
}
|
||||
let startPoint = self.rightKnob.frame.center
|
||||
let endPoint = endRangeRect.center
|
||||
let displayLinkAnimator = DisplayLinkAnimator(duration: 0.3, from: 0.0, to: 1.0, update: { [weak self] progress in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let point = CGPoint(x: (1.0 - progress) * startPoint.x + progress * endPoint.x, y: (1.0 - progress) * startPoint.y + progress * endPoint.y)
|
||||
strongSelf.recognizer?.moveKnob?(.right, point)
|
||||
}, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
})
|
||||
self.displayLinkAnimator = displayLinkAnimator
|
||||
}
|
||||
|
||||
private func updateSelection(range: NSRange?, animateIn: Bool) {
|
||||
var rects: [CGRect]?
|
||||
|
||||
@@ -362,8 +421,8 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
highlightOverlay.frame = self.bounds
|
||||
highlightOverlay.updateRects(rects)
|
||||
if let image = self.leftKnob.image {
|
||||
self.leftKnob.frame = CGRect(origin: CGPoint(x: floor(rects[0].minX - 1.0 - image.size.width / 2.0), y: rects[0].minY - 1.0 - image.size.width), size: CGSize(width: image.size.width, height: image.size.width + rects[0].height + 2.0))
|
||||
self.rightKnob.frame = CGRect(origin: CGPoint(x: floor(rects[rects.count - 1].maxX + 1.0 - image.size.width / 2.0), y: rects[rects.count - 1].maxY + 1.0 - (rects[0].height + 2.0)), size: CGSize(width: image.size.width, height: image.size.width + rects[0].height + 2.0))
|
||||
self.leftKnob.frame = CGRect(origin: CGPoint(x: floor(rects[0].minX - 1.0 - image.size.width / 2.0), y: rects[0].minY - 1.0 - self.theme.knobDiameter), size: CGSize(width: image.size.width, height: self.theme.knobDiameter + rects[0].height + 2.0))
|
||||
self.rightKnob.frame = CGRect(origin: CGPoint(x: floor(rects[rects.count - 1].maxX + 1.0 - image.size.width / 2.0), y: rects[rects.count - 1].maxY + 1.0 - (rects[0].height + 2.0)), size: CGSize(width: image.size.width, height: self.theme.knobDiameter + rects[0].height + 2.0))
|
||||
}
|
||||
if self.leftKnob.alpha.isZero {
|
||||
highlightOverlay.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
|
||||
Reference in New Issue
Block a user