Drawing improvements

This commit is contained in:
Ilya Laktyushin 2022-12-29 16:51:14 +04:00
parent 53adba6b1f
commit f9ca86db34
12 changed files with 286 additions and 55 deletions

View File

@ -8565,6 +8565,12 @@ Sorry for the inconvenience.";
"Paint.MoveForward" = "Move Forward";
"Paint.ColorTitle" = "Colors";
"Paint.ColorGrid" = "Grid";
"Paint.ColorSpectrum" = "Spectrum";
"Paint.ColorSliders" = "Sliders";
"Paint.ColorOpacity" = "OPACITY";
"StorageManagement.Title" = "Storage Usage";
"StorageManagement.TitleCleared" = "Storage Cleared";

View File

@ -2026,6 +2026,8 @@ private final class ColorPickerContent: CombinedComponent {
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
let component = context.component
let state = context.state
let strings = environment.strings
state.colorChanged = component.colorChanged
let sideInset: CGFloat = 16.0
@ -2079,7 +2081,7 @@ private final class ColorPickerContent: CombinedComponent {
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Colors",
string: strings.Paint_ColorTitle,
font: Font.semibold(17.0),
textColor: .white,
paragraphAlignment: .center
@ -2098,7 +2100,7 @@ private final class ColorPickerContent: CombinedComponent {
let modeControl = modeControl.update(
component: SegmentedControlComponent(
values: ["Grid", "Spectrum", "Sliders"],
values: [strings.Paint_ColorGrid, strings.Paint_ColorSpectrum, strings.Paint_ColorSliders],
selectedIndex: 0,
selectionChanged: { [weak state] index in
state?.updateSelectedMode(index)
@ -2172,7 +2174,7 @@ private final class ColorPickerContent: CombinedComponent {
let opacityTitle = opacityTitle.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: "OPACITY",
string: strings.Paint_ColorOpacity,
font: Font.semibold(13.0),
textColor: UIColor(rgb: 0x9b9da5),
paragraphAlignment: .center

View File

@ -134,6 +134,8 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
public var getEntityCenterPosition: () -> CGPoint = { return .zero }
public var getEntityInitialRotation: () -> CGFloat = { return 0.0 }
public var getEntityAdditionalScale: () -> CGFloat = { return 1.0 }
public var hasSelectionChanged: (Bool) -> Void = { _ in }
var selectionChanged: (DrawingEntity?) -> Void = { _ in }
var requestedMenuForEntityView: (DrawingEntityView, Bool) -> Void = { _, _ in }

View File

@ -60,8 +60,9 @@ final class NeonTool: DrawingElement {
self.layer.addSublayer(self.fillLayer)
}
fileprivate func updatePath(_ path: CGPath) {
fileprivate func updatePath(_ path: CGPath, shadowPath: CGPath) {
self.shadowLayer.path = path
self.shadowLayer.shadowPath = shadowPath
self.borderLayer.path = path
self.fillLayer.path = path
}
@ -81,6 +82,7 @@ final class NeonTool: DrawingElement {
private var addedPaths = 0
fileprivate var renderPath: CGPath?
fileprivate var shadowRenderPath: CGPath?
var translation: CGPoint = .zero
@ -91,7 +93,7 @@ final class NeonTool: DrawingElement {
}
var bounds: CGRect {
if let renderPath = self.renderPath {
if let renderPath = self.shadowRenderPath {
return normalizeDrawingRect(renderPath.boundingBoxOfPath.insetBy(dx: -self.renderShadowRadius - 30.0, dy: -self.renderShadowRadius - 30.0), drawingSize: self.drawingSize)
} else {
return .zero
@ -103,8 +105,8 @@ final class NeonTool: DrawingElement {
self.drawingSize = drawingSize
self.color = color
let strokeWidth = min(drawingSize.width, drawingSize.height) * 0.01
let shadowRadius = min(drawingSize.width, drawingSize.height) * 0.03
let strokeWidth = min(drawingSize.width, drawingSize.height) * 0.008
let shadowRadius = min(drawingSize.width, drawingSize.height) * 0.02
let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.002)
let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.07)
@ -138,9 +140,11 @@ final class NeonTool: DrawingElement {
if let activePath {
path?.addPath(activePath.cgPath)
}
if let renderPath = path?.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0) {
if let renderPath = path?.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0),
let shadowRenderPath = path?.copy(strokingWithWidth: self.renderLineWidth * 2.0, lineCap: .round, lineJoin: .round, miterLimit: 0.0) {
self.renderPath = renderPath
currentRenderView.updatePath(renderPath)
self.shadowRenderPath = shadowRenderPath
currentRenderView.updatePath(renderPath, shadowPath: shadowRenderPath)
}
}
@ -155,7 +159,7 @@ final class NeonTool: DrawingElement {
}
func draw(in context: CGContext, size: CGSize) {
guard let path = self.renderPath else {
guard let path = self.renderPath, let shadowPath = self.shadowRenderPath else {
return
}
context.saveGState()
@ -172,15 +176,20 @@ final class NeonTool: DrawingElement {
fillColor = shadowColor
shadowColor = UIColor(rgb: 0x440881)
}
context.addPath(path)
let shadowOffset = CGSize(width: 3000.0, height: 3000.0)
context.translateBy(x: -shadowOffset.width, y: -shadowOffset.height)
context.addPath(shadowPath)
context.setLineCap(.round)
context.setFillColor(fillColor.cgColor)
context.setStrokeColor(fillColor.cgColor)
context.setLineWidth(self.renderStrokeWidth * 0.5)
context.setShadow(offset: .zero, blur: self.renderShadowRadius * 1.9, color: shadowColor.cgColor)
context.setShadow(offset: shadowOffset, blur: self.renderShadowRadius * 1.9, color: shadowColor.withAlphaComponent(0.87).cgColor)
context.drawPath(using: .fillStroke)
context.translateBy(x: shadowOffset.width, y: shadowOffset.height)
context.addPath(path)
context.setShadow(offset: .zero, blur: 0.0, color: UIColor.clear.cgColor)
context.setLineWidth(self.renderStrokeWidth)

View File

@ -2,6 +2,8 @@ import Foundation
import UIKit
import Display
private let activeWidthFactor: CGFloat = 0.7
final class PenTool: DrawingElement {
class RenderView: UIView, DrawingRenderView {
private weak var element: PenTool?
@ -14,7 +16,7 @@ final class PenTool: DrawingElement {
private var segmentsCount = 0
private var drawScale = CGSize(width: 1.0, height: 1.0)
func setup(size: CGSize, screenSize: CGSize, isEraser: Bool) {
self.isEraser = isEraser
@ -22,7 +24,6 @@ final class PenTool: DrawingElement {
self.isOpaque = false
self.contentMode = .redraw
//let scale = CGSize(width: screenSize.width / max(1.0, size.width), height: screenSize.height / max(1.0, size.height))
let scale = CGSize(width: 0.33, height: 0.33)
let viewSize = CGSize(width: size.width * scale.width, height: size.height * scale.height)
@ -80,7 +81,26 @@ final class PenTool: DrawingElement {
})
}
var displaySize: CGSize?
var onDryingUp: () -> Void = {}
var isDryingUp = false {
didSet {
if !self.isDryingUp {
self.onDryingUp()
}
}
}
var dryingLayersCount: Int = 0 {
didSet {
if self.dryingLayersCount > 0 {
self.isDryingUp = true
} else {
self.isDryingUp = false
}
}
}
fileprivate var displaySize: CGSize?
fileprivate func draw(element: PenTool, rect: CGRect) {
self.element = element
@ -120,15 +140,71 @@ final class PenTool: DrawingElement {
self.start = newStart
}
if !element.isEraser && !element.isBlur {
let count = CGFloat(element.segments.count - self.segmentsCount)
if count > 0 {
let dryingPath = CGMutablePath()
var abFactor: CGFloat = activeWidthFactor * 1.35
let delta: CGFloat = (1.0 - abFactor) / count
for i in self.segmentsCount ..< element.segments.count {
let segmentPath = element.pathForSegment(element.segments[i], abFactor: abFactor, cdFactor: abFactor + delta)
dryingPath.addPath(segmentPath)
abFactor += delta
}
self.setupDrying(path: dryingPath)
}
}
self.segmentsCount = element.segments.count
if let rect = rect {
self.activeView?.setNeedsDisplay(rect.insetBy(dx: -10.0, dy: -10.0).applying(CGAffineTransform(scaleX: 1.0 / self.drawScale.width, y: 1.0 / self.drawScale.height)))
self.activeView?.setNeedsDisplay(rect.insetBy(dx: -40.0, dy: -40.0).applying(CGAffineTransform(scaleX: 1.0 / self.drawScale.width, y: 1.0 / self.drawScale.height)))
} else {
self.activeView?.setNeedsDisplay()
}
}
private let dryingFactor: CGFloat = 0.4
func setupDrying(path: CGPath) {
guard let element = self.element else {
return
}
let dryingLayer = CAShapeLayer()
dryingLayer.contentsScale = 1.0
dryingLayer.fillColor = element.renderColor.cgColor
dryingLayer.strokeColor = element.renderColor.cgColor
dryingLayer.lineWidth = element.renderLineWidth * self.dryingFactor
dryingLayer.path = path
dryingLayer.animate(from: dryingLayer.lineWidth as NSNumber, to: 0.0 as NSNumber, keyPath: "lineWidth", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.4, removeOnCompletion: false, completion: { [weak dryingLayer] _ in
dryingLayer?.removeFromSuperlayer()
self.dryingLayersCount -= 1
})
dryingLayer.transform = CATransform3DMakeScale(1.0 / self.drawScale.width, 1.0 / self.drawScale.height, 1.0)
dryingLayer.frame = self.bounds
self.layer.addSublayer(dryingLayer)
self.dryingLayersCount += 1
}
private var isActiveDrying = false
func setupActiveSegmentsDrying() {
guard let element = self.element else {
return
}
if !element.isEraser && !element.isBlur {
let dryingPath = CGMutablePath()
for segment in element.activeSegments {
let segmentPath = element.pathForSegment(segment)
dryingPath.addPath(segmentPath)
}
self.setupDrying(path: dryingPath)
self.isActiveDrying = true
self.setNeedsDisplay()
}
}
class ActiveView: UIView {
weak var parent: RenderView?
override func draw(_ rect: CGRect) {
@ -139,7 +215,12 @@ final class PenTool: DrawingElement {
parent.displaySize = rect.size
context.scaleBy(x: 1.0 / parent.drawScale.width, y: 1.0 / parent.drawScale.height)
element.drawSegments(in: context, from: parent.start, to: parent.segmentsCount)
element.drawActiveSegments(in: context)
if !element.isEraser || !element.isBlur {
element.drawActiveSegments(in: context, strokeWidth: !parent.isActiveDrying ? element.renderLineWidth * parent.dryingFactor : nil)
} else {
element.drawActiveSegments(in: context, strokeWidth: nil)
}
}
}
}
@ -169,6 +250,23 @@ final class PenTool: DrawingElement {
var blurredImage: UIImage?
private weak var currentRenderView: DrawingRenderView?
private var points: [Point] = Array(repeating: Point(location: .zero, width: 0.0), count: 4)
private var pointPtr = 0
private var smoothPoints: [Point] = []
private var activeSmoothPoints: [Point] = []
private var segments: [Segment] = []
private var activeSegments: [Segment] = []
private var previousActiveRect: CGRect?
private var previousRenderLineWidth: CGFloat?
private var segmentPaths: [Int: CGPath] = [:]
private var useCubicBezier = true
var isValid: Bool {
if self.hasArrow {
@ -217,7 +315,7 @@ final class PenTool: DrawingElement {
completion()
}
}
func setupRenderView(screenSize: CGSize) -> DrawingRenderView? {
let view = RenderView()
view.setup(size: self.drawingSize, screenSize: screenSize, isEraser: self.isEraser)
@ -249,6 +347,8 @@ final class PenTool: DrawingElement {
if state == .ended {
if !self.activeSegments.isEmpty {
(self.currentRenderView as? RenderView)?.setupActiveSegmentsDrying()
self.segments.append(contentsOf: self.activeSegments)
self.smoothPoints.append(contentsOf: self.activeSmoothPoints)
}
@ -279,6 +379,9 @@ final class PenTool: DrawingElement {
d: CGPoint(x: point.x + radius, y: point.y + 0.1),
radius1: radius,
radius2: radius,
abCenter: CGPoint(x: point.x, y: point.y),
cdCenter: CGPoint(x: point.x, y: point.y + 0.1),
perpendicular: .zero,
rect: CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius * 2.0, height: radius * 2.0))
)
)
@ -363,6 +466,9 @@ final class PenTool: DrawingElement {
let d: CGPoint
let radius1: CGFloat
let radius2: CGFloat
let abCenter: CGPoint
let cdCenter: CGPoint
let perpendicular: CGPoint
let rect: CGRect
init(
@ -372,6 +478,9 @@ final class PenTool: DrawingElement {
d: CGPoint,
radius1: CGFloat,
radius2: CGFloat,
abCenter: CGPoint,
cdCenter: CGPoint,
perpendicular: CGPoint,
rect: CGRect
) {
self.a = a
@ -380,8 +489,43 @@ final class PenTool: DrawingElement {
self.d = d
self.radius1 = radius1
self.radius2 = radius2
self.abCenter = abCenter
self.cdCenter = cdCenter
self.perpendicular = perpendicular
self.rect = rect
}
func withMultiplied(abFactor: CGFloat, cdFactor: CGFloat) -> Segment {
let a = CGPoint(
x: self.abCenter.x + self.perpendicular.x * self.radius1 * abFactor,
y: self.abCenter.y + self.perpendicular.y * self.radius1 * abFactor
)
let b = CGPoint(
x: self.abCenter.x - self.perpendicular.x * self.radius1 * abFactor,
y: self.abCenter.y - self.perpendicular.y * self.radius1 * abFactor
)
let c = CGPoint(
x: self.cdCenter.x + self.perpendicular.x * self.radius2 * cdFactor,
y: self.cdCenter.y + self.perpendicular.y * self.radius2 * cdFactor
)
let d = CGPoint(
x: self.cdCenter.x - self.perpendicular.x * self.radius2 * cdFactor,
y: self.cdCenter.y - self.perpendicular.y * self.radius2 * cdFactor
)
return Segment(
a: a,
b: b,
c: c,
d: d,
radius1: self.radius1 * abFactor,
radius2: self.radius2 * cdFactor,
abCenter: self.abCenter,
cdCenter: self.cdCenter,
perpendicular: self.perpendicular,
rect: self.rect
)
}
}
private struct Point {
@ -396,27 +540,16 @@ final class PenTool: DrawingElement {
self.width = width
}
}
private var points: [Point] = Array(repeating: Point(location: .zero, width: 0.0), count: 4)
private var pointPtr = 0
private var smoothPoints: [Point] = []
private var activeSmoothPoints: [Point] = []
private var segments: [Segment] = []
private var activeSegments: [Segment] = []
private var previousActiveRect: CGRect?
private var previousRenderLineWidth: CGFloat?
private var currentVelocity: CGFloat?
private func addPoint(_ point: DrawingPoint, state: DrawingGesturePipeline.DrawingGestureState, zoomScale: CGFloat) -> (Bool, CGRect)? {
let filterDistance: CGFloat = 10.0 / zoomScale
let filterDistance: CGFloat = 8.0 / zoomScale
var velocity = point.velocity
if velocity.isZero {
velocity = 1000.0
}
self.currentVelocity = velocity
var renderLineWidth = max(self.renderMinLineWidth, min(self.renderLineWidth - (velocity / 200.0), self.renderLineWidth))
if let previousRenderLineWidth = self.previousRenderLineWidth {
@ -492,9 +625,9 @@ final class PenTool: DrawingElement {
private func currentSmoothPoints(_ ctr: Int) -> [Point]? {
switch ctr {
case 0:
return [self.points[0]]
return nil//return [self.points[0]]
case 1:
return self.smoothPoints(.line(self.points[0], self.points[1]))
return nil//return self.smoothPoints(.line(self.points[0], self.points[1]))
case 2:
return self.smoothPoints(.quad(self.points[0], self.points[1], self.points[2]))
case 3:
@ -667,15 +800,29 @@ final class PenTool: DrawingElement {
let segmentRect = CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
updateRect = updateRect.union(segmentRect)
let segment = Segment(a: a, b: b, c: c, d: d, radius1: previousWidth / 2.0, radius2: currentWidth / 2.0, rect: segmentRect)
let segment = Segment(
a: a,
b: b,
c: c,
d: d,
radius1: previousWidth / 2.0,
radius2: currentWidth / 2.0,
abCenter: abCenter,
cdCenter: cdCenter,
perpendicular: perpendicular,
rect: segmentRect
)
segments.append(segment)
}
return (segments, !updateRect.isNull ? updateRect : nil)
}
private var segmentPaths: [Int: CGPath] = [:]
private func pathForSegment(_ segment: Segment) -> CGPath {
private func pathForSegment(_ segment: Segment, abFactor: CGFloat = 1.0, cdFactor: CGFloat = 1.0) -> CGPath {
var segment = segment
if abFactor != 1.0 || cdFactor != 1.0 {
segment = segment.withMultiplied(abFactor: abFactor, cdFactor: cdFactor)
}
let path = CGMutablePath()
path.move(to: segment.b)
@ -713,6 +860,19 @@ final class PenTool: DrawingElement {
return path
}
func cachedPathForSegmentIndex(_ i: Int) -> CGPath {
var segmentPath: CGPath
if let current = self.segmentPaths[i] {
segmentPath = current
} else {
let segment = self.segments[i]
let path = self.pathForSegment(segment)
self.segmentPaths[i] = path
segmentPath = path
}
return segmentPath
}
private func drawSegments(in context: CGContext, from: Int, to: Int) {
context.setFillColor(self.renderColor.cgColor)
@ -733,13 +893,24 @@ final class PenTool: DrawingElement {
}
}
private func drawActiveSegments(in context: CGContext) {
private func drawActiveSegments(in context: CGContext, strokeWidth: CGFloat?) {
context.setFillColor(self.renderColor.cgColor)
if let strokeWidth {
context.setStrokeColor(self.renderColor.cgColor)
context.setLineWidth(strokeWidth)
}
var abFactor: CGFloat = activeWidthFactor
let delta: CGFloat = (1.0 - activeWidthFactor) / CGFloat(self.activeSegments.count + 1)
for segment in self.activeSegments {
let path = self.pathForSegment(segment)
context.addPath(path)
context.fillPath()
if let _ = strokeWidth {
context.drawPath(using: .fillStroke)
} else {
context.fillPath()
}
abFactor += delta
}
}
}

View File

@ -308,9 +308,11 @@ struct DrawingState: Equatable {
final class DrawingSettings: Codable, Equatable {
let tools: [DrawingToolState]
let colors: [DrawingColor]
init(tools: [DrawingToolState]) {
init(tools: [DrawingToolState], colors: [DrawingColor]) {
self.tools = tools
self.colors = colors
}
init(from decoder: Decoder) throws {
@ -321,6 +323,12 @@ final class DrawingSettings: Codable, Equatable {
} else {
self.tools = DrawingState.initial.tools
}
if let data = try container.decodeIfPresent(Data.self, forKey: "colors"), let colors = try? JSONDecoder().decode([DrawingColor].self, from: data) {
self.colors = colors
} else {
self.colors = []
}
}
func encode(to encoder: Encoder) throws {
@ -329,10 +337,13 @@ final class DrawingSettings: Codable, Equatable {
if let data = try? JSONEncoder().encode(self.tools) {
try container.encode(data, forKey: "tools")
}
if let data = try? JSONEncoder().encode(self.colors) {
try container.encode(data, forKey: "colors")
}
}
static func ==(lhs: DrawingSettings, rhs: DrawingSettings) -> Bool {
return lhs.tools == rhs.tools
return lhs.tools == rhs.tools && lhs.colors == rhs.colors
}
}
@ -734,7 +745,7 @@ private final class DrawingScreenComponent: CombinedComponent {
let tools = self.drawingState.tools
let _ = (self.context.sharedContext.accountManager.transaction { transaction -> Void in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.drawingSettings, { _ in
return PreferencesEntry(DrawingSettings(tools: tools))
return PreferencesEntry(DrawingSettings(tools: tools, colors: []))
})
}).start()
}

View File

@ -390,9 +390,11 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
self.textView.becomeFirstResponder()
UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 0.65, initialSpringVelocity: 0.0) {
self.transform = .identity
if let superview = self.superview {
self.center = CGPoint(x: superview.bounds.width / 2.0, y: superview.bounds.height / 2.0)
if let parentView = self.superview as? DrawingEntitiesView {
let scale = parentView.getEntityAdditionalScale() / (parentView.drawingView?.zoomScale ?? 1.0)
self.transform = CGAffineTransformMakeRotation(parentView.getEntityInitialRotation()).scaledBy(x: scale, y: scale)
self.center = parentView.getEntityCenterPosition()
}
}

View File

@ -555,7 +555,13 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, UIPencilInt
self.currentDrawingViewContainer.mask = nil
self.currentDrawingViewContainer.image = nil
} else {
currentDrawingRenderView.removeFromSuperview()
if let renderView = currentDrawingRenderView as? PenTool.RenderView, renderView.isDryingUp {
renderView.onDryingUp = { [weak renderView] in
renderView?.removeFromSuperview()
}
} else {
currentDrawingRenderView.removeFromSuperview()
}
}
self.currentDrawingRenderView = nil
}
@ -651,10 +657,14 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, UIPencilInt
self.updateInternalState()
}
if let uncommitedElement = self.uncommitedElement as? PenTool, uncommitedElement.hasArrow {
uncommitedElement.finishArrow({
if let uncommitedElement = self.uncommitedElement as? PenTool {
if uncommitedElement.hasArrow {
uncommitedElement.finishArrow {
complete(true)
}
} else {
complete(true)
})
}
} else {
complete(synchronous)
}

View File

@ -231,9 +231,9 @@ final class TextFontComponent: Component {
self.button.clipsToBounds = true
self.button.setTitle(value.title, for: .normal)
self.button.titleLabel?.font = value.uiFont(size: 13.0)
self.button.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 26.0)
self.button.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 20.0)
var buttonSize = self.button.sizeThatFits(availableSize)
buttonSize.width += 39.0 - 13.0
buttonSize.width += 20.0
buttonSize.height = 30.0
transition.setFrame(view: self.button, frame: CGRect(origin: .zero, size: buttonSize))
self.button.layer.cornerRadius = 11.0

View File

@ -53,6 +53,7 @@
@property (nonatomic, copy) CGPoint (^ _Nonnull getEntityCenterPosition)(void);
@property (nonatomic, copy) CGFloat (^ _Nonnull getEntityInitialRotation)(void);
@property (nonatomic, copy) CGFloat (^ _Nonnull getEntityAdditionalScale)(void);
@property (nonatomic, copy) void(^ _Nonnull hasSelectionChanged)(bool);
@property (nonatomic, readonly) BOOL hasSelection;

View File

@ -246,6 +246,14 @@ const CGSize TGPhotoPaintingMaxSize = { 1920.0f, 1920.0f };
return [strongSelf entityInitialRotation];
};
_entitiesView.getEntityAdditionalScale = ^CGFloat {
__strong TGPhotoDrawingController *strongSelf = weakSelf;
if (strongSelf == nil)
return 1.0f;
return strongSelf->_photoEditor.cropRect.size.width / strongSelf->_photoEditor.originalSize.width;
};
[self.view setNeedsLayout];
}

View File

@ -625,6 +625,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
case .action, .optionalAction:
break
case let .openContextMenu(tapMessage, selectAll, subFrame):
var tapMessage = tapMessage
if selectAll, case let .group(messages) = item.content, tapMessage.text.isEmpty {
for message in messages {
if !message.0.text.isEmpty {
tapMessage = message.0
break
}
}
}
item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture, nil)
}
}