OSDN Git Service

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