// // AppKitExtend.swift // KSPlayer // // Created by kintan on 2018/3/9. // /// 'NSWindow' is unavailable in Mac Catalyst #if !canImport(UIKit) import AppKit import CoreMedia import IOKit.pwr_mgt public typealias UIApplicationDelegate = NSApplicationDelegate public typealias UIApplication = NSApplication public typealias UIWindow = NSWindow public typealias UIViewController = NSViewController public typealias UIColor = NSColor public typealias UIStackView = NSStackView public typealias UIPanGestureRecognizer = NSPanGestureRecognizer public typealias UIGestureRecognizer = NSGestureRecognizer public typealias UIGestureRecognizerDelegate = NSGestureRecognizerDelegate public typealias UIViewContentMode = ContentMode public typealias UIFont = NSFont public typealias UIFontDescriptor = NSFontDescriptor public typealias UIControl = NSControl public typealias UITextField = NSTextField public typealias UIImageView = NSImageView public typealias UITapGestureRecognizer = NSClickGestureRecognizer public typealias UXSlider = NSSlider public typealias UITableView = NSTableView public typealias UITableViewDelegate = NSTableViewDelegate public typealias UITableViewDataSource = NSTableViewDataSource public typealias UITouch = NSTouch public typealias UIEvent = NSEvent public typealias UIButton = KSButton public extension UIFontDescriptor.SymbolicTraits { static var traitItalic = italic static var traitBold = bold } extension NSScreen { var scale: CGFloat { backingScaleFactor } } public extension NSClickGestureRecognizer { var numberOfTapsRequired: Int { get { numberOfClicksRequired } set { numberOfClicksRequired = newValue } } func require(toFail otherGestureRecognizer: NSClickGestureRecognizer) { buttonMask = otherGestureRecognizer.buttonMask << 1 } } public extension NSView { @objc internal var contentMode: UIViewContentMode { get { if let contentsGravity = backingLayer?.contentsGravity { switch contentsGravity { case .resize: return .scaleToFill case .resizeAspect: return .scaleAspectFit case .resizeAspectFill: return .scaleAspectFill default: return .scaleAspectFit } } else { return .scaleAspectFit } } set { switch newValue { case .scaleToFill: backingLayer?.contentsGravity = .resize case .scaleAspectFit: backingLayer?.contentsGravity = .resizeAspect case .scaleAspectFill: backingLayer?.contentsGravity = .resizeAspectFill case .center: backingLayer?.contentsGravity = .center default: break } } } var center: CGPoint { CGPoint(x: frame.midX, y: frame.midY) } var alpha: CGFloat { get { alphaValue } set { alphaValue = newValue } } var clipsToBounds: Bool { get { if let layer { return layer.masksToBounds } else { return false } } set { backingLayer?.masksToBounds = newValue } } class func animate(withDuration duration: TimeInterval, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { CATransaction.begin() CATransaction.setAnimationDuration(duration) CATransaction.setCompletionBlock { completion?(true) } animations() CATransaction.commit() } class func animate(withDuration duration: TimeInterval, animations: @escaping () -> Void) { animate(withDuration: duration, animations: animations, completion: nil) } func layoutIfNeeded() { layer?.layoutIfNeeded() } func centerRotate(byDegrees: Double) { layer?.position = center layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5) layer?.setAffineTransform(CGAffineTransform(rotationAngle: CGFloat(Double.pi * byDegrees / 180.0))) } } public extension NSImage { convenience init(cgImage: CGImage) { self.init(cgImage: cgImage, size: NSSize.zero) } @available(macOS 11.0, *) convenience init?(systemName: String) { self.init(systemSymbolName: systemName, accessibilityDescription: nil) } } extension NSButton { var titleFont: UIFont? { get { font } set { font = newValue } } var tintColor: UIColor? { get { contentTintColor } set { contentTintColor = newValue } } var backgroundColor: UIColor? { get { if let layer, let cgColor = layer.backgroundColor { return UIColor(cgColor: cgColor) } else { return nil } } set { backingLayer?.backgroundColor = newValue?.cgColor } } } extension NSImageView { var backgroundColor: UIColor? { get { if let layer, let cgColor = layer.backgroundColor { return UIColor(cgColor: cgColor) } else { return nil } } set { backingLayer?.backgroundColor = newValue?.cgColor } } } public extension NSControl { var textAlignment: NSTextAlignment { get { alignment } set { alignment = newValue } } var text: String { get { stringValue } set { stringValue = newValue } } var attributedText: NSAttributedString? { get { attributedStringValue } set { attributedStringValue = newValue ?? NSAttributedString() } } var numberOfLines: Int { get { usesSingleLineMode ? 1 : 0 } set { usesSingleLineMode = newValue == 1 } } } public extension NSTextContainer { var numberOfLines: Int { get { maximumNumberOfLines } set { maximumNumberOfLines = newValue } } } public extension NSResponder { var next: NSResponder? { nextResponder } } public extension NSSlider { var minimumTrackTintColor: UIColor? { get { trackFillColor } set { trackFillColor = newValue } } var maximumTrackTintColor: UIColor? { get { nil } set {} } } public extension NSStackView { var axis: NSUserInterfaceLayoutOrientation { get { orientation } set { orientation = newValue } } } public extension NSGestureRecognizer { func addTarget(_ target: AnyObject, action: Selector) { self.target = target self.action = action } } public extension UIApplication { private static var assertionID = IOPMAssertionID() static var isIdleTimerDisabled = false { didSet { if isIdleTimerDisabled != oldValue { if isIdleTimerDisabled { _ = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep as CFString, IOPMAssertionLevel(kIOPMAssertionLevelOn), "KSPlayer is playing video" as CFString, &assertionID) } else { _ = IOPMAssertionRelease(assertionID) } } } } var isIdleTimerDisabled: Bool { get { UIApplication.isIdleTimerDisabled } set { UIApplication.isIdleTimerDisabled = newValue } } } // @available(*, unavailable, renamed: "UIView.ContentMode") @objc public enum ContentMode: Int { case scaleToFill case scaleAspectFit // contents scaled to fit with fixed aspect. remainder is transparent case scaleAspectFill // contents scaled to fill with fixed aspect. some portion of content may be clipped. case redraw // redraw on bounds change (calls -setNeedsDisplay) case center // contents remain same size. positioned adjusted. case top case bottom case left case right case topLeft case topRight case bottomLeft case bottomRight } public extension UIControl { struct State: OptionSet { public var rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue } public static var normal = State(rawValue: 1 << 0) public static var highlighted = State(rawValue: 1 << 1) public static var disabled = State(rawValue: 1 << 2) public static var selected = State(rawValue: 1 << 3) public static var focused = State(rawValue: 1 << 4) public static var application = State(rawValue: 1 << 5) public static var reserved = State(rawValue: 1 << 6) } } extension UIControl.State: Hashable {} public class UILabel: NSTextField { override init(frame frameRect: CGRect) { super.init(frame: frameRect) alignment = .left isBordered = false isEditable = false isSelectable = false isBezeled = false drawsBackground = false focusRingType = .none textColor = NSColor.white } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } public class KSButton: NSButton { private var images = [UIControl.State: UIImage]() private var titles = [UIControl.State: String]() private var titleColors = [State: UIColor]() private var targetActions = [ControlEvents: (AnyObject?, Selector)]() override public init(frame frameRect: CGRect) { super.init(frame: frameRect) isBordered = false } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } public var isSelected: Bool = false { didSet { update(state: isSelected ? .selected : .normal) } } override public var isEnabled: Bool { didSet { update(state: isEnabled ? .normal : .disabled) } } open func setImage(_ image: UIImage?, for state: UIControl.State) { images[state] = image if state == .normal, isEnabled, !isSelected { self.image = image } } open func setTitle(_ title: String, for state: UIControl.State) { titles[state] = title if state == .normal, isEnabled, !isSelected { self.title = title } } open func setTitleColor(_ titleColor: UIColor?, for state: UIControl.State) { titleColors[state] = titleColor if state == .normal, isEnabled, !isSelected { // self.titleColor = titleColor } } private func update(state: UIControl.State) { if let stateImage = images[state] { image = stateImage } if let stateTitle = titles[state] { title = stateTitle } } open func addTarget(_ target: AnyObject?, action: Selector, for controlEvents: ControlEvents) { targetActions[controlEvents] = (target, action) } open func removeTarget(_: AnyObject?, action _: Selector?, for controlEvents: ControlEvents) { targetActions.removeValue(forKey: controlEvents) } override open func updateTrackingAreas() { for trackingArea in trackingAreas { removeTrackingArea(trackingArea) } let trackingArea = NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .mouseMoved, .activeInKeyWindow], owner: self, userInfo: nil) addTrackingArea(trackingArea) } override public func mouseDown(with event: NSEvent) { super.mouseDown(with: event) if let (target, action) = targetActions[.touchUpInside] ?? targetActions[.primaryActionTriggered] { _ = target?.perform(action, with: self) } } override public func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) if let (target, action) = targetActions[.mouseExited] { _ = target?.perform(action, with: self) } } override public func mouseExited(with event: NSEvent) { super.mouseExited(with: event) if let (target, action) = targetActions[.mouseExited] { _ = target?.perform(action, with: self) } } open func sendActions(for controlEvents: ControlEvents) { if let (target, action) = targetActions[controlEvents] { _ = target?.perform(action, with: self) } } } public class KSSlider: NSSlider { weak var delegate: KSSliderDelegate? public var trackHeigt = CGFloat(2) public var isPlayable = false public var isUserInteractionEnabled: Bool = true var tintColor: UIColor? public convenience init() { self.init(frame: .zero) } override public init(frame frameRect: CGRect) { super.init(frame: frameRect) target = self action = #selector(progressSliderTouchEnded(_:)) } @available(*, unavailable) required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func progressSliderTouchEnded(_ sender: KSSlider) { if isUserInteractionEnabled { delegate?.slider(value: Double(sender.floatValue), event: .touchUpInside) } } open func setThumbImage(_: UIImage?, for _: State) {} @IBInspectable var maximumValue: Float { get { Float(maxValue) } set { maxValue = Double(newValue) } } @IBInspectable var minimumValue: Float { get { Float(minValue) } set { minValue = Double(newValue) } } @IBInspectable var value: Float { get { floatValue } set { floatValue = newValue } } } extension UIView { func image() -> UIImage? { guard let rep = bitmapImageRepForCachingDisplay(in: bounds) else { return nil } cacheDisplay(in: bounds, to: rep) let image = NSImage(size: bounds.size) image.addRepresentation(rep) return image } } // todo open class UIAlertController: UIViewController { public enum Style: Int { case actionSheet case alert } public convenience init(title _: String?, message _: String?, preferredStyle _: UIAlertController.Style) { self.init() } var preferredAction: UIAlertAction? open func addAction(_: UIAlertAction) {} } open class UIAlertAction: NSObject { public enum Style: Int { case `default` case cancel case destructive } public let title: String? public let style: UIAlertAction.Style public private(set) var isEnabled: Bool = false public init(title: String?, style: UIAlertAction.Style, handler _: ((UIAlertAction) -> Void)? = nil) { self.title = title self.style = style } } public extension UIViewController { func present(_: UIViewController, animated _: Bool, completion _: (() -> Void)? = nil) {} } #endif