|
5 | 5 | // Created by CYC on 2018/6/23. |
6 | 6 | // Copyright © 2018年 yichengchen. All rights reserved. |
7 | 7 | // |
| 8 | +// Thanks https://github.com/exelban/stats |
8 | 9 |
|
9 | 10 | import AppKit |
10 | 11 | import Foundation |
11 | | -import RxCocoa |
12 | | -import RxSwift |
13 | | - |
14 | 12 |
|
15 | 13 | @MainActor |
16 | | -class StatusItemView: NSView, StatusItemViewProtocol { |
17 | | - @IBOutlet var imageView: NSImageView! |
| 14 | +final class StatusItemView: NSView, StatusItemViewProtocol { |
| 15 | + private var statusItem: NSStatusItem? |
18 | 16 |
|
19 | | - @IBOutlet var uploadSpeedLabel: NSTextField! |
20 | | - @IBOutlet var downloadSpeedLabel: NSTextField! |
21 | | - @IBOutlet var speedContainerView: NSView! |
| 17 | + private var up: Int = 0 |
| 18 | + private var down: Int = 0 |
| 19 | + private var showSpeed: Bool = true |
| 20 | + private var enableProxy: Bool = false |
22 | 21 |
|
23 | | - var up: Int = 0 |
24 | | - var down: Int = 0 |
| 22 | + private let horizontalPadding: CGFloat = 3 |
| 23 | + private let iconSize: CGFloat = 16 |
| 24 | + private let itemHeight: CGFloat = 22 |
| 25 | + private let textFont = StatusItemTool.font |
| 26 | + private lazy var iconImage: NSImage = StatusItemTool.menuImage |
25 | 27 |
|
26 | 28 | static func create(statusItem: NSStatusItem?) async -> StatusItemView { |
27 | | - var topLevelObjects: NSArray? |
28 | | - if Bundle.main.loadNibNamed("StatusItemView", owner: self, topLevelObjects: &topLevelObjects) { |
29 | | - let view = (topLevelObjects!.first(where: { $0 is NSView }) as? StatusItemView)! |
30 | | - view.setupView() |
31 | | - view.imageView.image = StatusItemTool.menuImage |
32 | | - |
33 | | - if let button = statusItem?.button { |
34 | | - button.addSubview(view) |
35 | | - button.imagePosition = .imageOverlaps |
36 | | - } else { |
37 | | - Logger.log("button = nil") |
38 | | - await ConfigFileManager.shared.openConfigFolder() |
39 | | - } |
40 | | - view.updateViewStatus(enableProxy: false) |
| 29 | + let view = StatusItemView(frame: NSRect(x: 0, y: 0, width: statusItemLengthWithSpeed, height: 22)) |
| 30 | + view.statusItem = statusItem |
| 31 | + |
| 32 | + guard let button = statusItem?.button, let itemView = button.superview else { |
| 33 | + Logger.log("button = nil") |
| 34 | + await ConfigFileManager.shared.openConfigFolder() |
41 | 35 | return view |
42 | 36 | } |
43 | | - return NSView() as! StatusItemView |
| 37 | + |
| 38 | + itemView.subviews.filter { $0 is StatusItemView }.forEach { $0.removeFromSuperview() } |
| 39 | + itemView.addSubview(view) |
| 40 | + view.updateViewStatus(enableProxy: false) |
| 41 | + return view |
| 42 | + } |
| 43 | + |
| 44 | + override init(frame frameRect: NSRect) { |
| 45 | + super.init(frame: frameRect) |
| 46 | + wantsLayer = false |
| 47 | + } |
| 48 | + |
| 49 | + @available(*, unavailable) |
| 50 | + required init?(coder: NSCoder) { |
| 51 | + fatalError("init(coder:) has not been implemented") |
| 52 | + } |
| 53 | + |
| 54 | + override func hitTest(_ point: NSPoint) -> NSView? { |
| 55 | + nil |
| 56 | + } |
| 57 | + |
| 58 | + override var intrinsicContentSize: NSSize { |
| 59 | + NSSize(width: frame.width, height: itemHeight) |
44 | 60 | } |
45 | 61 |
|
46 | | - func setupView() { |
47 | | - uploadSpeedLabel.font = StatusItemTool.font |
48 | | - downloadSpeedLabel.font = StatusItemTool.font |
| 62 | + override func draw(_ dirtyRect: NSRect) { |
| 63 | + super.draw(dirtyRect) |
49 | 64 |
|
50 | | - uploadSpeedLabel.textColor = NSColor.labelColor |
51 | | - downloadSpeedLabel.textColor = NSColor.labelColor |
| 65 | + let sharedColor = enableProxy ? NSColor.labelColor : NSColor.labelColor.withSystemEffect(.disabled) |
| 66 | + |
| 67 | + let iconRect = CGRect( |
| 68 | + x: horizontalPadding, |
| 69 | + y: floor((bounds.height - iconSize) * 0.5), |
| 70 | + width: iconSize, |
| 71 | + height: iconSize |
| 72 | + ) |
| 73 | + NSGraphicsContext.saveGraphicsState() |
| 74 | + sharedColor.set() |
| 75 | + iconRect.fill() |
| 76 | + iconImage.draw(in: iconRect, from: .zero, operation: .destinationIn, fraction: 1.0) |
| 77 | + NSGraphicsContext.restoreGraphicsState() |
| 78 | + |
| 79 | + guard showSpeed else { return } |
| 80 | + |
| 81 | + let upText = SpeedUtils.getSpeedString(for: up) |
| 82 | + let downText = SpeedUtils.getSpeedString(for: down) |
| 83 | + let style = NSMutableParagraphStyle() |
| 84 | + style.alignment = .right |
| 85 | + |
| 86 | + let attributes: [NSAttributedString.Key: Any] = [ |
| 87 | + .font: textFont, |
| 88 | + .foregroundColor: sharedColor, |
| 89 | + .paragraphStyle: style |
| 90 | + ] |
| 91 | + |
| 92 | + let upAttributed = NSAttributedString(string: upText, attributes: attributes) |
| 93 | + let downAttributed = NSAttributedString(string: downText, attributes: attributes) |
| 94 | + let maxDimension: CGFloat = CGFloat.greatestFiniteMagnitude |
| 95 | + |
| 96 | + let upSize = upAttributed.boundingRect( |
| 97 | + with: CGSize(width: maxDimension, height: maxDimension), |
| 98 | + options: [.usesLineFragmentOrigin, .usesFontLeading] |
| 99 | + ).integral.size |
| 100 | + let downSize = downAttributed.boundingRect( |
| 101 | + with: CGSize(width: maxDimension, height: maxDimension), |
| 102 | + options: [.usesLineFragmentOrigin, .usesFontLeading] |
| 103 | + ).integral.size |
| 104 | + |
| 105 | + let textWidth = max(upSize.width, downSize.width) |
| 106 | + let textRight = bounds.width - horizontalPadding |
| 107 | + let textX = textRight - textWidth |
| 108 | + let textHeight = max(upSize.height, downSize.height) |
| 109 | + let upRect = CGRect(x: textX, y: 12, width: textWidth, height: textHeight) |
| 110 | + let downRect = CGRect(x: textX, y: 2, width: textWidth, height: textHeight) |
| 111 | + |
| 112 | + upAttributed.draw(with: upRect) |
| 113 | + downAttributed.draw(with: downRect) |
52 | 114 | } |
53 | 115 |
|
54 | | - func updateSize(width: CGFloat) { |
55 | | - frame = CGRect(x: 0, y: 0, width: width, height: 22) |
| 116 | + func updateSize(_ statusItem: NSStatusItem?, width: CGFloat) { |
| 117 | + frame = NSRect(x: 0, y: 0, width: width, height: itemHeight) |
| 118 | + statusItem?.length = max(width, 1) |
| 119 | + needsDisplay = true |
56 | 120 | } |
57 | 121 |
|
58 | 122 | func updateViewStatus(enableProxy: Bool) { |
59 | | - if enableProxy { |
60 | | - imageView.contentTintColor = NSColor.labelColor |
61 | | - } else { |
62 | | - imageView.contentTintColor = NSColor.labelColor.withSystemEffect(.disabled) |
63 | | - } |
| 123 | + self.enableProxy = enableProxy |
| 124 | + needsDisplay = true |
64 | 125 | } |
65 | 126 |
|
66 | 127 | func updateSpeedLabel(up: Int, down: Int) { |
67 | | - guard !speedContainerView.isHidden else { return } |
68 | | - if up != self.up { |
69 | | - uploadSpeedLabel.stringValue = SpeedUtils.getSpeedString(for: up) |
70 | | - self.up = up |
71 | | - } |
72 | | - if down != self.down { |
73 | | - downloadSpeedLabel.stringValue = SpeedUtils.getSpeedString(for: down) |
74 | | - self.down = down |
75 | | - } |
| 128 | + guard showSpeed else { return } |
| 129 | + self.up = up |
| 130 | + self.down = down |
| 131 | + needsDisplay = true |
76 | 132 | } |
77 | 133 |
|
78 | 134 | func showSpeedContainer(show: Bool) { |
79 | | - speedContainerView.isHidden = !show |
| 135 | + showSpeed = show |
| 136 | + needsDisplay = true |
80 | 137 | } |
81 | 138 | } |
0 commit comments