[macOS][Swift] カスタムカラーをDarkModeに対応する（暫定版）


*MojaveおよびXcode10を持ってないので動作確認できてません*

[ソースコード in Gist](https://gist.github.com/masakih/ad579fe47d9712e4b85a9be3bb771c09)

ダークモードに対応するために作った。
ついでに「アクセシビリティ」環境設定の「コントラストを上げる」にも対応した。

システムが提供する名前付きのNSColorはシステムが勝手にやってくれるので何もやらなくてもよい。
まずは適切な名前付きNSColorを用いるようにすべきである。

これは10.13及びそれより前のmacOSもターゲットに入れた方法である。
10.14以上のみをターゲットとする場合は Asset Catalog の Colors を用いるべきである。

富豪プログラマなので独自に定義したカスタムカラーにいかに簡単にアクセスするかを最上位の目的とした。
この方法を用いると描画パフォーマンスが低下する恐れがある。


##使い方
draw(_:)内などで使用する。
モードの変更の通知がよくわからない。
モードが変わると NSWindow#display() などが呼び出されるようだ。

```swift

func draw(_ dirtyRect: NSRect) {

  // モードに準じた色が得られる
  let borderColor = ColorSetManager.current[.borderColor]
  borderColor.set()

  ...
}
```

##登場する人

 - protocol
   - ColorSet 色が集まってる
   - SpecialColorSet スペシャルな色が集まってる
 - struct
   - ColorSetManager 神様。全部知ってる。 名前を ColorSetGod にするか悩んだ。
   - ColorName 色の名前が定義されてる。
   - BaseColorSet  通常時の色のセット。
   - そのたColorSet SpecialColorSet の具象化されたもの
 - enum
   - AppearanceMode あぴあらんす！

##主要な人

###ColorSet protocol
ColorNameからNSColorがもらえるsubscriptが宣言されてる。

```swift
protocol ColorSet {
    
    subscript(named: ColorName) -> NSColor { get }
}
```
###ColorSetManager struct
現在の使用するべき ColorSet を知っている。

```swift
struct ColorSetManager {

  static var current: ColorSet { ... }
}
```

##脇役の人

###AppearanceMode enum
現在のモードを表す内部的なenum。
現在のモードも教えてくれる。

```swift
enum AppearanceMode {
    
    case normal
    
    case highContrast
    
    case dark
    
    case highContrastDark
}

extension AppearanceMode {

    static var current: AppearanceMode { ... }
}
```

###ColorName struct
色の名前。 *initは privateの方がイイかも*
static let で色を追加していく。

```swift
struct ColorName {
    
    let name: String
    
    init(_ name: String) {
        
        self.name = name
    }
}

extension ColorName {

  static let asokoNoAnoBubun = ColorName("asokoNoAnoBubun")
}
```

##裏方の人 （private）

###BaseColorSet struct
inherit from ColorSet
基本となるカラーセット。
外部からは隠されている。

```swift
private struct BaseColorSet: ColorSet { ... }
```

###SpecialColorSet protocol
inherit from ColorSet
特殊なモードでのカラーセット。
色の定義がない場合は BaseColorSet の色を返すように protocol extension が定義されている。
外部からは隠されている。

```swift
private protocol SpecialColorSet: ColorSet {

  func color(named: ColorName) -> NSColor?
}

private extension SpecialColorSet {

  subscript(named: ColorName) -> NSColor {
        
        return color(named: named) ?? BaseColorSet.shared[named]
    }
}
```

###HighContrastColorSet struct
inherit from SpecialColorSet
###DarkModeColorSet struct
inherit from SpecialColorSet
###HighContrastDarkModeColorSet struct
inherit from SpecialColorSet

それぞれのモードでの具象型
外部からは隠されている。

##現在のモードの識別
現在のモードの取得方法は10.14とそれ以前で変わっている。
前述通りMojaveおよびXcode10を持ってないので動くかどうかは不明。

```swift
extension AppearanceMode {
    
    static var current -> AppearanceMode {
        
        if #available(macOS 10.14, *) {
            
            return currentMode1014()
        }
        
        /// macOS 10.13までは NSWorkspaceから。DarkModeはない。
        
        if NSWorkspace.shared.accessibilityDisplayShouldIncreaseContrast {
            
            return .highContrast
        }
        
        return .normal
    }
    
    @available(macOS 10.14, *)
    private static func currentMode1014() -> AppearanceMode {
        
        // 10.14ではNSAppearance.current.nameだけを調べればいい
        switch NSAppearance.current.name {
            
        case .aqua: return .normal
            
            /// not available in macOS 10.13 SDK
//  macOS 10.14 SDKがないのでコンパイル不能。コメントアウト
//        case .accessibilityHighContrastAqua: return .highContrast
//
//        case .darkAqua: return .dark
//
//        case .accessibilityHighContrastDarkAqua: return .highContrastDark
            
        default: return .normal
        }
    }
}
```