OSDN Git Service

AppDelegateからウインドウに関する部分を分離した
[kcd/KCD.git] / KCD / FleetViewController.swift
1 //
2 //  FleetViewController.swift
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2016/12/27.
6 //  Copyright © 2016年 Hori,Masaki. All rights reserved.
7 //
8
9 import Cocoa
10
11 enum FleetViewType: Int {
12     case detailViewType = 0
13     case minimumViewType = 1
14     case miniVierticalType = 2
15 }
16
17 fileprivate var shipKeysContext: Int = 0
18 fileprivate var shipsContext: Int = 0
19
20 class FleetViewController: NSViewController {
21     enum ShipOrder: Int {
22         case doubleLine = 0
23         case leftToRight = 1
24     }
25     
26     static let oldStyleFleetViewHeight: CGFloat = 128.0
27     static let detailViewHeight: CGFloat = 288.0
28     static let heightDifference: CGFloat = detailViewHeight - oldStyleFleetViewHeight
29     private static let maxFleetNumber: Int = 4
30     
31     fileprivate let details: [ShipDetailViewController]
32     private let shipKeys = ["ship_0", "ship_1", "ship_2", "ship_3", "ship_4", "ship_5"]
33     private let type: FleetViewType
34     private let fleetController = NSObjectController()
35     private let shipObserveKeys = ["sakuteki_0", "seiku", "totalSeiku", "lv", "totalDrums"]
36     
37     init?(viewType: FleetViewType) {
38         type = viewType
39         
40         let shipiewType: ShipDetailViewType = {
41             switch viewType {
42             case .detailViewType: return .full
43             case .minimumViewType: return .medium
44             case .miniVierticalType: return .minimum
45             }
46         }()
47         details = (1...6).map {
48             guard let res = ShipDetailViewController(type: shipiewType)
49                 else { fatalError("Can not create ShipDetailViewController") }
50             res.title = "\($0)"
51             return res
52         }
53         
54         let nibName: String = {
55             switch viewType {
56             case .detailViewType: return "FleetViewController"
57             case .minimumViewType: return "FleetMinimumViewController"
58             case .miniVierticalType: return "VerticalFleetViewController"
59             }
60         }()
61         super.init(nibName: nibName, bundle: nil)
62     }
63     required init?(coder: NSCoder) {
64         fatalError("init(coder:) has not been implemented")
65     }
66     deinit {
67         NotificationCenter.default.removeObserver(self)
68     }
69     
70     @IBOutlet weak var placeholder01: NSView!
71     @IBOutlet weak var placeholder02: NSView!
72     @IBOutlet weak var placeholder03: NSView!
73     @IBOutlet weak var placeholder04: NSView!
74     @IBOutlet weak var placeholder05: NSView!
75     @IBOutlet weak var placeholder06: NSView!
76     
77     dynamic var fleetNumber: Int = 1 {
78         didSet {
79             ServerDataStore.default
80                 .deck(by: fleetNumber)
81                 .map { fleet = $0 }
82         }
83     }
84     dynamic var fleet: Deck? {
85         get { return representedObject as? Deck }
86         set {
87             representedObject = newValue
88             title = newValue?.name
89             setupShips()
90         }
91     }
92     
93     var enableAnimation: Bool = false
94     var shipOrder: FleetViewController.ShipOrder = .doubleLine {
95         willSet {
96             if shipOrder == newValue { return }
97             switch newValue {
98             case .doubleLine: reorderShipToDoubleLine()
99             case .leftToRight: reorderShipToLeftToRight()
100             }
101         }
102     }
103     var canDivide: Bool { return type == .detailViewType }
104     var normalHeight: CGFloat {
105         switch type {
106         case .detailViewType: return FleetViewController.detailViewHeight
107         case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
108         case .miniVierticalType: return 0.0
109         }
110     }
111     var upsideHeight: CGFloat {
112         switch type {
113         case .detailViewType: return 159.0
114         case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
115         case .miniVierticalType: return 0.0
116         }
117     }
118     var totalSakuteki: Int { return ships.reduce(0) { $0 + $1.sakuteki_0 } }
119     var totalSeiku: Int { return ships.reduce(0) { $0 + $1.seiku } }
120     var totalCalclatedSeiku: Int { return ships.reduce(0) { $0 + $1.totalSeiku } }
121     var totalLevel: Int { return ships.reduce(0) { $0 + $1.lv } }
122     var totalDrums: Int { return ships.reduce(0) { $0 + $1.totalDrums } }
123     
124     fileprivate var ships: [Ship] = [] {
125         willSet {
126             ships.forEach { ship in
127                 shipObserveKeys.forEach {
128                     ship.removeObserver(self, forKeyPath: $0)
129                 }
130             }
131         }
132         didSet {
133             ships.forEach { ship in
134                 shipObserveKeys.forEach {
135                     ship.addObserver(self, forKeyPath: $0, context: &shipsContext)
136                 }
137             }
138         }
139     }
140     private(set) var anchorageRepair = AnchorageRepairManager.default
141     dynamic fileprivate(set) var repairTime: NSNumber?
142     
143     override func viewDidLoad() {
144         super.viewDidLoad()
145         
146         fleetController.bind("content", to:self, withKeyPath:#keyPath(fleet), options:nil)
147         fleetController.addObserver(self, forKeyPath:"selection.name", context:nil)
148         shipKeys.forEach {
149             let keyPath = "selection.\($0)"
150             fleetController.addObserver(self, forKeyPath:keyPath, context:&shipKeysContext)
151         }
152         
153         buildAnchorageRepairHolder()
154         
155         [NSView?]()
156             .appended { placeholder01 }
157             .appended { placeholder02 }
158             .appended { placeholder03 }
159             .appended { placeholder04 }
160             .appended { placeholder05 }
161             .appended { placeholder06 }
162             .enumerated()
163             .forEach {
164                 guard let view = $0.element else { return }
165                 let detail = details[$0.offset]
166                 detail.view.frame = view.frame
167                 detail.view.autoresizingMask = view.autoresizingMask
168                 self.view.replaceSubview(view, with: detail.view)
169         }
170         fleetNumber = 1
171         
172         NotificationCenter.default
173             .addObserver(forName: .DidPrepareFleet, object: nil, queue: nil) { [weak self] _ in
174                 guard let `self` = self else { return }
175                 self.notifyChangeValue(forKey: #keyPath(fleetNumber))
176         }
177     }
178     override func observeValue(forKeyPath keyPath: String?,
179                                of object: Any?,
180                                change: [NSKeyValueChangeKey: Any]?,
181                                context: UnsafeMutableRawPointer?) {
182         
183         if keyPath == "selection.name" {
184             title = fleet?.name
185             return
186         }
187         
188         if context == &shipKeysContext {
189             setupShips()
190             return
191         }
192         
193         if let keyPath = keyPath {
194             if context == &shipsContext {
195                 switch keyPath {
196                 case "sakuteki_0":
197                     notifyChangeValue(forKey: #keyPath(totalSakuteki))
198                 case "seiku":
199                     notifyChangeValue(forKey: #keyPath(totalSeiku))
200                 case "totalSeiku":
201                     notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
202                 case "lv":
203                     notifyChangeValue(forKey: #keyPath(totalLevel))
204                 case "totalDrums":
205                     notifyChangeValue(forKey: #keyPath(totalDrums))
206                 default: break
207                 }
208                 return
209             }
210         }
211         
212         super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
213     }
214     
215     @IBAction func selectNextFleet(_ sender: AnyObject?) {
216         let next = fleetNumber + 1
217         fleetNumber = next <= FleetViewController.maxFleetNumber ? next : 1
218     }
219     @IBAction func selectPreviousFleet(_ sender: AnyObject?) {
220         let prev = fleetNumber - 1
221         fleetNumber = prev > 0 ? prev : 4
222     }
223     
224     private func setupShips() {
225         let array: [Ship?] = (0..<6).map { fleet?[$0] }
226         zip(details, array).forEach { $0.0.ship = $0.1 }
227         ships = array.flatMap { $0 }
228         
229         [String]()
230             .appended { #keyPath(totalSakuteki) }
231             .appended { #keyPath(totalSeiku) }
232             .appended { #keyPath(totalCalclatedSeiku) }
233             .appended { #keyPath(totalLevel) }
234             .appended { #keyPath(totalDrums) }
235             .appended { #keyPath(repairable) }
236             .forEach { notifyChangeValue(forKey: $0) }
237     }
238 }
239
240 extension FleetViewController {
241     private func reorder(order: [Int]) {
242         guard order.count == 6 else {
243             print("FleetViewController: order count is not 6.")
244             return
245         }
246         let views: [NSView] = details.map { $0.view }
247         let options: [NSAutoresizingMaskOptions] = views.map { $0.autoresizingMask }
248         let reorderedOptions = order.map { options[$0] }
249         zip(views, reorderedOptions).forEach { $0.0.autoresizingMask = $0.1 }
250         
251         let frames: [NSRect] = views.map { $0.frame }
252         let reorderedFrames = order.map { frames[$0] }
253         zip(views, reorderedFrames)
254             .forEach { $0.0.setFrame($0.1, animate: enableAnimation) }
255     }
256     fileprivate func reorderShipToDoubleLine() {
257         reorder(order: [0, 3, 1, 4, 2, 5])
258     }
259     fileprivate func reorderShipToLeftToRight() {
260         reorder(order: [0, 2, 4, 1, 3, 5])
261     }
262 }
263
264 extension FleetViewController {
265     func buildAnchorageRepairHolder() {
266         AppDelegate.shared.addCounterUpdate { [weak self] in
267             guard let `self` = self else { return }
268             self.repairTime = self.calcRepairTime()
269         }
270     }
271     private func calcRepairTime() -> NSNumber? {
272         let time = anchorageRepair.repairTime
273         let complete = time.timeIntervalSince1970
274         let now = Date(timeIntervalSinceNow: 0.0)
275         let diff = complete - now.timeIntervalSince1970
276         return NSNumber(value: diff + 20.0 * 60)
277     }
278     private var repairShipIds: [Int] { return [19] }
279     dynamic var repairable: Bool {
280         guard let flagShip = fleet?[0]
281             else { return false }
282         return repairShipIds.contains(flagShip.master_ship.stype.id)
283     }
284 }