OSDN Git Service

keyPathsForValuesAffectingValue(forKey:)に集約した
[kcd/KCD.git] / KCD / BroserWindowController.swift
1 //
2 //  BroserWindowController.swift
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2016/12/31.
6 //  Copyright © 2016年 Hori,Masaki. All rights reserved.
7 //
8
9 import Cocoa
10
11
12 private extension Selector {
13     
14     static let reloadContent = #selector(BroserWindowController.reloadContent(_:))
15     static let deleteCacheAndReload = #selector(BroserWindowController.deleteCacheAndReload(_:))
16     static let clearQuestList = #selector(BroserWindowController.clearQuestList(_:))
17     static let selectView = #selector(BroserWindowController.selectView(_:))
18     static let changeMainTab = #selector(BroserWindowController.changeMainTab(_:))
19     static let screenShot = #selector(BroserWindowController.screenShot(_:))
20     static let toggleAnchorageSize = #selector(BroserWindowController.toggleAnchorageSize(_:))
21     static let showHideCombinedView = #selector(BroserWindowController.showHideCombinedView(_:))
22     static let fleetListAbove = #selector(BroserWindowController.fleetListAbove(_:))
23     static let fleetListBelow = #selector(BroserWindowController.fleetListBelow(_:))
24     static let fleetListDivide = #selector(BroserWindowController.fleetListDivide(_:))
25     static let fleetListSimple = #selector(BroserWindowController.fleetListSimple(_:))
26     static let reorderToDoubleLine = #selector(BroserWindowController.reorderToDoubleLine(_:))
27     static let reorderToLeftToRight = #selector(BroserWindowController.reorderToLeftToRight(_:))
28     static let selectNextFleet = #selector(BroserWindowController.selectNextFleet(_:))
29     static let selectPreviousFleet = #selector(BroserWindowController.selectPreviousFleet(_:))
30     static let changeSakutekiCalculator = #selector(BroserWindowController.changeSakutekiCalculator(_:))
31 }
32
33 final class BroserWindowController: NSWindowController {
34     
35     enum FleetViewPosition: Int {
36         
37         case above = 0
38         case below = 1
39         case divided = 2
40         case oldStyle = 0xffffffff
41     }
42     
43     @objc override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
44         
45         switch key {
46             
47         case #keyPath(flagShipName): return [#keyPath(flagShipID)]
48             
49         default: return []
50         }
51     }
52     
53     @objc let managedObjectContext = ServerDataStore.default.context
54     
55     deinit {
56         
57         NotificationCenter.default.removeObserver(self)
58     }
59     
60     @IBOutlet weak var placeholder: NSView!
61     @IBOutlet weak var combinedViewPlaceholder: NSView!
62     @IBOutlet weak var deckPlaceholder: NSView!
63     @IBOutlet weak var resourcePlaceholder: NSView!
64     @IBOutlet weak var ancherageRepariTimerPlaceholder: NSView!
65     @IBOutlet weak var informations: NSTabView!
66     @IBOutlet var deckContoller: NSArrayController!
67     
68     override var windowNibName: NSNib.Name {
69         
70         return .nibName(instanceOf: self)
71     }
72     
73     @objc var flagShipID: Int = 0
74     @objc var flagShipName: String? {
75         return ServerDataStore.default.ship(by: flagShipID)?.name
76     }
77     var changeMainTabHandler: ((Int) -> Void)?
78     @objc dynamic var selectedMainTabIndex: Int = 0 {
79         
80         didSet {
81             changeMainTabHandler?(selectedMainTabIndex)
82         }
83     }
84     
85     private var gameViewController: GameViewController!
86     private var fleetViewController: FleetViewController!
87     private var tabViewItemViewControllers: [MainTabVIewItemViewController] = []
88     private var ancherageRepariTimerViewController: AncherageRepairTimerViewController!
89     private var resourceViewController: ResourceViewController!
90     private var docksViewController: DocksViewController!
91     private var shipViewController: ShipViewController!
92     private var powerUpViewController: PowerUpSupportViewController!
93     private var strengthedListViewController: StrengthenListViewController!
94     private var repairListViewController: RepairListViewController!
95     private var combinedViewController: CombileViewController!
96     
97     private var fleetViewPosition: FleetViewPosition = .above
98     private var isCombinedMode = false
99     
100     // MARK: - Function
101     override func windowDidLoad() {
102         
103         super.windowDidLoad()
104     
105         gameViewController = GameViewController()
106         replace(placeholder, with: gameViewController)
107         
108         resourceViewController = ResourceViewController()
109         replace(resourcePlaceholder, with: resourceViewController)
110         
111         ancherageRepariTimerViewController = AncherageRepairTimerViewController()
112         replace(ancherageRepariTimerPlaceholder, with: ancherageRepariTimerViewController)
113         if UserDefaults.standard[.screenshotButtonSize] == .small { toggleAnchorageSize(nil) }
114         
115         tabViewItemViewControllers = [
116             DocksViewController(),
117             ShipViewController(),
118             PowerUpSupportViewController(),
119             StrengthenListViewController(),
120             RepairListViewController()
121         ]
122         tabViewItemViewControllers.enumerated().forEach {
123             
124             _ = $0.element.view
125             let item = informations.tabViewItem(at: $0.offset)
126             item.viewController = $0.element
127         }
128         
129         fleetViewController = FleetViewController(viewType: .detailViewType)
130         replace(deckPlaceholder, with: fleetViewController)
131         setFleetView(position: UserDefaults.standard[.fleetViewPosition], animate: false)
132         fleetViewController.enableAnimation = false
133         fleetViewController.shipOrder = UserDefaults.standard[.fleetViewShipOrder]
134         fleetViewController.enableAnimation = true
135         
136         bind(NSBindingName(rawValue: #keyPath(flagShipID)), to: deckContoller, withKeyPath: "selection.ship_0", options: nil)
137         
138         NotificationCenter.default
139             .addObserver(forName: .CombinedDidCange, object: nil, queue: nil) {
140                 
141                 guard UserDefaults.standard[.autoCombinedView] else { return }
142                 guard let type = $0.userInfo?[CombinedCommand.userInfoKey] as? CombineType else { return }
143                 
144                 if !Thread.isMainThread { Thread.sleep(forTimeInterval: 0.1) }
145                 
146                 DispatchQueue.main.async {
147                     
148                     switch type {
149                     case .cancel:
150                         self.hideCombinedView()
151                         
152                     case .maneuver, .water, .transportation:
153                         self.showCombinedView()
154                     }
155                 }
156         }
157         
158         if UserDefaults.standard[.lastHasCombinedView] { showCombinedView() }
159     }
160     
161     override func swipe(with event: NSEvent) {
162         
163         guard UserDefaults.standard[.useSwipeChangeCombinedView] else { return }
164         
165         if event.deltaX > 0 {
166             
167             showCombinedView()
168             
169         }
170         
171         if event.deltaX < 0 {
172             
173             hideCombinedView()
174             
175         }
176     }
177     
178     @objc func windowWillClose(_ notification: Notification) {
179         
180         UserDefaults.standard[.lastHasCombinedView] = isCombinedMode
181     }
182     
183     private func replace(_ view: NSView, with viewController: NSViewController) {
184         
185         viewController.view.frame = view.frame
186         viewController.view.autoresizingMask = view.autoresizingMask
187         self.window?.contentView?.replaceSubview(view, with: viewController.view)
188     }
189     
190     private func showCombinedView() {
191         
192         if isCombinedMode { return }
193         
194         if fleetViewPosition == .oldStyle { return }
195         
196         isCombinedMode = true
197         
198         if combinedViewController == nil {
199             
200             combinedViewController = CombileViewController()
201             combinedViewController.view.isHidden = true
202             replace(combinedViewPlaceholder, with: combinedViewController)
203         }
204         
205         var winFrame = window!.frame
206         let incWid = combinedViewController.view.frame.maxX
207         winFrame.size.width += incWid
208         winFrame.origin.x -= incWid
209         combinedViewController.view.isHidden = false
210         window?.setFrame(winFrame, display: true, animate: true)
211     }
212     
213     private func hideCombinedView() {
214         
215         if !isCombinedMode { return }
216         
217         isCombinedMode = false
218         
219         var winFrame = window!.frame
220         let decWid = combinedViewController.view.frame.maxX
221         winFrame.size.width -= decWid
222         winFrame.origin.x += decWid
223         window?.setFrame(winFrame, display: true, animate: true)
224         combinedViewController.view.isHidden = true
225     }
226 }
227
228 // MARK: - IBAction
229 extension BroserWindowController {
230     
231     private func showView(number: Int) {
232         
233         informations.selectTabViewItem(at: number)
234     }
235     
236     @IBAction func reloadContent(_ sender: AnyObject?) {
237         
238         gameViewController.reloadContent(sender)
239     }
240     
241     @IBAction func deleteCacheAndReload(_ sender: AnyObject?) {
242         
243         gameViewController.deleteCacheAndReload(sender)
244     }
245     
246     @IBAction func clearQuestList(_ sender: AnyObject?) {
247         
248         let store = ServerDataStore.oneTimeEditor()
249         store.quests().forEach { store.delete($0) }
250     }
251     
252     @IBAction func selectView(_ sender: AnyObject?) {
253         
254         guard let tag = sender?.tag else { return }
255         
256         showView(number: tag)
257     }
258     
259     @IBAction func changeMainTab(_ sender: AnyObject?) {
260         
261         guard let segment = sender?.selectedSegment else { return }
262         
263         showView(number: segment)
264     }
265     
266     @IBAction func screenShot(_ sender: AnyObject?) {
267         
268         gameViewController.screenShot(sender)
269     }
270     
271     @IBAction func toggleAnchorageSize(_ sender: AnyObject?) {
272         
273         let current = ancherageRepariTimerViewController.controlSize
274         var diff = AncherageRepairTimerViewController.regularHeight - AncherageRepairTimerViewController.smallHeight
275         let newSize: NSControl.ControlSize = {
276             
277             if current == .regular {
278                 
279                 diff *= -1
280                 
281                 return .small
282             }
283             
284             return .regular
285         }()
286         ancherageRepariTimerViewController.controlSize = newSize
287         
288         var frame = informations.frame
289         frame.size.height -= diff
290         frame.origin.y += diff
291         informations.frame = frame
292         
293         UserDefaults.standard[.screenshotButtonSize] = newSize
294     }
295     
296     @IBAction func showHideCombinedView(_ sender: AnyObject?) {
297         
298          isCombinedMode ? hideCombinedView() : showCombinedView()
299     }
300     
301     @IBAction func fleetListAbove(_ sender: AnyObject?) {
302         
303         setFleetView(position: .above, animate: true)
304     }
305     
306     @IBAction func fleetListBelow(_ sender: AnyObject?) {
307         
308         setFleetView(position: .below, animate: true)
309     }
310     
311     @IBAction func fleetListDivide(_ sender: AnyObject?) {
312         
313         setFleetView(position: .divided, animate: true)
314     }
315     
316     @IBAction func fleetListSimple(_ sender: AnyObject?) {
317         
318         setFleetView(position: .oldStyle, animate: true)
319     }
320     
321     @IBAction func reorderToDoubleLine(_ sender: AnyObject?) {
322         
323         fleetViewController.shipOrder = .doubleLine
324         UserDefaults.standard[.fleetViewShipOrder] = .doubleLine
325     }
326     
327     @IBAction func reorderToLeftToRight(_ sender: AnyObject?) {
328         
329         fleetViewController.shipOrder = .leftToRight
330         UserDefaults.standard[.fleetViewShipOrder] = .leftToRight
331     }
332     
333     @IBAction func selectNextFleet(_ sender: AnyObject?) {
334         
335         fleetViewController.selectNextFleet(sender)
336     }
337     
338     @IBAction func selectPreviousFleet(_ sender: AnyObject?) {
339         
340         fleetViewController.selectPreviousFleet(sender)
341     }
342     
343     @IBAction func changeSakutekiCalculator(_ sender: Any?) {
344         
345         fleetViewController.changeSakutekiCalculator(sender)
346     }
347     
348     override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
349         
350         guard let action: Selector = menuItem.action else { return false }
351         
352         switch action {
353         case Selector.reloadContent, Selector.screenShot, Selector.deleteCacheAndReload:
354             return gameViewController.validateMenuItem(menuItem)
355             
356         case Selector.selectView, Selector.selectNextFleet, Selector.selectPreviousFleet:
357             return true
358             
359         case Selector.fleetListAbove:
360             menuItem.state = (fleetViewPosition == .above ? .on : .off)
361             return true
362             
363         case Selector.fleetListBelow:
364             menuItem.state = (fleetViewPosition == .below ? .on : .off)
365             return true
366             
367         case Selector.fleetListDivide:
368             menuItem.state = (fleetViewPosition == .divided ? .on : .off)
369             return true
370             
371         case Selector.fleetListSimple:
372             menuItem.state = (fleetViewPosition == .oldStyle ? .on : .off)
373             return true
374             
375         case Selector.reorderToDoubleLine:
376             menuItem.state = (fleetViewController.shipOrder == .doubleLine ? .on : .off)
377             return true
378             
379         case Selector.reorderToLeftToRight:
380             menuItem.state = (fleetViewController.shipOrder == .leftToRight ? .on: .off)
381             return true
382             
383         case Selector.clearQuestList:
384             return true
385             
386         case Selector.showHideCombinedView:
387             if isCombinedMode {
388                 
389                 menuItem.title = LocalizedStrings.hideCombinedView.string
390                 
391             } else {
392                 
393                 menuItem.title = LocalizedStrings.showCombinedView.string
394                 
395             }
396             if fleetViewPosition == .oldStyle { return false }
397             
398             return true
399             
400         case Selector.toggleAnchorageSize:
401             return true
402             
403         case Selector.changeSakutekiCalculator:
404             return fleetViewController.validateMenuItem(menuItem)
405             
406         default:
407             return false
408         }
409     }
410 }
411
412 extension BroserWindowController {
413     
414     private static let margin: CGFloat = 1.0
415     private static let flashTopMargin: CGFloat = 4.0
416     
417     private func changeFleetViewForFleetViewPositionIfNeeded(position newPosition: FleetViewPosition) {
418         
419         if fleetViewPosition == newPosition { return }
420         if fleetViewPosition != .oldStyle && newPosition != .oldStyle { return }
421         if newPosition == .oldStyle && isCombinedMode { hideCombinedView() }
422         
423         let type: FleetViewType = (newPosition == .oldStyle) ? .minimumViewType : .detailViewType
424         
425         guard let newController = FleetViewController(viewType: type) else { return }
426         
427         newController.enableAnimation = true
428         newController.shipOrder = fleetViewController.shipOrder
429         replace(fleetViewController.view, with: newController)
430         fleetViewController = newController
431     }
432     
433     private func windowHeightForFleetViewPosition(position newPosition: FleetViewPosition) -> CGFloat {
434         
435         guard var contentHeight = window!.contentView?.frame.size.height else { return 0.0 }
436         
437         if fleetViewPosition == newPosition { return contentHeight }
438         if fleetViewPosition == .oldStyle {
439             
440             contentHeight += FleetViewController.heightDifference
441         }
442         if newPosition == .oldStyle {
443             
444             contentHeight -= FleetViewController.heightDifference
445         }
446         
447         return contentHeight
448     }
449     
450     private func windowFrameForFleetViewPosition(position newPosition: FleetViewPosition) -> NSRect {
451         
452         var contentRect = window!.frame
453         
454         if fleetViewPosition == newPosition { return contentRect }
455         if fleetViewPosition == .oldStyle {
456             
457             contentRect.size.height += FleetViewController.heightDifference
458             contentRect.origin.y -= FleetViewController.heightDifference
459         }
460         if newPosition == .oldStyle {
461             
462             contentRect.size.height -= FleetViewController.heightDifference
463             contentRect.origin.y += FleetViewController.heightDifference
464         }
465         
466         return contentRect
467     }
468     
469     private func flashFrameForFleetViewPosition(position newPosition: FleetViewPosition) -> NSRect {
470         
471         let contentHeight = windowHeightForFleetViewPosition(position: newPosition)
472         
473         var flashRect = gameViewController.view.frame
474         var flashY: CGFloat
475         switch newPosition {
476         case .above:
477             flashY = contentHeight - flashRect.height - fleetViewController.normalHeight
478             
479         case .below:
480             flashY = contentHeight - flashRect.height
481             
482         case .divided:
483             flashY = contentHeight - flashRect.height - fleetViewController.upsideHeight - BroserWindowController.margin
484             
485         case .oldStyle:
486             flashY = contentHeight - flashRect.height - BroserWindowController.flashTopMargin
487         }
488         
489         flashRect.origin.y = flashY
490         
491         return flashRect
492     }
493     
494     private func fleetViewFrameForFleetViewPosition(position newPosition: FleetViewPosition) -> NSRect {
495         
496         let contentHeight = windowHeightForFleetViewPosition(position: newPosition)
497         let flashRect = gameViewController.view.frame
498         var fleetListRect = fleetViewController.view.frame
499         
500         var fleetViewHeight: CGFloat
501         var fleetViewY: CGFloat
502         
503         switch newPosition {
504         case .above:
505             fleetViewHeight = fleetViewController.normalHeight
506             fleetViewY = contentHeight - fleetViewHeight
507             
508         case .below:
509             fleetViewHeight = fleetViewController.normalHeight
510             fleetViewY = contentHeight - fleetViewHeight - flashRect.height - BroserWindowController.margin
511             
512         case .divided:
513             fleetViewHeight = fleetViewController.normalHeight + flashRect.height + BroserWindowController.margin + BroserWindowController.margin
514             fleetViewY = contentHeight - fleetViewHeight
515             
516         case .oldStyle:
517             fleetViewHeight = FleetViewController.oldStyleFleetViewHeight
518             fleetViewY = contentHeight - fleetViewHeight - flashRect.height - BroserWindowController.margin - BroserWindowController.flashTopMargin
519         }
520         
521         fleetListRect.size.height = fleetViewHeight
522         fleetListRect.origin.y = fleetViewY
523         
524         return fleetListRect
525     }
526     
527     private func setFleetView(position newPosition: FleetViewPosition, animate: Bool) {
528         
529         guard let window = window else { return }
530         
531         changeFleetViewForFleetViewPositionIfNeeded(position: newPosition)
532         let winFrame = windowFrameForFleetViewPosition(position: newPosition)
533         let flashRect = flashFrameForFleetViewPosition(position: newPosition)
534         let fleetListRect = fleetViewFrameForFleetViewPosition(position: newPosition)
535         
536         fleetViewPosition = newPosition
537         UserDefaults.standard[.fleetViewPosition] = newPosition
538         
539         if animate {
540             
541             let winAnime: [NSViewAnimation.Key: Any]  = [.target: window,
542                                                          .endFrame: NSValue(rect: winFrame) ]
543             let flashAnime: [NSViewAnimation.Key: Any] = [.target: gameViewController.view,
544                                                           .endFrame: NSValue(rect: flashRect) ]
545             let fleetAnime: [NSViewAnimation.Key: Any] = [.target: fleetViewController.view,
546                                                           .endFrame: NSValue(rect: fleetListRect) ]
547             
548             let anime = NSViewAnimation(viewAnimations: [winAnime, flashAnime, fleetAnime])
549             
550             anime.start()
551             
552         } else {
553             
554             window.setFrame(winFrame, display: false)
555             gameViewController.view.frame = flashRect
556             fleetViewController.view.frame = fleetListRect
557         }
558     }
559     
560 }
561
562 @available(OSX 10.12.2, *)
563 private var mainTouchBars: [Int: NSTouchBar] = [:]
564 @available(OSX 10.12.2, *)
565 private var shipTypeButtons: [Int: NSPopoverTouchBarItem] = [:]
566 @available(OSX 10.12.2, *)
567 private var shipTypeSegments: [Int: NSSegmentedControl] = [:]
568
569 @available(OSX 10.12.2, *)
570 extension BroserWindowController {
571     
572     @IBOutlet var mainTouchBar: NSTouchBar! {
573         
574         get { return mainTouchBars[hashValue] }
575         set { mainTouchBars[hashValue] = newValue }
576     }
577     
578     @IBOutlet var shipTypeButton: NSPopoverTouchBarItem! {
579         
580         get { return shipTypeButtons[hashValue] }
581         set { shipTypeButtons[hashValue] = newValue }
582     }
583     
584     @IBOutlet var shipTypeSegment: NSSegmentedControl! {
585         
586         get { return shipTypeSegments[hashValue] }
587         set { shipTypeSegments[hashValue] = newValue }
588     }
589     
590     override func makeTouchBar() -> NSTouchBar? {
591         
592         if let mainTouchBar = mainTouchBar { return mainTouchBar }
593         
594         var array: NSArray?
595         Bundle.main.loadNibNamed(NSNib.Name("BroswerTouchBar"), owner: self, topLevelObjects: &array)
596         
597         shipTypeSegment.bind(.selectedIndex,
598                              to: tabViewItemViewControllers[0],
599                              withKeyPath: #keyPath(MainTabVIewItemViewController.selectedShipType),
600                              options: nil)
601         let o = selectedMainTabIndex
602         selectedMainTabIndex = o
603         
604         changeMainTabHandler = { [weak self] in
605             
606             guard let `self` = self else { return }
607             
608             self.shipTypeButton.dismissPopover(nil)
609             self.shipTypeSegment.unbind(.selectedIndex)
610             
611             guard let button = self.shipTypeButton.view as? NSButton else { return }
612             
613             let vc = self.tabViewItemViewControllers[$0]
614             button.isHidden = !vc.hasShipTypeSelector
615             self.shipTypeSegment.bind(.selectedIndex,
616                                       to: vc,
617                                       withKeyPath: #keyPath(MainTabVIewItemViewController.selectedShipType),
618                                       options: nil)
619         }
620         
621         return mainTouchBar
622     }
623 }