Files
Privado-macOS/macOS/Privado/Sources/View/BackgroundTextField.swift
2022-05-31 11:44:27 +00:00

335 lines
11 KiB
Swift

//
// BackgroundTextField.swift.swift
// PrivadoVPN
//
// Created by Murad Shabanov on 31.03.2022.
// Copyright © 2022 Privado LLC. All rights reserved.
//
import Foundation
enum TextFieldType {
case login
case password
}
final class BackgroundTextField: NSView, NSTextFieldDelegate {
typealias EnterHandlerClosure = () -> Bool
typealias TextDidChageClosure = ((String) -> ())
typealias RightImageTappedClosure = () -> ()
enum Constants {
static let lineHeight: CGFloat = 2.0
enum Font {
static let name = PrivadoConstants.Font.regular
static let size: CGFloat = 14.0
}
enum Color {
static let text = NSColor.white
static let placeholder = NSColor(calibratedRed: 173.0 / 255, green: 179.0 / 255, blue: 210.0 / 255, alpha: 1.0)
static let neutral = NSColor(calibratedRed: 45.0 / 255, green: 51.0 / 255, blue: 82.0 / 255, alpha: 1.0)
static let beginEditing = NSColor(red: 40, green: 215, blue: 153)
}
}
// MARK: - Public
public var textField: NSTextField
public var enterHandler: EnterHandlerClosure?
public var textDidChangeClosure: TextDidChageClosure?
public var rightImageTappedClosure: RightImageTappedClosure?
public var backgroundColor: NSColor? {
didSet {
self.backgroundView.viewBackgroundColor = self.backgroundColor
}
}
public var highlightBackgroundColor: NSColor?
public var iconImage: NSImage? {
didSet {
self.leftIconView.image = self.iconImage
}
}
public var rightImage: NSImage? {
didSet {
self.rightIconView.image = self.rightImage
}
}
public var backgroundRadius: CGFloat? {
didSet {
self.backgroundView.layer?.cornerRadius = self.backgroundRadius ?? 0
}
}
public var strokeWidth: CGFloat? {
didSet {
self.backgroundView.layer?.borderWidth = self.strokeWidth ?? 0
}
}
public var strokeColor: NSColor? {
didSet {
self.backgroundView.layer?.borderColor = self.strokeColor?.cgColor
}
}
public var iconSize: CGSize {
didSet {
self.layoutUI()
}
}
public var rightIconSize: CGSize? {
didSet {
self.clearConstraints()
self.layoutUI()
}
}
public var textFieldYTopOffset: CGFloat = 11 {
didSet {
self.clearConstraints()
self.layoutUI()
}
}
public var textFieldTrailingOffset: CGFloat = 10 {
didSet {
self.clearConstraints()
self.layoutUI()
}
}
public var textFieldYBottomOffset: CGFloat = 10 {
didSet {
self.clearConstraints()
self.layoutUI()
}
}
public var coloredPlaceHolder: String? {
get {
return self.textField.placeholderAttributedString?.string
}
set {
if let value = newValue {
let attrs: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor: Constants.Color.placeholder,
NSAttributedString.Key.font: self.placeHolderFont ?? .systemFont(ofSize: 14)]
self.textField.placeholderAttributedString = NSAttributedString(string: value, attributes: attrs)
} else {
self.textField.placeholderAttributedString = nil
}
}
}
public var placeHolderFont: NSFont?
public func highlight(with color: NSColor?) {
self.backgroundView.layer?.borderWidth = 1
self.backgroundView.layer?.borderColor = color?.cgColor ?? .clear
}
// MARK: - Init
convenience init(frame frameRect: NSRect, textFieldType: TextFieldType, iconSize: CGSize) {
self.init(frame: .zero)
self.iconSize = iconSize
switch textFieldType {
case .login:
self.textField = NSTextField()
case .password:
self.textField = DSFSecureTextField()
}
self.setupUI()
self.layoutUI()
}
override init(frame frameRect: NSRect) {
self.textField = NSTextField()
self.backgroundView = NSView()
self.leftIconView = ImageView()
self.rightIconView = ImageView()
self.iconSize = CGSize(width: 15, height: 15)
super.init(frame: frameRect)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Private
private var backgroundView: NSView
private var leftIconView: ImageView
private var rightIconView: ImageView
private func setupUI() {
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundView.viewBackgroundColor = self.backgroundColor ?? NSColor.gray
self.backgroundView.translatesAutoresizingMaskIntoConstraints = false
self.backgroundView.wantsLayer = true
self.addSubview(self.backgroundView)
self.textField.cell?.isScrollable = true
self.textField.isBezeled = false
self.textField.drawsBackground = false
self.textField.translatesAutoresizingMaskIntoConstraints = false
self.textField.focusRingType = .none
self.textField.usesSingleLineMode = true
self.textField.delegate = self
self.backgroundView.addSubview(self.textField)
self.leftIconView.translatesAutoresizingMaskIntoConstraints = false
self.backgroundView.addSubview(self.leftIconView)
self.rightIconView.translatesAutoresizingMaskIntoConstraints = false
self.rightIconView.useHandCursor = true
self.rightIconView.target = self
self.rightIconView.action = #selector(self.rightImageTapped)
self.addSubview(self.rightIconView)
}
@objc func rightImageTapped() {
self.rightImageTappedClosure?()
}
private func layoutUI() {
NSLayoutConstraint.activate([
self.backgroundView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.backgroundView.topAnchor.constraint(equalTo: self.topAnchor),
self.backgroundView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
self.backgroundView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
self.leftIconView.leadingAnchor.constraint(equalTo: self.backgroundView.leadingAnchor, constant: 13),
self.leftIconView.topAnchor.constraint(equalTo: self.backgroundView.topAnchor, constant: 13),
self.leftIconView.widthAnchor.constraint(equalToConstant: self.iconSize.width),
self.leftIconView.heightAnchor.constraint(equalToConstant: self.iconSize.height),
self.textField.leadingAnchor.constraint(equalTo: self.leftIconView.trailingAnchor, constant: 10),
self.textField.topAnchor.constraint(equalTo: self.backgroundView.topAnchor, constant: self.textFieldYTopOffset),
self.textField.bottomAnchor.constraint(equalTo: self.backgroundView.bottomAnchor, constant: -self.textFieldYBottomOffset),
self.textField.trailingAnchor.constraint(equalTo: self.backgroundView.trailingAnchor, constant: -self.textFieldTrailingOffset),
])
guard let rightIconSize = rightIconSize else { return }
NSLayoutConstraint.activate([
self.rightIconView.widthAnchor.constraint(equalToConstant: rightIconSize.width),
self.rightIconView.heightAnchor.constraint(equalToConstant: rightIconSize.height),
self.rightIconView.topAnchor.constraint(equalTo: self.topAnchor, constant: 13),
self.rightIconView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10)
])
}
private func changeBackground(with color: NSColor?) {
guard let color = color else {
return
}
NSAnimationContext.runAnimationGroup { inputContext in
inputContext.duration = 0.2
inputContext.allowsImplicitAnimation = true
self.backgroundView.animator().viewBackgroundColor = color
} completionHandler: {
print("highlightBackground")
}
}
// MARK: - NSTEextFieldDelegate
func controlTextDidBeginEditing(_ obj: Notification) {
self.changeBackground(with: self.highlightBackgroundColor)
self.highlight(with: nil)
}
func controlTextDidEndEditing(_ obj: Notification) {
self.changeBackground(with: self.backgroundColor)
}
func controlTextDidChange(_ obj: Notification) {
self.textDidChangeClosure?(self.textField.stringValue)
}
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
if commandSelector == #selector(NSResponder.insertNewline(_:)) {
guard let handler = self.enterHandler else { return true }
return handler()
}
return false
}
}
extension NSTextField {
private func performEditingKeyEquivalent(with event: NSEvent) -> Bool {
guard event.type == NSEvent.EventType.keyDown else { return false }
let commandKey = NSEvent.ModifierFlags.command.rawValue
let commandShiftKey = NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue
let key = event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue
switch key {
case commandKey: return self.handleCommandKey(keyCode: event.keyCode)
case commandShiftKey: return self.handleShiftKey(keyCode: event.keyCode)
default:
break
}
return false
}
private func handleCommandKey(keyCode: UInt16) -> Bool {
switch keyCode {
case KeyCode.x:
if NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: self) { return true }
case KeyCode.c:
if NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: self) { return true }
case KeyCode.v:
if NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: self) { return true }
case KeyCode.z:
if NSApp.sendAction(Selector(("undo:")), to: nil, from: self) { return true }
case KeyCode.a:
if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self) { return true }
default:
break
}
return false
}
private func handleShiftKey(keyCode: UInt16) -> Bool {
if keyCode == KeyCode.z {
if NSApp.sendAction(Selector(("redo:")), to: nil, from: self) { return true }
}
return false
}
public override func performKeyEquivalent(with event: NSEvent) -> Bool {
if self.performEditingKeyEquivalent(with: event) {
return true
}
return super.performKeyEquivalent(with: event)
}
}
extension NSView {
func clearConstraints() {
for subview in self.subviews {
subview.clearConstraints()
}
self.removeConstraints(self.constraints)
}
}