mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Camera and media editor improvements
This commit is contained in:
@@ -0,0 +1,814 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import LegacyComponents
|
||||
import MediaEditor
|
||||
|
||||
private final class BlurModeComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
let title: String
|
||||
let icon: UIImage?
|
||||
let isSelected: Bool
|
||||
|
||||
init(
|
||||
title: String,
|
||||
icon: UIImage?,
|
||||
isSelected: Bool
|
||||
) {
|
||||
self.title = title
|
||||
self.icon = icon
|
||||
self.isSelected = isSelected
|
||||
}
|
||||
|
||||
static func ==(lhs: BlurModeComponent, rhs: BlurModeComponent) -> Bool {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.icon !== rhs.icon {
|
||||
return false
|
||||
}
|
||||
if lhs.isSelected != rhs.isSelected {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let icon = ComponentView<Empty>()
|
||||
private let title = ComponentView<Empty>()
|
||||
|
||||
private var component: BlurModeComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: BlurModeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let iconSize = self.icon.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
Image(
|
||||
image: component.icon,
|
||||
tintColor: component.isSelected ? UIColor(rgb: 0xf8d74a) : .white,
|
||||
size: CGSize(width: 30.0, height: 30.0)
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
Text(
|
||||
text: component.title,
|
||||
font: Font.regular(14.0),
|
||||
color: component.isSelected ? UIColor(rgb: 0xf8d74a) : UIColor(rgb: 0x808080)
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let spacing: CGFloat = 3.0
|
||||
let size = CGSize(width: 66.0, height: iconSize.height + spacing + titleSize.height)
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - iconSize.width) / 2.0), y: 0.0), size: iconSize)
|
||||
if let view = self.icon.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: iconFrame)
|
||||
}
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: iconSize.height + spacing), size: titleSize)
|
||||
if let view = self.title.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: titleFrame)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class BlurComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
let value: BlurValue
|
||||
let hasPortrait: Bool
|
||||
let valueUpdated: (BlurValue) -> Void
|
||||
|
||||
init(
|
||||
value: BlurValue,
|
||||
hasPortrait: Bool,
|
||||
valueUpdated: @escaping (BlurValue) -> Void
|
||||
) {
|
||||
self.value = value
|
||||
self.hasPortrait = hasPortrait
|
||||
self.valueUpdated = valueUpdated
|
||||
}
|
||||
|
||||
static func ==(lhs: BlurComponent, rhs: BlurComponent) -> Bool {
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
if lhs.hasPortrait != rhs.hasPortrait {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(value: self.value)
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
var value: BlurValue
|
||||
|
||||
init(value: BlurValue) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let title = ComponentView<Empty>()
|
||||
private let offButton = ComponentView<Empty>()
|
||||
private let radialButton = ComponentView<Empty>()
|
||||
private let linearButton = ComponentView<Empty>()
|
||||
private let portraitButton = ComponentView<Empty>()
|
||||
|
||||
private let slider = ComponentView<Empty>()
|
||||
|
||||
private var component: BlurComponent?
|
||||
private weak var state: State?
|
||||
|
||||
private let offImage = UIImage(bundleImageName: "Media Editor/BlurOff")
|
||||
private let radialImage = UIImage(bundleImageName: "Media Editor/BlurRadial")
|
||||
private let linearImage = UIImage(bundleImageName: "Media Editor/BlurLinear")
|
||||
private let portraitImage = UIImage(bundleImageName: "Media Editor/BlurPortrait")
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: BlurComponent, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
state.value = component.value
|
||||
|
||||
let valueUpdated = component.valueUpdated
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
Text(
|
||||
text: "Blur",
|
||||
font: Font.regular(14.0),
|
||||
color: UIColor(rgb: 0x808080)
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: 11.0), size: titleSize)
|
||||
if let view = self.title.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: titleFrame)
|
||||
}
|
||||
|
||||
let offButtonSize = self.offButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
BlurModeComponent(
|
||||
title: "Off",
|
||||
icon: self.offImage,
|
||||
isSelected: state.value.mode == .off
|
||||
)
|
||||
),
|
||||
action: { [weak state] in
|
||||
if let state {
|
||||
valueUpdated(state.value.withUpdatedMode(.off))
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let _ = self.radialButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
BlurModeComponent(
|
||||
title: "Radial",
|
||||
icon: self.radialImage,
|
||||
isSelected: state.value.mode == .radial
|
||||
)
|
||||
),
|
||||
action: { [weak state] in
|
||||
if let state {
|
||||
valueUpdated(state.value.withUpdatedMode(.radial))
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let _ = self.linearButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
BlurModeComponent(
|
||||
title: "Linear",
|
||||
icon: self.linearImage,
|
||||
isSelected: state.value.mode == .linear
|
||||
)
|
||||
),
|
||||
action: { [weak state] in
|
||||
if let state {
|
||||
valueUpdated(state.value.withUpdatedMode(.linear))
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let _ = self.portraitButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
BlurModeComponent(
|
||||
title: "Portrait",
|
||||
icon: self.portraitImage,
|
||||
isSelected: state.value.mode == .portrait
|
||||
)
|
||||
),
|
||||
action: { [weak state] in
|
||||
if let state {
|
||||
valueUpdated(state.value.withUpdatedMode(.portrait))
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let sliderSize = self.slider.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
AdjustmentSliderComponent(
|
||||
title: "",
|
||||
value: state.value.intensity,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
startValue: 0.0,
|
||||
isEnabled: state.value.mode != .off,
|
||||
trackColor: nil,
|
||||
updateValue: { [weak state] value in
|
||||
if let state {
|
||||
valueUpdated(state.value.withUpdatedIntensity(value))
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
var buttons = [self.offButton, self.radialButton, self.linearButton]
|
||||
if component.hasPortrait {
|
||||
buttons.append(self.portraitButton)
|
||||
}
|
||||
|
||||
let topInset: CGFloat = 34.0
|
||||
let horizontalSpacing: CGFloat = 24.0
|
||||
let width: CGFloat = CGFloat(buttons.count) * offButtonSize.width + (CGFloat(buttons.count - 1) * horizontalSpacing)
|
||||
let commonX = floorToScreenPixels((availableSize.width - width) / 2.0)
|
||||
var offsetX: CGFloat = commonX
|
||||
for button in buttons {
|
||||
if let view = button.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: offsetX, y: topInset), size: offButtonSize))
|
||||
}
|
||||
offsetX += offButtonSize.width + horizontalSpacing
|
||||
}
|
||||
|
||||
let verticalSpacing: CGFloat = -5.0
|
||||
let sliderFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset + offButtonSize.height + verticalSpacing), size: sliderSize)
|
||||
if let view = self.slider.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: sliderFrame)
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: topInset + offButtonSize.height + verticalSpacing + sliderSize.height + 6.0)
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private let blurInsetProximity: CGFloat = 20.0
|
||||
private let blurMinimumFalloff: Float = 0.1
|
||||
private let blurMinimumDifference: Float = 0.02
|
||||
private let blurViewCenterInset: CGFloat = 30.0
|
||||
private let blurViewRadiusInset: CGFloat = 30.0
|
||||
|
||||
final class BlurScreenComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
let value: BlurValue
|
||||
let valueUpdated: (BlurValue) -> Void
|
||||
|
||||
init(
|
||||
value: BlurValue,
|
||||
valueUpdated: @escaping (BlurValue) -> Void
|
||||
|
||||
) {
|
||||
self.value = value
|
||||
self.valueUpdated = valueUpdated
|
||||
}
|
||||
|
||||
static func ==(lhs: BlurScreenComponent, rhs: BlurScreenComponent) -> Bool {
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView, UIGestureRecognizerDelegate {
|
||||
enum Control {
|
||||
case center
|
||||
case innerRadius
|
||||
case outerRadius
|
||||
case rotation
|
||||
case wholeArea
|
||||
}
|
||||
private var component: BlurScreenComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundColor = .clear
|
||||
self.contentMode = .redraw
|
||||
self.isOpaque = false
|
||||
|
||||
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
|
||||
panGestureRecognizer.delegate = self
|
||||
self.addGestureRecognizer(panGestureRecognizer)
|
||||
|
||||
let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(self.handlePinch(_:)))
|
||||
pinchGestureRecognizer.delegate = self
|
||||
self.addGestureRecognizer(pinchGestureRecognizer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private var activeControl: Control?
|
||||
private var startCenterPoint: CGPoint?
|
||||
private var startDistance: CGFloat?
|
||||
private var startRadius: CGFloat?
|
||||
@objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
let location = gestureRecognizer.location(in: gestureRecognizer.view)
|
||||
let centerPoint = CGPoint(
|
||||
x: component.value.position.x * self.frame.width,
|
||||
y: component.value.position.y * self.frame.height
|
||||
)
|
||||
let delta = CGPoint(x: location.x - centerPoint.x, y: location.y - centerPoint.y)
|
||||
let shorterSide = min(self.frame.width, self.frame.height)
|
||||
let innerRadius = shorterSide * CGFloat(component.value.falloff)
|
||||
let outerRadius = shorterSide * CGFloat(component.value.size)
|
||||
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
switch component.value.mode {
|
||||
case .radial:
|
||||
let distance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
||||
|
||||
let close = abs(outerRadius - innerRadius) < blurInsetProximity
|
||||
let innerRadiusOuterInset = close ? 0 : blurViewRadiusInset
|
||||
let outerRadiusInnerInset = close ? 0 : blurViewRadiusInset
|
||||
|
||||
if distance < blurViewCenterInset {
|
||||
self.activeControl = .center
|
||||
self.startCenterPoint = centerPoint
|
||||
}
|
||||
else if distance > innerRadius - blurViewRadiusInset && distance < innerRadius + innerRadiusOuterInset {
|
||||
self.activeControl = .innerRadius
|
||||
self.startDistance = distance
|
||||
self.startRadius = innerRadius
|
||||
} else if distance > outerRadius - outerRadiusInnerInset && distance < outerRadius + blurViewRadiusInset {
|
||||
self.activeControl = .outerRadius
|
||||
self.startDistance = distance
|
||||
self.startRadius = outerRadius
|
||||
}
|
||||
case .linear:
|
||||
let radialDistance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
||||
let distance = abs(delta.x * cos(CGFloat(component.value.rotation) + .pi / 2.0) + delta.y * sin(CGFloat(component.value.rotation) + .pi / 2.0))
|
||||
|
||||
let close = abs(outerRadius - innerRadius) < blurInsetProximity
|
||||
let innerRadiusOuterInset = close ? 0 : blurViewRadiusInset
|
||||
let outerRadiusInnerInset = close ? 0 : blurViewRadiusInset
|
||||
|
||||
if radialDistance < blurViewCenterInset {
|
||||
self.activeControl = .center
|
||||
self.startCenterPoint = centerPoint
|
||||
} else if distance > innerRadius - blurViewRadiusInset && distance < innerRadius + innerRadiusOuterInset {
|
||||
self.activeControl = .innerRadius
|
||||
self.startDistance = distance
|
||||
self.startRadius = innerRadius
|
||||
} else if distance > outerRadius - outerRadiusInnerInset && distance < outerRadius + blurViewRadiusInset {
|
||||
self.activeControl = .outerRadius;
|
||||
self.startDistance = distance
|
||||
self.startRadius = outerRadius
|
||||
} else if distance <= innerRadius - blurViewRadiusInset || distance >= outerRadius + blurViewRadiusInset {
|
||||
self.activeControl = .rotation
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .changed:
|
||||
switch component.value.mode {
|
||||
case .radial:
|
||||
guard let activeControl = self.activeControl else {
|
||||
return
|
||||
}
|
||||
let distance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
||||
|
||||
switch activeControl {
|
||||
case .center:
|
||||
guard let startCenterPoint = self.startCenterPoint else {
|
||||
return
|
||||
}
|
||||
let translation = gestureRecognizer.translation(in: gestureRecognizer.view)
|
||||
let centerPoint = CGPoint(
|
||||
x: max(0.0, min(self.frame.width, startCenterPoint.x + translation.x)),
|
||||
y: max(0.0, min(self.frame.height, startCenterPoint.y + translation.y))
|
||||
)
|
||||
let position = CGPoint(
|
||||
x: centerPoint.x / self.frame.width,
|
||||
y: centerPoint.y / self.frame.height
|
||||
)
|
||||
component.valueUpdated(component.value.withUpdatedPosition(position))
|
||||
case .innerRadius:
|
||||
guard let startDistance = self.startDistance, let startRadius = self.startRadius else {
|
||||
return
|
||||
}
|
||||
let delta = distance - startDistance
|
||||
let falloff = min(max(blurMinimumFalloff, Float((startRadius + delta) / shorterSide)), component.value.size - blurMinimumDifference)
|
||||
component.valueUpdated(component.value.withUpdatedFalloff(falloff))
|
||||
case .outerRadius:
|
||||
guard let startDistance = self.startDistance, let startRadius = self.startRadius else {
|
||||
return
|
||||
}
|
||||
let delta = distance - startDistance
|
||||
let size = max(component.value.falloff + blurMinimumDifference, Float((startRadius + delta) / shorterSide))
|
||||
component.valueUpdated(component.value.withUpdatedSize(size))
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .linear:
|
||||
guard let activeControl = self.activeControl else {
|
||||
return
|
||||
}
|
||||
let distance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
||||
|
||||
switch activeControl {
|
||||
case .center:
|
||||
guard let startCenterPoint = self.startCenterPoint else {
|
||||
return
|
||||
}
|
||||
let translation = gestureRecognizer.translation(in: gestureRecognizer.view)
|
||||
let centerPoint = CGPoint(
|
||||
x: max(0.0, min(self.frame.width, startCenterPoint.x + translation.x)),
|
||||
y: max(0.0, min(self.frame.height, startCenterPoint.y + translation.y))
|
||||
)
|
||||
let position = CGPoint(
|
||||
x: centerPoint.x / self.frame.width,
|
||||
y: centerPoint.y / self.frame.height
|
||||
)
|
||||
component.valueUpdated(component.value.withUpdatedPosition(position))
|
||||
case .innerRadius:
|
||||
guard let startDistance = self.startDistance, let startRadius = self.startRadius else {
|
||||
return
|
||||
}
|
||||
let delta = distance - startDistance
|
||||
let falloff = min(max(blurMinimumFalloff, Float((startRadius + delta) / shorterSide)), component.value.size - blurMinimumDifference)
|
||||
component.valueUpdated(component.value.withUpdatedFalloff(falloff))
|
||||
case .outerRadius:
|
||||
guard let startDistance = self.startDistance, let startRadius = self.startRadius else {
|
||||
return
|
||||
}
|
||||
let delta = distance - startDistance
|
||||
let size = max(component.value.falloff + blurMinimumDifference, Float((startRadius + delta) / shorterSide))
|
||||
component.valueUpdated(component.value.withUpdatedSize(size))
|
||||
case .rotation:
|
||||
let translation = gestureRecognizer.translation(in: gestureRecognizer.view)
|
||||
var clockwise = false
|
||||
let right = location.x > centerPoint.x
|
||||
let bottom = location.y > centerPoint.y
|
||||
|
||||
if !right && !bottom {
|
||||
if abs(translation.y) > abs(translation.x) {
|
||||
if translation.y < 0.0 {
|
||||
clockwise = true
|
||||
}
|
||||
} else {
|
||||
if translation.x > 0.0 {
|
||||
clockwise = true
|
||||
}
|
||||
}
|
||||
} else if right && !bottom {
|
||||
if abs(translation.y) > abs(translation.x) {
|
||||
if translation.y > 0.0 {
|
||||
clockwise = true
|
||||
}
|
||||
} else
|
||||
{
|
||||
if translation.x > 0.0 {
|
||||
clockwise = true
|
||||
}
|
||||
}
|
||||
} else if right && bottom {
|
||||
if abs(translation.y) > abs(translation.x) {
|
||||
if translation.y > 0 {
|
||||
clockwise = true
|
||||
}
|
||||
} else {
|
||||
if translation.x < 0 {
|
||||
clockwise = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if abs(translation.y) > abs(translation.x) {
|
||||
if translation.y < 0 {
|
||||
clockwise = true
|
||||
}
|
||||
} else {
|
||||
if translation.x < 0 {
|
||||
clockwise = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let delta = sqrt(translation.x * translation.x + translation.y * translation.y)
|
||||
|
||||
let angleInDegress = radiansToDegrees(radians: CGFloat(component.value.rotation))
|
||||
let updatedAngle = angleInDegress + delta * (clockwise ? 1.0 : -1.0) / .pi / 1.15
|
||||
component.valueUpdated(component.value.withUpdatedRotation(Float(degreesToRadians(degrees: updatedAngle))))
|
||||
|
||||
gestureRecognizer.setTranslation(.zero, in: gestureRecognizer.view)
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
self.activeControl = nil
|
||||
self.startCenterPoint = nil
|
||||
self.startDistance = nil
|
||||
self.startRadius = nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handlePinch(_ gestureRecognizer: UIPinchGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.activeControl = .wholeArea
|
||||
case .changed:
|
||||
let scale = Float(gestureRecognizer.scale)
|
||||
|
||||
let size = max(component.value.falloff + blurMinimumDifference, component.value.size * scale)
|
||||
let falloff = max(blurMinimumFalloff, component.value.falloff * scale)
|
||||
component.valueUpdated(component.value.withUpdatedSize(size).withUpdatedFalloff(falloff))
|
||||
|
||||
gestureRecognizer.scale = 1.0
|
||||
default:
|
||||
self.activeControl = nil
|
||||
}
|
||||
}
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
guard let component = self.component else {
|
||||
return false
|
||||
}
|
||||
|
||||
let location = gestureRecognizer.location(in: gestureRecognizer.view)
|
||||
let centerPoint = CGPoint(
|
||||
x: component.value.position.x * self.frame.width,
|
||||
y: component.value.position.y * self.frame.height
|
||||
)
|
||||
let delta = CGPoint(x: location.x - centerPoint.x, y: location.y - centerPoint.y)
|
||||
let innerRadius = min(self.frame.width, self.frame.height) * CGFloat(component.value.falloff)
|
||||
let outerRadius = min(self.frame.width, self.frame.height) * CGFloat(component.value.size)
|
||||
|
||||
switch component.value.mode {
|
||||
case .radial:
|
||||
let distance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
||||
|
||||
let close = abs(outerRadius - innerRadius) < blurInsetProximity
|
||||
let innerRadiusOuterInset = close ? 0.0 : blurViewRadiusInset
|
||||
let outerRadiusInnerInset = close ? 0.0 : blurViewRadiusInset
|
||||
|
||||
if distance < blurViewCenterInset && gestureRecognizer is UIPanGestureRecognizer {
|
||||
return true
|
||||
} else if distance > innerRadius - blurViewRadiusInset && distance < innerRadius + innerRadiusOuterInset {
|
||||
return true
|
||||
} else if distance > outerRadius - outerRadiusInnerInset && distance < outerRadius + blurViewRadiusInset {
|
||||
return true
|
||||
}
|
||||
case .linear:
|
||||
let radialDistance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
||||
let distance = abs(delta.x * cos(CGFloat(component.value.rotation) + .pi / 2.0) + delta.y * sin(CGFloat(component.value.rotation) + .pi / 2.0))
|
||||
|
||||
let close = abs(outerRadius - innerRadius) < blurInsetProximity
|
||||
let innerRadiusOuterInset = close ? 0.0 : blurViewRadiusInset
|
||||
let outerRadiusInnerInset = close ? 0.0 : blurViewRadiusInset
|
||||
|
||||
if radialDistance < blurViewCenterInset && gestureRecognizer is UIPanGestureRecognizer {
|
||||
return true
|
||||
} else if distance > innerRadius - blurViewRadiusInset && distance < innerRadius + innerRadiusOuterInset {
|
||||
return true
|
||||
} else if distance > outerRadius - outerRadiusInnerInset && distance < outerRadius + blurViewRadiusInset {
|
||||
return true
|
||||
} else if distance <= innerRadius - blurViewRadiusInset || distance >= outerRadius + blurViewRadiusInset {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func update(component: BlurScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
self.setNeedsDisplay()
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard let context = UIGraphicsGetCurrentContext(), let component = self.component else {
|
||||
return
|
||||
}
|
||||
guard ![.off, .portrait].contains(component.value.mode) else {
|
||||
return
|
||||
}
|
||||
|
||||
let centerPoint = CGPoint(
|
||||
x: component.value.position.x * rect.size.width,
|
||||
y: component.value.position.y * rect.size.height
|
||||
)
|
||||
let innerRadius = min(rect.size.width, rect.size.height) * CGFloat(component.value.falloff)
|
||||
let outerRadius = min(rect.size.width, rect.size.height) * CGFloat(component.value.size)
|
||||
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.setShadow(offset: .zero, blur: 2.5, color: UIColor(rgb: 0x000000, alpha: 0.3).cgColor)
|
||||
|
||||
let knobSize = CGSize(width: 16.0, height: 16.0)
|
||||
switch component.value.mode {
|
||||
case .radial:
|
||||
var radSpace = degreesToRadians(degrees: 6.15)
|
||||
var radLen = degreesToRadians(degrees: 10.2)
|
||||
for i in 0 ..< 22 {
|
||||
let cgPath = CGMutablePath()
|
||||
cgPath.addArc(
|
||||
center: centerPoint,
|
||||
radius: innerRadius,
|
||||
startAngle: CGFloat(i) * (radSpace + radLen),
|
||||
endAngle: CGFloat(i) * (radSpace + radLen) + radLen,
|
||||
clockwise: false
|
||||
)
|
||||
let strokedArc = cgPath.copy(strokingWithWidth: 1.5, lineCap: .butt, lineJoin: .miter, miterLimit: 10.0)
|
||||
context.addPath(strokedArc)
|
||||
}
|
||||
|
||||
radSpace = degreesToRadians(degrees: 2.02)
|
||||
radLen = degreesToRadians(degrees: 3.6)
|
||||
for i in 0 ..< 64 {
|
||||
let cgPath = CGMutablePath()
|
||||
cgPath.addArc(
|
||||
center: centerPoint,
|
||||
radius: outerRadius,
|
||||
startAngle: CGFloat(i) * (radSpace + radLen),
|
||||
endAngle: CGFloat(i) * (radSpace + radLen) + radLen,
|
||||
clockwise: false
|
||||
)
|
||||
let strokedArc = cgPath.copy(strokingWithWidth: 1.5, lineCap: .butt, lineJoin: .miter, miterLimit: 10.0)
|
||||
context.addPath(strokedArc)
|
||||
}
|
||||
context.fillPath()
|
||||
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: centerPoint.x - knobSize.width / 2.0, y: centerPoint.y - knobSize.height / 2.0), size: knobSize))
|
||||
case .linear:
|
||||
context.translateBy(x: centerPoint.x, y: centerPoint.y)
|
||||
context.rotate(by: CGFloat(component.value.rotation))
|
||||
|
||||
let space: CGFloat = 6.0
|
||||
var length: CGFloat = 12.0
|
||||
let thickness: CGFloat = 1.5
|
||||
|
||||
for i in 0 ..< 30 {
|
||||
context.addRect(CGRect(x: CGFloat(i) * (length + space), y: -innerRadius, width: length, height: thickness))
|
||||
context.addRect(CGRect(x: CGFloat(-i) * (length + space) - space - length, y: -innerRadius, width: length, height: thickness))
|
||||
|
||||
context.addRect(CGRect(x: CGFloat(i) * (length + space), y: innerRadius, width: length, height: thickness))
|
||||
context.addRect(CGRect(x: CGFloat(-i) * (length + space) - space - length, y: innerRadius, width: length, height: thickness))
|
||||
}
|
||||
|
||||
length = 6.0
|
||||
|
||||
for i in 0 ..< 64 {
|
||||
context.addRect(CGRect(x: CGFloat(i) * (length + space), y: -outerRadius, width: length, height: thickness))
|
||||
context.addRect(CGRect(x: CGFloat(-i) * (length + space) - space - length, y: -outerRadius, width: length, height: thickness))
|
||||
|
||||
context.addRect(CGRect(x: CGFloat(i) * (length + space), y: outerRadius, width: length, height: thickness))
|
||||
context.addRect(CGRect(x: CGFloat(-i) * (length + space) - space - length, y: outerRadius, width: length, height: thickness))
|
||||
}
|
||||
|
||||
context.fillPath()
|
||||
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: -knobSize.width / 2.0, y: -knobSize.height / 2.0), size: knobSize))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private func degreesToRadians(degrees: CGFloat) -> CGFloat {
|
||||
return degrees * .pi / 180.0
|
||||
}
|
||||
|
||||
private func radiansToDegrees(radians: CGFloat) -> CGFloat {
|
||||
return radians * 180.0 / .pi
|
||||
}
|
||||
Reference in New Issue
Block a user