mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-18 19:40:19 +00:00
Drawing improvements
This commit is contained in:
parent
53adba6b1f
commit
f9ca86db34
@ -8565,6 +8565,12 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"Paint.MoveForward" = "Move Forward";
|
"Paint.MoveForward" = "Move Forward";
|
||||||
|
|
||||||
|
"Paint.ColorTitle" = "Colors";
|
||||||
|
"Paint.ColorGrid" = "Grid";
|
||||||
|
"Paint.ColorSpectrum" = "Spectrum";
|
||||||
|
"Paint.ColorSliders" = "Sliders";
|
||||||
|
"Paint.ColorOpacity" = "OPACITY";
|
||||||
|
|
||||||
"StorageManagement.Title" = "Storage Usage";
|
"StorageManagement.Title" = "Storage Usage";
|
||||||
"StorageManagement.TitleCleared" = "Storage Cleared";
|
"StorageManagement.TitleCleared" = "Storage Cleared";
|
||||||
|
|
||||||
|
@ -2026,6 +2026,8 @@ private final class ColorPickerContent: CombinedComponent {
|
|||||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
let component = context.component
|
let component = context.component
|
||||||
let state = context.state
|
let state = context.state
|
||||||
|
let strings = environment.strings
|
||||||
|
|
||||||
state.colorChanged = component.colorChanged
|
state.colorChanged = component.colorChanged
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
@ -2079,7 +2081,7 @@ private final class ColorPickerContent: CombinedComponent {
|
|||||||
let title = title.update(
|
let title = title.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: "Colors",
|
string: strings.Paint_ColorTitle,
|
||||||
font: Font.semibold(17.0),
|
font: Font.semibold(17.0),
|
||||||
textColor: .white,
|
textColor: .white,
|
||||||
paragraphAlignment: .center
|
paragraphAlignment: .center
|
||||||
@ -2098,7 +2100,7 @@ private final class ColorPickerContent: CombinedComponent {
|
|||||||
|
|
||||||
let modeControl = modeControl.update(
|
let modeControl = modeControl.update(
|
||||||
component: SegmentedControlComponent(
|
component: SegmentedControlComponent(
|
||||||
values: ["Grid", "Spectrum", "Sliders"],
|
values: [strings.Paint_ColorGrid, strings.Paint_ColorSpectrum, strings.Paint_ColorSliders],
|
||||||
selectedIndex: 0,
|
selectedIndex: 0,
|
||||||
selectionChanged: { [weak state] index in
|
selectionChanged: { [weak state] index in
|
||||||
state?.updateSelectedMode(index)
|
state?.updateSelectedMode(index)
|
||||||
@ -2172,7 +2174,7 @@ private final class ColorPickerContent: CombinedComponent {
|
|||||||
let opacityTitle = opacityTitle.update(
|
let opacityTitle = opacityTitle.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: "OPACITY",
|
string: strings.Paint_ColorOpacity,
|
||||||
font: Font.semibold(13.0),
|
font: Font.semibold(13.0),
|
||||||
textColor: UIColor(rgb: 0x9b9da5),
|
textColor: UIColor(rgb: 0x9b9da5),
|
||||||
paragraphAlignment: .center
|
paragraphAlignment: .center
|
||||||
|
@ -134,6 +134,8 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
|
|
||||||
public var getEntityCenterPosition: () -> CGPoint = { return .zero }
|
public var getEntityCenterPosition: () -> CGPoint = { return .zero }
|
||||||
public var getEntityInitialRotation: () -> CGFloat = { return 0.0 }
|
public var getEntityInitialRotation: () -> CGFloat = { return 0.0 }
|
||||||
|
public var getEntityAdditionalScale: () -> CGFloat = { return 1.0 }
|
||||||
|
|
||||||
public var hasSelectionChanged: (Bool) -> Void = { _ in }
|
public var hasSelectionChanged: (Bool) -> Void = { _ in }
|
||||||
var selectionChanged: (DrawingEntity?) -> Void = { _ in }
|
var selectionChanged: (DrawingEntity?) -> Void = { _ in }
|
||||||
var requestedMenuForEntityView: (DrawingEntityView, Bool) -> Void = { _, _ in }
|
var requestedMenuForEntityView: (DrawingEntityView, Bool) -> Void = { _, _ in }
|
||||||
|
@ -60,8 +60,9 @@ final class NeonTool: DrawingElement {
|
|||||||
self.layer.addSublayer(self.fillLayer)
|
self.layer.addSublayer(self.fillLayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func updatePath(_ path: CGPath) {
|
fileprivate func updatePath(_ path: CGPath, shadowPath: CGPath) {
|
||||||
self.shadowLayer.path = path
|
self.shadowLayer.path = path
|
||||||
|
self.shadowLayer.shadowPath = shadowPath
|
||||||
self.borderLayer.path = path
|
self.borderLayer.path = path
|
||||||
self.fillLayer.path = path
|
self.fillLayer.path = path
|
||||||
}
|
}
|
||||||
@ -81,6 +82,7 @@ final class NeonTool: DrawingElement {
|
|||||||
private var addedPaths = 0
|
private var addedPaths = 0
|
||||||
|
|
||||||
fileprivate var renderPath: CGPath?
|
fileprivate var renderPath: CGPath?
|
||||||
|
fileprivate var shadowRenderPath: CGPath?
|
||||||
|
|
||||||
var translation: CGPoint = .zero
|
var translation: CGPoint = .zero
|
||||||
|
|
||||||
@ -91,7 +93,7 @@ final class NeonTool: DrawingElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var bounds: CGRect {
|
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)
|
return normalizeDrawingRect(renderPath.boundingBoxOfPath.insetBy(dx: -self.renderShadowRadius - 30.0, dy: -self.renderShadowRadius - 30.0), drawingSize: self.drawingSize)
|
||||||
} else {
|
} else {
|
||||||
return .zero
|
return .zero
|
||||||
@ -103,8 +105,8 @@ final class NeonTool: DrawingElement {
|
|||||||
self.drawingSize = drawingSize
|
self.drawingSize = drawingSize
|
||||||
self.color = color
|
self.color = color
|
||||||
|
|
||||||
let strokeWidth = min(drawingSize.width, drawingSize.height) * 0.01
|
let strokeWidth = min(drawingSize.width, drawingSize.height) * 0.008
|
||||||
let shadowRadius = min(drawingSize.width, drawingSize.height) * 0.03
|
let shadowRadius = min(drawingSize.width, drawingSize.height) * 0.02
|
||||||
|
|
||||||
let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.002)
|
let minLineWidth = max(1.0, max(drawingSize.width, drawingSize.height) * 0.002)
|
||||||
let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.07)
|
let maxLineWidth = max(10.0, max(drawingSize.width, drawingSize.height) * 0.07)
|
||||||
@ -138,9 +140,11 @@ final class NeonTool: DrawingElement {
|
|||||||
if let activePath {
|
if let activePath {
|
||||||
path?.addPath(activePath.cgPath)
|
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
|
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) {
|
func draw(in context: CGContext, size: CGSize) {
|
||||||
guard let path = self.renderPath else {
|
guard let path = self.renderPath, let shadowPath = self.shadowRenderPath else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
context.saveGState()
|
context.saveGState()
|
||||||
@ -172,15 +176,20 @@ final class NeonTool: DrawingElement {
|
|||||||
fillColor = shadowColor
|
fillColor = shadowColor
|
||||||
shadowColor = UIColor(rgb: 0x440881)
|
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.setLineCap(.round)
|
||||||
context.setFillColor(fillColor.cgColor)
|
context.setFillColor(fillColor.cgColor)
|
||||||
context.setStrokeColor(fillColor.cgColor)
|
context.setStrokeColor(fillColor.cgColor)
|
||||||
context.setLineWidth(self.renderStrokeWidth * 0.5)
|
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.drawPath(using: .fillStroke)
|
||||||
|
|
||||||
|
context.translateBy(x: shadowOffset.width, y: shadowOffset.height)
|
||||||
|
|
||||||
context.addPath(path)
|
context.addPath(path)
|
||||||
context.setShadow(offset: .zero, blur: 0.0, color: UIColor.clear.cgColor)
|
context.setShadow(offset: .zero, blur: 0.0, color: UIColor.clear.cgColor)
|
||||||
context.setLineWidth(self.renderStrokeWidth)
|
context.setLineWidth(self.renderStrokeWidth)
|
||||||
|
@ -2,6 +2,8 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
|
|
||||||
|
private let activeWidthFactor: CGFloat = 0.7
|
||||||
|
|
||||||
final class PenTool: DrawingElement {
|
final class PenTool: DrawingElement {
|
||||||
class RenderView: UIView, DrawingRenderView {
|
class RenderView: UIView, DrawingRenderView {
|
||||||
private weak var element: PenTool?
|
private weak var element: PenTool?
|
||||||
@ -14,7 +16,7 @@ final class PenTool: DrawingElement {
|
|||||||
private var segmentsCount = 0
|
private var segmentsCount = 0
|
||||||
|
|
||||||
private var drawScale = CGSize(width: 1.0, height: 1.0)
|
private var drawScale = CGSize(width: 1.0, height: 1.0)
|
||||||
|
|
||||||
func setup(size: CGSize, screenSize: CGSize, isEraser: Bool) {
|
func setup(size: CGSize, screenSize: CGSize, isEraser: Bool) {
|
||||||
self.isEraser = isEraser
|
self.isEraser = isEraser
|
||||||
|
|
||||||
@ -22,7 +24,6 @@ final class PenTool: DrawingElement {
|
|||||||
self.isOpaque = false
|
self.isOpaque = false
|
||||||
self.contentMode = .redraw
|
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 scale = CGSize(width: 0.33, height: 0.33)
|
||||||
let viewSize = CGSize(width: size.width * scale.width, height: size.height * scale.height)
|
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) {
|
fileprivate func draw(element: PenTool, rect: CGRect) {
|
||||||
self.element = element
|
self.element = element
|
||||||
|
|
||||||
@ -120,15 +140,71 @@ final class PenTool: DrawingElement {
|
|||||||
self.start = newStart
|
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
|
self.segmentsCount = element.segments.count
|
||||||
|
|
||||||
if let rect = rect {
|
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 {
|
} else {
|
||||||
self.activeView?.setNeedsDisplay()
|
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 {
|
class ActiveView: UIView {
|
||||||
weak var parent: RenderView?
|
weak var parent: RenderView?
|
||||||
override func draw(_ rect: CGRect) {
|
override func draw(_ rect: CGRect) {
|
||||||
@ -139,7 +215,12 @@ final class PenTool: DrawingElement {
|
|||||||
parent.displaySize = rect.size
|
parent.displaySize = rect.size
|
||||||
context.scaleBy(x: 1.0 / parent.drawScale.width, y: 1.0 / parent.drawScale.height)
|
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.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?
|
var blurredImage: UIImage?
|
||||||
|
|
||||||
private weak var currentRenderView: DrawingRenderView?
|
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 {
|
var isValid: Bool {
|
||||||
if self.hasArrow {
|
if self.hasArrow {
|
||||||
@ -217,7 +315,7 @@ final class PenTool: DrawingElement {
|
|||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupRenderView(screenSize: CGSize) -> DrawingRenderView? {
|
func setupRenderView(screenSize: CGSize) -> DrawingRenderView? {
|
||||||
let view = RenderView()
|
let view = RenderView()
|
||||||
view.setup(size: self.drawingSize, screenSize: screenSize, isEraser: self.isEraser)
|
view.setup(size: self.drawingSize, screenSize: screenSize, isEraser: self.isEraser)
|
||||||
@ -249,6 +347,8 @@ final class PenTool: DrawingElement {
|
|||||||
|
|
||||||
if state == .ended {
|
if state == .ended {
|
||||||
if !self.activeSegments.isEmpty {
|
if !self.activeSegments.isEmpty {
|
||||||
|
(self.currentRenderView as? RenderView)?.setupActiveSegmentsDrying()
|
||||||
|
|
||||||
self.segments.append(contentsOf: self.activeSegments)
|
self.segments.append(contentsOf: self.activeSegments)
|
||||||
self.smoothPoints.append(contentsOf: self.activeSmoothPoints)
|
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),
|
d: CGPoint(x: point.x + radius, y: point.y + 0.1),
|
||||||
radius1: radius,
|
radius1: radius,
|
||||||
radius2: 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))
|
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 d: CGPoint
|
||||||
let radius1: CGFloat
|
let radius1: CGFloat
|
||||||
let radius2: CGFloat
|
let radius2: CGFloat
|
||||||
|
let abCenter: CGPoint
|
||||||
|
let cdCenter: CGPoint
|
||||||
|
let perpendicular: CGPoint
|
||||||
let rect: CGRect
|
let rect: CGRect
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -372,6 +478,9 @@ final class PenTool: DrawingElement {
|
|||||||
d: CGPoint,
|
d: CGPoint,
|
||||||
radius1: CGFloat,
|
radius1: CGFloat,
|
||||||
radius2: CGFloat,
|
radius2: CGFloat,
|
||||||
|
abCenter: CGPoint,
|
||||||
|
cdCenter: CGPoint,
|
||||||
|
perpendicular: CGPoint,
|
||||||
rect: CGRect
|
rect: CGRect
|
||||||
) {
|
) {
|
||||||
self.a = a
|
self.a = a
|
||||||
@ -380,8 +489,43 @@ final class PenTool: DrawingElement {
|
|||||||
self.d = d
|
self.d = d
|
||||||
self.radius1 = radius1
|
self.radius1 = radius1
|
||||||
self.radius2 = radius2
|
self.radius2 = radius2
|
||||||
|
self.abCenter = abCenter
|
||||||
|
self.cdCenter = cdCenter
|
||||||
|
self.perpendicular = perpendicular
|
||||||
self.rect = rect
|
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 {
|
private struct Point {
|
||||||
@ -396,27 +540,16 @@ final class PenTool: DrawingElement {
|
|||||||
self.width = width
|
self.width = width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var points: [Point] = Array(repeating: Point(location: .zero, width: 0.0), count: 4)
|
private var currentVelocity: CGFloat?
|
||||||
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 func addPoint(_ point: DrawingPoint, state: DrawingGesturePipeline.DrawingGestureState, zoomScale: CGFloat) -> (Bool, CGRect)? {
|
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
|
var velocity = point.velocity
|
||||||
if velocity.isZero {
|
if velocity.isZero {
|
||||||
velocity = 1000.0
|
velocity = 1000.0
|
||||||
}
|
}
|
||||||
|
self.currentVelocity = velocity
|
||||||
|
|
||||||
var renderLineWidth = max(self.renderMinLineWidth, min(self.renderLineWidth - (velocity / 200.0), self.renderLineWidth))
|
var renderLineWidth = max(self.renderMinLineWidth, min(self.renderLineWidth - (velocity / 200.0), self.renderLineWidth))
|
||||||
if let previousRenderLineWidth = self.previousRenderLineWidth {
|
if let previousRenderLineWidth = self.previousRenderLineWidth {
|
||||||
@ -492,9 +625,9 @@ final class PenTool: DrawingElement {
|
|||||||
private func currentSmoothPoints(_ ctr: Int) -> [Point]? {
|
private func currentSmoothPoints(_ ctr: Int) -> [Point]? {
|
||||||
switch ctr {
|
switch ctr {
|
||||||
case 0:
|
case 0:
|
||||||
return [self.points[0]]
|
return nil//return [self.points[0]]
|
||||||
case 1:
|
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:
|
case 2:
|
||||||
return self.smoothPoints(.quad(self.points[0], self.points[1], self.points[2]))
|
return self.smoothPoints(.quad(self.points[0], self.points[1], self.points[2]))
|
||||||
case 3:
|
case 3:
|
||||||
@ -667,15 +800,29 @@ final class PenTool: DrawingElement {
|
|||||||
let segmentRect = CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
|
let segmentRect = CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
|
||||||
updateRect = updateRect.union(segmentRect)
|
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)
|
segments.append(segment)
|
||||||
}
|
}
|
||||||
return (segments, !updateRect.isNull ? updateRect : nil)
|
return (segments, !updateRect.isNull ? updateRect : nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var segmentPaths: [Int: CGPath] = [:]
|
private func pathForSegment(_ segment: Segment, abFactor: CGFloat = 1.0, cdFactor: CGFloat = 1.0) -> CGPath {
|
||||||
|
var segment = segment
|
||||||
private func pathForSegment(_ segment: Segment) -> CGPath {
|
if abFactor != 1.0 || cdFactor != 1.0 {
|
||||||
|
segment = segment.withMultiplied(abFactor: abFactor, cdFactor: cdFactor)
|
||||||
|
}
|
||||||
|
|
||||||
let path = CGMutablePath()
|
let path = CGMutablePath()
|
||||||
path.move(to: segment.b)
|
path.move(to: segment.b)
|
||||||
|
|
||||||
@ -713,6 +860,19 @@ final class PenTool: DrawingElement {
|
|||||||
return path
|
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) {
|
private func drawSegments(in context: CGContext, from: Int, to: Int) {
|
||||||
context.setFillColor(self.renderColor.cgColor)
|
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)
|
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 {
|
for segment in self.activeSegments {
|
||||||
let path = self.pathForSegment(segment)
|
let path = self.pathForSegment(segment)
|
||||||
context.addPath(path)
|
context.addPath(path)
|
||||||
context.fillPath()
|
if let _ = strokeWidth {
|
||||||
|
context.drawPath(using: .fillStroke)
|
||||||
|
} else {
|
||||||
|
context.fillPath()
|
||||||
|
}
|
||||||
|
abFactor += delta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -308,9 +308,11 @@ struct DrawingState: Equatable {
|
|||||||
|
|
||||||
final class DrawingSettings: Codable, Equatable {
|
final class DrawingSettings: Codable, Equatable {
|
||||||
let tools: [DrawingToolState]
|
let tools: [DrawingToolState]
|
||||||
|
let colors: [DrawingColor]
|
||||||
|
|
||||||
init(tools: [DrawingToolState]) {
|
init(tools: [DrawingToolState], colors: [DrawingColor]) {
|
||||||
self.tools = tools
|
self.tools = tools
|
||||||
|
self.colors = colors
|
||||||
}
|
}
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
init(from decoder: Decoder) throws {
|
||||||
@ -321,6 +323,12 @@ final class DrawingSettings: Codable, Equatable {
|
|||||||
} else {
|
} else {
|
||||||
self.tools = DrawingState.initial.tools
|
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 {
|
func encode(to encoder: Encoder) throws {
|
||||||
@ -329,10 +337,13 @@ final class DrawingSettings: Codable, Equatable {
|
|||||||
if let data = try? JSONEncoder().encode(self.tools) {
|
if let data = try? JSONEncoder().encode(self.tools) {
|
||||||
try container.encode(data, forKey: "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 {
|
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 tools = self.drawingState.tools
|
||||||
let _ = (self.context.sharedContext.accountManager.transaction { transaction -> Void in
|
let _ = (self.context.sharedContext.accountManager.transaction { transaction -> Void in
|
||||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.drawingSettings, { _ in
|
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.drawingSettings, { _ in
|
||||||
return PreferencesEntry(DrawingSettings(tools: tools))
|
return PreferencesEntry(DrawingSettings(tools: tools, colors: []))
|
||||||
})
|
})
|
||||||
}).start()
|
}).start()
|
||||||
}
|
}
|
||||||
|
@ -390,9 +390,11 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
|
|||||||
self.textView.becomeFirstResponder()
|
self.textView.becomeFirstResponder()
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 0.65, initialSpringVelocity: 0.0) {
|
UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 0.65, initialSpringVelocity: 0.0) {
|
||||||
self.transform = .identity
|
if let parentView = self.superview as? DrawingEntitiesView {
|
||||||
if let superview = self.superview {
|
let scale = parentView.getEntityAdditionalScale() / (parentView.drawingView?.zoomScale ?? 1.0)
|
||||||
self.center = CGPoint(x: superview.bounds.width / 2.0, y: superview.bounds.height / 2.0)
|
self.transform = CGAffineTransformMakeRotation(parentView.getEntityInitialRotation()).scaledBy(x: scale, y: scale)
|
||||||
|
|
||||||
|
self.center = parentView.getEntityCenterPosition()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,7 +555,13 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, UIPencilInt
|
|||||||
self.currentDrawingViewContainer.mask = nil
|
self.currentDrawingViewContainer.mask = nil
|
||||||
self.currentDrawingViewContainer.image = nil
|
self.currentDrawingViewContainer.image = nil
|
||||||
} else {
|
} 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
|
self.currentDrawingRenderView = nil
|
||||||
}
|
}
|
||||||
@ -651,10 +657,14 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, UIPencilInt
|
|||||||
|
|
||||||
self.updateInternalState()
|
self.updateInternalState()
|
||||||
}
|
}
|
||||||
if let uncommitedElement = self.uncommitedElement as? PenTool, uncommitedElement.hasArrow {
|
if let uncommitedElement = self.uncommitedElement as? PenTool {
|
||||||
uncommitedElement.finishArrow({
|
if uncommitedElement.hasArrow {
|
||||||
|
uncommitedElement.finishArrow {
|
||||||
|
complete(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
complete(true)
|
complete(true)
|
||||||
})
|
}
|
||||||
} else {
|
} else {
|
||||||
complete(synchronous)
|
complete(synchronous)
|
||||||
}
|
}
|
||||||
|
@ -231,9 +231,9 @@ final class TextFontComponent: Component {
|
|||||||
self.button.clipsToBounds = true
|
self.button.clipsToBounds = true
|
||||||
self.button.setTitle(value.title, for: .normal)
|
self.button.setTitle(value.title, for: .normal)
|
||||||
self.button.titleLabel?.font = value.uiFont(size: 13.0)
|
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)
|
var buttonSize = self.button.sizeThatFits(availableSize)
|
||||||
buttonSize.width += 39.0 - 13.0
|
buttonSize.width += 20.0
|
||||||
buttonSize.height = 30.0
|
buttonSize.height = 30.0
|
||||||
transition.setFrame(view: self.button, frame: CGRect(origin: .zero, size: buttonSize))
|
transition.setFrame(view: self.button, frame: CGRect(origin: .zero, size: buttonSize))
|
||||||
self.button.layer.cornerRadius = 11.0
|
self.button.layer.cornerRadius = 11.0
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
|
|
||||||
@property (nonatomic, copy) CGPoint (^ _Nonnull getEntityCenterPosition)(void);
|
@property (nonatomic, copy) CGPoint (^ _Nonnull getEntityCenterPosition)(void);
|
||||||
@property (nonatomic, copy) CGFloat (^ _Nonnull getEntityInitialRotation)(void);
|
@property (nonatomic, copy) CGFloat (^ _Nonnull getEntityInitialRotation)(void);
|
||||||
|
@property (nonatomic, copy) CGFloat (^ _Nonnull getEntityAdditionalScale)(void);
|
||||||
|
|
||||||
@property (nonatomic, copy) void(^ _Nonnull hasSelectionChanged)(bool);
|
@property (nonatomic, copy) void(^ _Nonnull hasSelectionChanged)(bool);
|
||||||
@property (nonatomic, readonly) BOOL hasSelection;
|
@property (nonatomic, readonly) BOOL hasSelection;
|
||||||
|
@ -246,6 +246,14 @@ const CGSize TGPhotoPaintingMaxSize = { 1920.0f, 1920.0f };
|
|||||||
return [strongSelf entityInitialRotation];
|
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];
|
[self.view setNeedsLayout];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -625,6 +625,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
case .action, .optionalAction:
|
case .action, .optionalAction:
|
||||||
break
|
break
|
||||||
case let .openContextMenu(tapMessage, selectAll, subFrame):
|
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)
|
item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user