feat(menu): use view to draw proxy item [beta] [appcenter] [notarize]
This commit is contained in:
parent
0a1d259b72
commit
b8d5d007e5
|
@ -54,6 +54,7 @@
|
|||
49D176A72355FE680093DD7B /* NetworkChangeNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176A62355FE680093DD7B /* NetworkChangeNotifier.swift */; };
|
||||
49D176A9235614340093DD7B /* ProxyGroupSpeedTestMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176A8235614340093DD7B /* ProxyGroupSpeedTestMenuItem.swift */; };
|
||||
49D176AB23575BB20093DD7B /* ProxyGroupMenuItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176AA23575BB20093DD7B /* ProxyGroupMenuItemView.swift */; };
|
||||
F910AA24240134AF00116E95 /* ProxyGroupMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910AA23240134AF00116E95 /* ProxyGroupMenu.swift */; };
|
||||
F915A4622366ADEF004840BE /* ClashConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F915A4612366ADEF004840BE /* ClashConnection.swift */; };
|
||||
F9203A26236342820020D57D /* AppDelegate+..swift in Sources */ = {isa = PBXBuildFile; fileRef = F9203A25236342820020D57D /* AppDelegate+..swift */; };
|
||||
F92D0B24236BC12000575E15 /* SavedProxyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92D0B23236BC12000575E15 /* SavedProxyModel.swift */; };
|
||||
|
@ -168,6 +169,7 @@
|
|||
49D176AA23575BB20093DD7B /* ProxyGroupMenuItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupMenuItemView.swift; sourceTree = "<group>"; };
|
||||
5217C006C5A22A1CEA24BFC1 /* Pods-ClashX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClashX.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ClashX/Pods-ClashX.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
A1485BCE642059532D01B8BA /* Pods-ClashX.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClashX.release.xcconfig"; path = "Pods/Target Support Files/Pods-ClashX/Pods-ClashX.release.xcconfig"; sourceTree = "<group>"; };
|
||||
F910AA23240134AF00116E95 /* ProxyGroupMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupMenu.swift; sourceTree = "<group>"; };
|
||||
F915A4612366ADEF004840BE /* ClashConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashConnection.swift; sourceTree = "<group>"; };
|
||||
F9203A25236342820020D57D /* AppDelegate+..swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+..swift"; sourceTree = "<group>"; };
|
||||
F92D0B23236BC12000575E15 /* SavedProxyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedProxyModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -301,6 +303,7 @@
|
|||
499A485922ED781100F6C675 /* RemoteConfigAddView.xib */,
|
||||
495340B220DE68C300B0D3FF /* StatusItemView.swift */,
|
||||
495340AF20DE5F7200B0D3FF /* StatusItemView.xib */,
|
||||
F910AA23240134AF00116E95 /* ProxyGroupMenu.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
@ -668,6 +671,7 @@
|
|||
499A486522EEA3FD00F6C675 /* Array+Safe.swift in Sources */,
|
||||
F92D0B24236BC12000575E15 /* SavedProxyModel.swift in Sources */,
|
||||
F92D0B2A236C759100575E15 /* NSTextField+Vibrancy.swift in Sources */,
|
||||
F910AA24240134AF00116E95 /* ProxyGroupMenu.swift in Sources */,
|
||||
4952C3BF2115C7CA004A4FA8 /* MenuItemFactory.swift in Sources */,
|
||||
F977FAAC2366790500C17F1F /* AutoUpgardeManager.swift in Sources */,
|
||||
499A485822ED715200F6C675 /* RemoteConfigModel.swift in Sources */,
|
||||
|
|
|
@ -88,8 +88,7 @@ class MenuItemFactory {
|
|||
if !ConfigManager.shared.disableShowCurrentProxyInMenu {
|
||||
menu.view = ProxyGroupMenuItemView(group: proxyGroup.name, targetProxy: selectedName)
|
||||
}
|
||||
let submenu = NSMenu(title: proxyGroup.name)
|
||||
var hasSelected = false
|
||||
let submenu = ProxyGroupMenu(title: proxyGroup.name)
|
||||
|
||||
for proxy in proxyGroup.all ?? [] {
|
||||
guard let proxyModel = proxyMap[proxy] else { continue }
|
||||
|
@ -98,17 +97,11 @@ class MenuItemFactory {
|
|||
continue
|
||||
}
|
||||
let proxyItem = ProxyMenuItem(proxy: proxyModel, action: #selector(MenuItemFactory.actionSelectProxy(sender:)),
|
||||
maxProxyNameLength: proxyGroup.maxProxyNameLength)
|
||||
selected: proxy == selectedName)
|
||||
proxyItem.target = MenuItemFactory.self
|
||||
proxyItem.isSelected = proxy == selectedName
|
||||
|
||||
if proxyItem.isSelected { hasSelected = true }
|
||||
submenu.add(delegate: proxyItem)
|
||||
submenu.addItem(proxyItem)
|
||||
}
|
||||
|
||||
if !hasSelected && submenu.items.count > 0 {
|
||||
actionSelectProxy(sender: submenu.items[0] as! ProxyMenuItem)
|
||||
}
|
||||
addSpeedTestMenuItem(submenu, proxyGroup: proxyGroup)
|
||||
menu.submenu = submenu
|
||||
if !ConfigManager.shared.disableShowCurrentProxyInMenu {
|
||||
|
@ -145,17 +138,18 @@ class MenuItemFactory {
|
|||
return menu
|
||||
}
|
||||
|
||||
private static func addSpeedTestMenuItem(_ menus: NSMenu, proxyGroup: ClashProxy) {
|
||||
private static func addSpeedTestMenuItem(_ menu: NSMenu, proxyGroup: ClashProxy) {
|
||||
guard proxyGroup.speedtestAble.count > 0 else { return }
|
||||
let speedTestItem = ProxyGroupSpeedTestMenuItem(group: proxyGroup)
|
||||
let separator = NSMenuItem.separator()
|
||||
if showSpeedTestItemAtTop {
|
||||
menus.insertItem(separator, at: 0)
|
||||
menus.insertItem(speedTestItem, at: 0)
|
||||
menu.insertItem(separator, at: 0)
|
||||
menu.insertItem(speedTestItem, at: 0)
|
||||
} else {
|
||||
menus.addItem(separator)
|
||||
menus.addItem(speedTestItem)
|
||||
menu.addItem(separator)
|
||||
menu.addItem(speedTestItem)
|
||||
}
|
||||
(menu as? ProxyGroupMenu)?.add(delegate: speedTestItem)
|
||||
}
|
||||
|
||||
private static func generateHistoryMenu(_ proxy: ClashProxy) -> NSMenu? {
|
||||
|
@ -171,15 +165,15 @@ class MenuItemFactory {
|
|||
let proxyMap = proxyInfo.proxiesMap
|
||||
|
||||
let menu = NSMenuItem(title: proxyGroup.name, action: nil, keyEquivalent: "")
|
||||
let submenu = NSMenu(title: proxyGroup.name)
|
||||
let submenu = ProxyGroupMenu(title: proxyGroup.name)
|
||||
|
||||
for proxy in proxyGroup.all ?? [] {
|
||||
guard let proxyModel = proxyMap[proxy] else { continue }
|
||||
let proxyItem = ProxyMenuItem(proxy: proxyModel,
|
||||
action: #selector(empty),
|
||||
maxProxyNameLength: proxyGroup.maxProxyNameLength)
|
||||
proxyItem.isSelected = false
|
||||
selected: false)
|
||||
proxyItem.target = MenuItemFactory.self
|
||||
submenu.add(delegate: proxyItem)
|
||||
submenu.addItem(proxyItem)
|
||||
}
|
||||
addSpeedTestMenuItem(submenu, proxyGroup: proxyGroup)
|
||||
|
|
|
@ -99,19 +99,6 @@ class ClashProxy: Codable {
|
|||
private enum CodingKeys: String, CodingKey {
|
||||
case type, all, history, now, name
|
||||
}
|
||||
|
||||
lazy var maxProxyName: String = {
|
||||
return all?.max { $1.count > $0.count } ?? ""
|
||||
}()
|
||||
|
||||
lazy var maxProxyNameLength: CGFloat = {
|
||||
let rect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: 20)
|
||||
let attr = [NSAttributedString.Key.font: NSFont.menuBarFont(ofSize: 14)]
|
||||
return (self.maxProxyName as NSString)
|
||||
.boundingRect(with: rect,
|
||||
options: .usesLineFragmentOrigin,
|
||||
attributes: attr).width
|
||||
}()
|
||||
}
|
||||
|
||||
class ClashProxyResp {
|
||||
|
|
|
@ -6,22 +6,16 @@
|
|||
// Copyright © 2019 west2online. All rights reserved.
|
||||
//
|
||||
|
||||
import Carbon
|
||||
import Cocoa
|
||||
|
||||
class MenuItemBaseView: NSView {
|
||||
private var isMouseInsideView = false
|
||||
private var isMenuOpen = false
|
||||
private var eventHandler: EventHandlerRef?
|
||||
private let handleClick: Bool
|
||||
private let autolayout: Bool
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var isHighlighted: Bool {
|
||||
let enable = enclosingMenuItem?.isEnabled ?? true
|
||||
return (isMouseInsideView || isMenuOpen) && enable
|
||||
}
|
||||
var isHighlighted: Bool = false
|
||||
|
||||
let effectView: NSVisualEffectView = {
|
||||
let effectView = NSVisualEffectView()
|
||||
|
@ -32,17 +26,18 @@ class MenuItemBaseView: NSView {
|
|||
return effectView
|
||||
}()
|
||||
|
||||
var labels: [NSTextField] {
|
||||
var cells: [NSCell?] {
|
||||
assertionFailure("Please override")
|
||||
return []
|
||||
}
|
||||
|
||||
static let labelFont = NSFont.menuFont(ofSize: 14)
|
||||
var labels: [NSTextField] {
|
||||
return []
|
||||
}
|
||||
|
||||
init(frame frameRect: NSRect = NSRect(x: 0, y: 0, width: 0, height: 20),
|
||||
handleClick: Bool,
|
||||
autolayout: Bool) {
|
||||
self.handleClick = handleClick
|
||||
static let labelFont = NSFont.menuBarFont(ofSize: 0)
|
||||
|
||||
init(frame frameRect: NSRect = NSRect(x: 0, y: 0, width: 0, height: 20), autolayout: Bool) {
|
||||
self.autolayout = autolayout
|
||||
super.init(frame: frameRect)
|
||||
setupView()
|
||||
|
@ -60,10 +55,6 @@ class MenuItemBaseView: NSView {
|
|||
assertionFailure("Please override this method")
|
||||
}
|
||||
|
||||
func updateBackground(_ label: NSTextField) {
|
||||
label.cell?.backgroundStyle = isHighlighted ? .emphasized : .normal
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func setupView() {
|
||||
|
@ -82,8 +73,10 @@ class MenuItemBaseView: NSView {
|
|||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
super.draw(dirtyRect)
|
||||
effectView.material = isHighlighted ? .selection : .popover
|
||||
labels.forEach { updateBackground($0) }
|
||||
labels.forEach { $0.textColor = (enclosingMenuItem?.isEnabled ?? true) ? NSColor.labelColor : NSColor.placeholderTextColor }
|
||||
let highlighted = isHighlighted && (enclosingMenuItem?.isEnabled ?? false)
|
||||
effectView.material = highlighted ? .selection : .popover
|
||||
cells.forEach { $0?.backgroundStyle = isHighlighted ? .emphasized : .normal }
|
||||
}
|
||||
|
||||
override func viewWillMove(toWindow newWindow: NSWindow?) {
|
||||
|
@ -94,10 +87,6 @@ class MenuItemBaseView: NSView {
|
|||
updateTrackingAreas()
|
||||
}
|
||||
|
||||
override func viewDidMoveToWindow() {
|
||||
super.viewDidMoveToWindow()
|
||||
}
|
||||
|
||||
override func viewDidMoveToSuperview() {
|
||||
super.viewDidMoveToSuperview()
|
||||
guard autolayout else { return }
|
||||
|
@ -108,38 +97,13 @@ class MenuItemBaseView: NSView {
|
|||
}
|
||||
}
|
||||
|
||||
override func updateTrackingAreas() {
|
||||
super.updateTrackingAreas()
|
||||
}
|
||||
|
||||
override func mouseUp(with event: NSEvent) {
|
||||
DispatchQueue.main.async {
|
||||
self.didClickView()
|
||||
}
|
||||
}
|
||||
|
||||
override func updateTrackingAreas() {
|
||||
super.updateTrackingAreas()
|
||||
trackingAreas.forEach { removeTrackingArea($0) }
|
||||
enclosingMenuItem?.submenu?.delegate = self
|
||||
addTrackingArea(NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeAlways], owner: self, userInfo: nil))
|
||||
}
|
||||
|
||||
override func mouseEntered(with event: NSEvent) {
|
||||
isMouseInsideView = true
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
override func mouseExited(with event: NSEvent) {
|
||||
isMouseInsideView = false
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
extension MenuItemBaseView: NSMenuDelegate {
|
||||
func menuWillOpen(_ menu: NSMenu) {
|
||||
isMenuOpen = true
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
func menuDidClose(_ menu: NSMenu) {
|
||||
isMenuOpen = false
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// ProxyGroupMenu.swift
|
||||
// ClashX
|
||||
//
|
||||
// Created by yicheng on 2020/2/22.
|
||||
// Copyright © 2020 west2online. All rights reserved.
|
||||
//
|
||||
import AppKit
|
||||
|
||||
@objc protocol ProxyGroupMenuHighlightDelegate: class {
|
||||
func highlight(item: NSMenuItem?)
|
||||
}
|
||||
|
||||
class ProxyGroupMenu: NSMenu {
|
||||
var highlightDelegates = NSHashTable<ProxyGroupMenuHighlightDelegate>.weakObjects()
|
||||
|
||||
override init(title: String) {
|
||||
super.init(title: title)
|
||||
delegate = self
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
func add(delegate: ProxyGroupMenuHighlightDelegate) {
|
||||
highlightDelegates.add(delegate)
|
||||
}
|
||||
|
||||
func remove(_ delegate: ProxyGroupMenuHighlightDelegate) {
|
||||
highlightDelegates.remove(delegate)
|
||||
}
|
||||
}
|
||||
|
||||
extension ProxyGroupMenu: NSMenuDelegate {
|
||||
func menuDidClose(_ menu: NSMenu) {
|
||||
highlightDelegates.allObjects.forEach { $0.highlight(item: nil) }
|
||||
}
|
||||
|
||||
func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) {
|
||||
highlightDelegates.allObjects.forEach { $0.highlight(item: item) }
|
||||
}
|
||||
}
|
|
@ -13,21 +13,29 @@ class ProxyGroupMenuItemView: MenuItemBaseView {
|
|||
let selectProxyLabel: NSTextField
|
||||
let arrowLabel = NSTextField(labelWithString: "▶")
|
||||
|
||||
override var labels: [NSTextField] {
|
||||
return [groupNameLabel, selectProxyLabel, arrowLabel]
|
||||
override var cells: [NSCell?] {
|
||||
return [groupNameLabel.cell, selectProxyLabel.cell, arrowLabel.cell]
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
set {}
|
||||
get {
|
||||
return enclosingMenuItem?.isHighlighted ?? false
|
||||
}
|
||||
}
|
||||
|
||||
init(group: ClashProxyName, targetProxy: ClashProxyName) {
|
||||
groupNameLabel = VibrancyTextField(labelWithString: group)
|
||||
selectProxyLabel = VibrancyTextField(labelWithString: targetProxy)
|
||||
super.init(handleClick: false, autolayout: true)
|
||||
super.init(autolayout: true)
|
||||
|
||||
// arrow
|
||||
effectView.addSubview(arrowLabel)
|
||||
arrowLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
arrowLabel.rightAnchor.constraint(equalTo: effectView.rightAnchor, constant: -10).isActive = true
|
||||
arrowLabel.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
||||
|
||||
arrowLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||
arrowLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
// group
|
||||
groupNameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
effectView.addSubview(groupNameLabel)
|
||||
|
|
|
@ -49,6 +49,12 @@ class ProxyGroupSpeedTestMenuItem: NSMenuItem {
|
|||
}
|
||||
}
|
||||
|
||||
extension ProxyGroupSpeedTestMenuItem: ProxyGroupMenuHighlightDelegate {
|
||||
func highlight(item: NSMenuItem?) {
|
||||
(view as? ProxyGroupSpeedTestMenuItemView)?.isHighlighted = item == self
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate class ProxyGroupSpeedTestMenuItemView: MenuItemBaseView {
|
||||
private let label: NSTextField
|
||||
|
||||
|
@ -57,7 +63,7 @@ fileprivate class ProxyGroupSpeedTestMenuItemView: MenuItemBaseView {
|
|||
label.font = type(of: self).labelFont
|
||||
label.sizeToFit()
|
||||
let rect = NSRect(x: 0, y: 0, width: label.bounds.width + 40, height: 20)
|
||||
super.init(frame: rect, handleClick: true, autolayout: false)
|
||||
super.init(frame: rect, autolayout: false)
|
||||
addSubview(label)
|
||||
label.frame = NSRect(x: 20, y: 0, width: label.bounds.width, height: 20)
|
||||
label.textColor = NSColor.labelColor
|
||||
|
@ -67,6 +73,10 @@ fileprivate class ProxyGroupSpeedTestMenuItemView: MenuItemBaseView {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var cells: [NSCell?] {
|
||||
return [label.cell]
|
||||
}
|
||||
|
||||
override var labels: [NSTextField] {
|
||||
return [label]
|
||||
}
|
||||
|
@ -121,12 +131,6 @@ fileprivate class ProxyGroupSpeedTestMenuItemView: MenuItemBaseView {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
super.draw(dirtyRect)
|
||||
label.textColor = (enclosingMenuItem?.isEnabled ?? true) ? NSColor.labelColor : NSColor.placeholderTextColor
|
||||
updateBackground(label)
|
||||
}
|
||||
}
|
||||
|
||||
extension ProxyGroupSpeedTestMenuItem {
|
||||
|
|
|
@ -11,42 +11,54 @@ import Cocoa
|
|||
class ProxyItemView: MenuItemBaseView {
|
||||
let nameLabel: NSTextField
|
||||
let delayLabel: NSTextField
|
||||
let imageView: NSImageView?
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
init(name: ClashProxyName, history: ClashProxySpeedHistory) {
|
||||
init(name: ClashProxyName, selected: Bool, delay: String?) {
|
||||
nameLabel = VibrancyTextField(labelWithString: name)
|
||||
delayLabel = VibrancyTextField(labelWithString: history.delayDisplay)
|
||||
super.init(handleClick: true, autolayout: true)
|
||||
delayLabel = VibrancyTextField(labelWithString: delay ?? " ")
|
||||
if selected {
|
||||
imageView = NSImageView(image: NSImage(named: NSImage.menuOnStateTemplateName)!)
|
||||
} else {
|
||||
imageView = nil
|
||||
}
|
||||
super.init(autolayout: true)
|
||||
effectView.addSubview(nameLabel)
|
||||
effectView.addSubview(delayLabel)
|
||||
if let imageView = imageView {
|
||||
effectView.addSubview(imageView)
|
||||
}
|
||||
|
||||
imageView?.translatesAutoresizingMaskIntoConstraints = false
|
||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
delayLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
delayLabel.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
||||
nameLabel.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
||||
imageView?.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
||||
|
||||
delayLabel.rightAnchor.constraint(equalTo: effectView.rightAnchor, constant: -15).isActive = true
|
||||
nameLabel.leftAnchor.constraint(equalTo: effectView.leftAnchor, constant: 20).isActive = true
|
||||
nameLabel.leftAnchor.constraint(equalTo: effectView.leftAnchor, constant: 25).isActive = true
|
||||
imageView?.leftAnchor.constraint(equalTo: effectView.leftAnchor, constant: 8).isActive = true
|
||||
|
||||
delayLabel.leftAnchor.constraint(greaterThanOrEqualTo: nameLabel.rightAnchor, constant: 30).isActive = true
|
||||
|
||||
nameLabel.font = type(of: self).labelFont
|
||||
delayLabel.font = NSFont.menuFont(ofSize: 13)
|
||||
delayLabel.font = NSFont.menuBarFont(ofSize: 12)
|
||||
}
|
||||
|
||||
override func didClickView() {
|
||||
enclosingMenuItem?.menu?.cancelTracking()
|
||||
func update(delay: String?) {
|
||||
delayLabel.stringValue = delay ?? " "
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var labels: [NSTextField] {
|
||||
return [nameLabel, delayLabel]
|
||||
override func didClickView() {
|
||||
(enclosingMenuItem as? ProxyMenuItem)?.didClick()
|
||||
}
|
||||
|
||||
override var cells: [NSCell?] {
|
||||
return [nameLabel.cell, delayLabel.cell, imageView?.cell]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,23 +10,17 @@ import Cocoa
|
|||
|
||||
class ProxyMenuItem: NSMenuItem {
|
||||
let proxyName: String
|
||||
var maxProxyNameLength: CGFloat
|
||||
|
||||
var isSelected: Bool = false {
|
||||
didSet {
|
||||
state = isSelected ? .on : .off
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
init(proxy: ClashProxy, action selector: Selector?, maxProxyNameLength: CGFloat) {
|
||||
self.maxProxyNameLength = maxProxyNameLength
|
||||
init(proxy: ClashProxy,
|
||||
action selector: Selector?,
|
||||
selected: Bool) {
|
||||
proxyName = proxy.name
|
||||
super.init(title: proxyName, action: selector, keyEquivalent: "")
|
||||
attributedTitle = getAttributedTitle(name: proxyName, delay: proxy.history.last?.delayDisplay)
|
||||
view = ProxyItemView(name: proxyName, selected: selected, delay: proxy.history.last?.delayDisplay)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateDelayNotification(note:)), name: kSpeedTestFinishForProxy, object: nil)
|
||||
}
|
||||
|
||||
|
@ -34,35 +28,11 @@ class ProxyMenuItem: NSMenuItem {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func getAttributedTitle(name: String, delay: String?) -> NSAttributedString {
|
||||
let paragraph = NSMutableParagraphStyle()
|
||||
paragraph.tabStops = [
|
||||
NSTextTab(textAlignment: .right, location: maxProxyNameLength + 90, options: [:]),
|
||||
]
|
||||
let proxyName = name.replacingOccurrences(of: "\t", with: " ")
|
||||
let str:String
|
||||
if let delay = delay {
|
||||
str = "\(proxyName)\t\(delay)"
|
||||
} else {
|
||||
str = proxyName.appending(" ")
|
||||
func didClick() {
|
||||
if let action = action {
|
||||
_ = target?.perform(action, with: self)
|
||||
}
|
||||
|
||||
let attributed = NSMutableAttributedString(
|
||||
string: str,
|
||||
attributes: [
|
||||
NSAttributedString.Key.paragraphStyle: paragraph,
|
||||
NSAttributedString.Key.font: NSFont.menuBarFont(ofSize: 14)
|
||||
]
|
||||
)
|
||||
|
||||
let hackAttr = [NSAttributedString.Key.font: NSFont.menuBarFont(ofSize: 15),]
|
||||
attributed.addAttributes(hackAttr, range: NSRange(name.utf16.count..<name.utf16.count + 1))
|
||||
|
||||
if delay != nil {
|
||||
let delayAttr = [NSAttributedString.Key.font: NSFont.menuBarFont(ofSize: 12),]
|
||||
attributed.addAttributes(delayAttr, range: NSRange(name.utf16.count + 1..<str.utf16.count))
|
||||
}
|
||||
return attributed
|
||||
menu?.cancelTracking()
|
||||
}
|
||||
|
||||
@objc private func updateDelayNotification(note: Notification) {
|
||||
|
@ -70,7 +40,13 @@ class ProxyMenuItem: NSMenuItem {
|
|||
return
|
||||
}
|
||||
if let delay = note.userInfo?["delay"] as? String {
|
||||
attributedTitle = getAttributedTitle(name: proxyName, delay: delay)
|
||||
(view as? ProxyItemView)?.update(delay: delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ProxyMenuItem: ProxyGroupMenuHighlightDelegate {
|
||||
func highlight(item: NSMenuItem?) {
|
||||
(view as? ProxyItemView)?.isHighlighted = item == self
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue