Skip to content

Commit 865f14f

Browse files
committed
refactor: StatusItemView
Thanks https://github.com/exelban/stats
1 parent 2387f44 commit 865f14f

4 files changed

Lines changed: 113 additions & 52 deletions

File tree

ClashX.xcodeproj/project.pbxproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,7 @@
12251225
CURRENT_PROJECT_VERSION = 0;
12261226
DEAD_CODE_STRIPPING = YES;
12271227
DEVELOPMENT_TEAM = "";
1228+
DRIVERKIT_DEPLOYMENT_TARGET = "$(RECOMMENDED_DRIVERKIT_DEPLOYMENT_TARGET)";
12281229
ENABLE_HARDENED_RUNTIME = YES;
12291230
FRAMEWORK_SEARCH_PATHS = (
12301231
"$(inherited)",
@@ -1272,6 +1273,7 @@
12721273
DEAD_CODE_STRIPPING = YES;
12731274
DEPLOYMENT_POSTPROCESSING = YES;
12741275
DEVELOPMENT_TEAM = "";
1276+
DRIVERKIT_DEPLOYMENT_TARGET = "$(RECOMMENDED_DRIVERKIT_DEPLOYMENT_TARGET)";
12751277
ENABLE_HARDENED_RUNTIME = YES;
12761278
FRAMEWORK_SEARCH_PATHS = (
12771279
"$(inherited)",
@@ -1443,6 +1445,7 @@
14431445
CURRENT_PROJECT_VERSION = 3;
14441446
DEAD_CODE_STRIPPING = YES;
14451447
DEVELOPMENT_TEAM = "";
1448+
DRIVERKIT_DEPLOYMENT_TARGET = "$(RECOMMENDED_DRIVERKIT_DEPLOYMENT_TARGET)";
14461449
ENABLE_HARDENED_RUNTIME = YES;
14471450
INFOPLIST_FILE = "$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist";
14481451
LD_RUNPATH_SEARCH_PATHS = (
@@ -1451,7 +1454,7 @@
14511454
"@loader_path/../Frameworks",
14521455
);
14531456
LIBRARY_SEARCH_PATHS = "$(inherited)";
1454-
MACOSX_DEPLOYMENT_TARGET = 11.0;
1457+
MACOSX_DEPLOYMENT_TARGET = 12.4;
14551458
MARKETING_VERSION = 1.2;
14561459
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
14571460
MTL_FAST_MATH = YES;
@@ -1487,6 +1490,7 @@
14871490
DEAD_CODE_STRIPPING = YES;
14881491
DEPLOYMENT_POSTPROCESSING = YES;
14891492
DEVELOPMENT_TEAM = "";
1493+
DRIVERKIT_DEPLOYMENT_TARGET = "$(RECOMMENDED_DRIVERKIT_DEPLOYMENT_TARGET)";
14901494
ENABLE_HARDENED_RUNTIME = YES;
14911495
INFOPLIST_FILE = "$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist";
14921496
LD_RUNPATH_SEARCH_PATHS = (
@@ -1495,7 +1499,7 @@
14951499
"@loader_path/../Frameworks",
14961500
);
14971501
LIBRARY_SEARCH_PATHS = "$(inherited)";
1498-
MACOSX_DEPLOYMENT_TARGET = 11.0;
1502+
MACOSX_DEPLOYMENT_TARGET = 12.4;
14991503
MARKETING_VERSION = 1.2;
15001504
MTL_FAST_MATH = YES;
15011505
OTHER_LDFLAGS = (

ClashX/AppDelegate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
8989
// setup menu item first
9090
statusItem = NSStatusBar.system.statusItem(withLength: statusItemLengthWithSpeed)
9191
statusItemView = await StatusItemView.create(statusItem: statusItem)
92-
statusItemView.updateSize(width: statusItemLengthWithSpeed)
92+
statusItemView.updateSize(statusItem, width: statusItemLengthWithSpeed)
9393
statusMenu.delegate = self
9494
setupStatusMenuItemData()
9595

@@ -206,7 +206,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
206206
self.showNetSpeedIndicatorMenuItem.state = (show ?? true) ? .on : .off
207207
let statusItemLength: CGFloat = (show ?? true) ? statusItemLengthWithSpeed : 25
208208
self.statusItem.length = statusItemLength
209-
self.statusItemView.updateSize(width: statusItemLength)
209+
self.statusItemView.updateSize(self.statusItem, width: statusItemLength)
210210
self.statusItemView.showSpeedContainer(show: show ?? true)
211211
}.disposed(by: disposeBag)
212212

ClashX/Views/StatusItem/StatusItemView.swift

Lines changed: 104 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,77 +5,134 @@
55
// Created by CYC on 2018/6/23.
66
// Copyright © 2018年 yichengchen. All rights reserved.
77
//
8+
// Thanks https://github.com/exelban/stats
89

910
import AppKit
1011
import Foundation
11-
import RxCocoa
12-
import RxSwift
13-
1412

1513
@MainActor
16-
class StatusItemView: NSView, StatusItemViewProtocol {
17-
@IBOutlet var imageView: NSImageView!
14+
final class StatusItemView: NSView, StatusItemViewProtocol {
15+
private var statusItem: NSStatusItem?
1816

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
2221

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
2527

2628
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()
4135
return view
4236
}
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)
4460
}
4561

46-
func setupView() {
47-
uploadSpeedLabel.font = StatusItemTool.font
48-
downloadSpeedLabel.font = StatusItemTool.font
62+
override func draw(_ dirtyRect: NSRect) {
63+
super.draw(dirtyRect)
4964

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)
52114
}
53115

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
56120
}
57121

58122
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
64125
}
65126

66127
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
76132
}
77133

78134
func showSpeedContainer(show: Bool) {
79-
speedContainerView.isHidden = !show
135+
showSpeed = show
136+
needsDisplay = true
80137
}
81138
}

ClashX/Views/StatusItem/StatusItemViewProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ protocol StatusItemViewProtocol: AnyObject {
1313
func updateViewStatus(enableProxy: Bool)
1414
func updateSpeedLabel(up: Int, down: Int)
1515
func showSpeedContainer(show: Bool)
16-
func updateSize(width: CGFloat)
16+
func updateSize(_ statusItem: NSStatusItem?, width: CGFloat)
1717
}

0 commit comments

Comments
 (0)