import Foundation import UIKit import Display import ComponentFlow import SwiftSignalKit import LegacyComponents import MediaEditor final class AdjustmentSliderComponent: Component { typealias EnvironmentType = Empty let title: String let value: Float let minValue: Float let maxValue: Float let startValue: Float let isEnabled: Bool let trackColor: UIColor? let displayValue: Bool let valueUpdated: (Float) -> Void let isTrackingUpdated: ((Bool) -> Void)? init( title: String, value: Float, minValue: Float, maxValue: Float, startValue: Float, isEnabled: Bool, trackColor: UIColor?, displayValue: Bool, valueUpdated: @escaping (Float) -> Void, isTrackingUpdated: ((Bool) -> Void)? = nil ) { self.title = title self.value = value self.minValue = minValue self.maxValue = maxValue self.startValue = startValue self.isEnabled = isEnabled self.trackColor = trackColor self.displayValue = displayValue self.valueUpdated = valueUpdated self.isTrackingUpdated = isTrackingUpdated } static func ==(lhs: AdjustmentSliderComponent, rhs: AdjustmentSliderComponent) -> Bool { if lhs.title != rhs.title { return false } if lhs.value != rhs.value { return false } if lhs.minValue != rhs.minValue { return false } if lhs.maxValue != rhs.maxValue { return false } if lhs.startValue != rhs.startValue { return false } if lhs.isEnabled != rhs.isEnabled { return false } if lhs.trackColor != rhs.trackColor { return false } if lhs.displayValue != rhs.displayValue { return false } return true } final class View: UIView, UITextFieldDelegate { private let title = ComponentView() private let value = ComponentView() private var sliderView: TGPhotoEditorSliderView? private var component: AdjustmentSliderComponent? 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: AdjustmentSliderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.component = component self.state = state var internalIsTrackingUpdated: ((Bool) -> Void)? if let isTrackingUpdated = component.isTrackingUpdated { internalIsTrackingUpdated = { [weak self] isTracking in if let self { if isTracking { self.sliderView?.bordered = true } else { Queue.mainQueue().after(0.1) { self.sliderView?.bordered = false } } isTrackingUpdated(isTracking) let transition: Transition if isTracking { transition = .immediate } else { transition = .easeInOut(duration: 0.25) } if let titleView = self.title.view { transition.setAlpha(view: titleView, alpha: isTracking ? 0.0 : 1.0) } if let valueView = self.value.view { transition.setAlpha(view: valueView, alpha: isTracking ? 0.0 : 1.0) } } } } let sliderView: TGPhotoEditorSliderView if let current = self.sliderView { sliderView = current sliderView.value = CGFloat(component.value) } else { sliderView = TGPhotoEditorSliderView() sliderView.backgroundColor = .clear sliderView.startColor = UIColor(rgb: 0xffffff) sliderView.enablePanHandling = true sliderView.trackCornerRadius = 1.0 sliderView.lineSize = 2.0 sliderView.minimumValue = CGFloat(component.minValue) sliderView.maximumValue = CGFloat(component.maxValue) sliderView.startValue = CGFloat(component.startValue) sliderView.value = CGFloat(component.value) sliderView.disablesInteractiveTransitionGestureRecognizer = true sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) sliderView.layer.allowsGroupOpacity = true self.sliderView = sliderView self.addSubview(sliderView) } sliderView.interactionBegan = { internalIsTrackingUpdated?(true) } sliderView.interactionEnded = { internalIsTrackingUpdated?(false) } if component.isEnabled { sliderView.alpha = 1.3 sliderView.trackColor = component.trackColor ?? UIColor(rgb: 0xffffff) sliderView.isUserInteractionEnabled = true } else { sliderView.trackColor = UIColor(rgb: 0xffffff) sliderView.alpha = 0.3 sliderView.isUserInteractionEnabled = false } transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: 22.0, y: 7.0), size: CGSize(width: availableSize.width - 22.0 * 2.0, height: 44.0))) sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) let titleSize = self.title.update( transition: .immediate, component: AnyComponent( Text(text: component.title, font: Font.regular(14.0), color: UIColor(rgb: 0x808080)) ), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) ) if let titleView = self.title.view { if titleView.superview == nil { self.addSubview(titleView) } transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: 21.0, y: 0.0), size: titleSize)) } let valueText: String if component.displayValue { if component.value > 0.005 { valueText = String(format: "+%.2f", component.value) } else if component.value < -0.005 { valueText = String(format: "%.2f", component.value) } else { valueText = "" } } else { valueText = "" } let valueSize = self.value.update( transition: .immediate, component: AnyComponent( Text(text: valueText, font: Font.with(size: 14.0, traits: .monospacedNumbers), color: UIColor(rgb: 0xf8d74a)) ), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) ) if let valueView = self.value.view { if valueView.superview == nil { self.addSubview(valueView) } transition.setFrame(view: valueView, frame: CGRect(origin: CGPoint(x: availableSize.width - 21.0 - valueSize.width, y: 0.0), size: valueSize)) } return CGSize(width: availableSize.width, height: 52.0) } @objc private func sliderValueChanged() { guard let component = self.component, let sliderView = self.sliderView else { return } component.valueUpdated(Float(sliderView.value)) } } public func makeView() -> View { return View(frame: CGRect()) } public func update(view: View, availableSize: CGSize, state: State, environment: Environment, transition: Transition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } struct AdjustmentTool: Equatable { let key: EditorToolKey let title: String let value: Float let minValue: Float let maxValue: Float let startValue: Float } final class AdjustmentsComponent: Component { typealias EnvironmentType = Empty let tools: [AdjustmentTool] let valueUpdated: (EditorToolKey, Float) -> Void let isTrackingUpdated: (Bool) -> Void init( tools: [AdjustmentTool], valueUpdated: @escaping (EditorToolKey, Float) -> Void, isTrackingUpdated: @escaping (Bool) -> Void ) { self.tools = tools self.valueUpdated = valueUpdated self.isTrackingUpdated = isTrackingUpdated } static func ==(lhs: AdjustmentsComponent, rhs: AdjustmentsComponent) -> Bool { if lhs.tools != rhs.tools { return false } return true } final class View: UIView { private let scrollView = UIScrollView() private var toolViews: [ComponentView] = [] private var component: AdjustmentsComponent? private weak var state: EmptyComponentState? override init(frame: CGRect) { self.scrollView.showsVerticalScrollIndicator = false super.init(frame: frame) self.addSubview(self.scrollView) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func update(component: AdjustmentsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.component = component self.state = state let valueUpdated = component.valueUpdated let isTrackingUpdated: (EditorToolKey, Bool) -> Void = { [weak self] trackingTool, isTracking in component.isTrackingUpdated(isTracking) if let self { for i in 0 ..< component.tools.count { let tool = component.tools[i] if tool.key != trackingTool && i < self.toolViews.count { if let view = self.toolViews[i].view { let transition: Transition if isTracking { transition = .immediate } else { transition = .easeInOut(duration: 0.25) } transition.setAlpha(view: view, alpha: isTracking ? 0.0 : 1.0) } } } } } var sizes: [CGSize] = [] for i in 0 ..< component.tools.count { let tool = component.tools[i] let componentView: ComponentView if i >= self.toolViews.count { componentView = ComponentView() self.toolViews.append(componentView) } else { componentView = self.toolViews[i] } var valueIsNegative = false var value = tool.value if case .enhance = tool.key { if value < 0.0 { valueIsNegative = true } value = abs(value) } let size = componentView.update( transition: transition, component: AnyComponent( AdjustmentSliderComponent( title: tool.title, value: value, minValue: tool.minValue, maxValue: tool.maxValue, startValue: tool.startValue, isEnabled: true, trackColor: nil, displayValue: true, valueUpdated: { value in var updatedValue = value if valueIsNegative { updatedValue *= -1.0 } valueUpdated(tool.key, updatedValue) }, isTrackingUpdated: { isTracking in isTrackingUpdated(tool.key, isTracking) } ) ), environment: {}, containerSize: availableSize ) sizes.append(size) } var origin: CGPoint = CGPoint(x: 0.0, y: 11.0) for i in 0 ..< component.tools.count { let size = sizes[i] let componentView = self.toolViews[i] if let view = componentView.view { if view.superview == nil { self.scrollView.addSubview(view) } transition.setFrame(view: view, frame: CGRect(origin: origin, size: size)) } origin = origin.offsetBy(dx: 0.0, dy: size.height) } let size = CGSize(width: availableSize.width, height: 180.0) let contentSize = CGSize(width: availableSize.width, height: origin.y) if contentSize != self.scrollView.contentSize { self.scrollView.contentSize = contentSize } transition.setFrame(view: self.scrollView, frame: CGRect(origin: .zero, size: size)) return size } } public func makeView() -> View { return View(frame: CGRect()) } public func update(view: View, availableSize: CGSize, state: State, environment: Environment, transition: Transition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } final class AdjustmentsScreenComponent: Component { typealias EnvironmentType = Empty let toggleUneditedPreview: (Bool) -> Void init( toggleUneditedPreview: @escaping (Bool) -> Void ) { self.toggleUneditedPreview = toggleUneditedPreview } static func ==(lhs: AdjustmentsScreenComponent, rhs: AdjustmentsScreenComponent) -> Bool { return true } final class View: UIView { enum Field { case blacks case shadows case midtones case highlights case whites } private var component: AdjustmentsScreenComponent? private weak var state: EmptyComponentState? override init(frame: CGRect) { super.init(frame: frame) let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongPress(_:))) longPressGestureRecognizer.minimumPressDuration = 0.05 self.addGestureRecognizer(longPressGestureRecognizer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc func handleLongPress(_ gestureRecognizer: UIPanGestureRecognizer) { guard let component = self.component else { return } switch gestureRecognizer.state { case .began: component.toggleUneditedPreview(true) case .ended, .cancelled: component.toggleUneditedPreview(false) default: break } } func update(component: AdjustmentsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.component = component self.state = state return availableSize } } public func makeView() -> View { return View(frame: CGRect()) } public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } }