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 */; };
|
49D176A72355FE680093DD7B /* NetworkChangeNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176A62355FE680093DD7B /* NetworkChangeNotifier.swift */; };
|
||||||
49D176A9235614340093DD7B /* ProxyGroupSpeedTestMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176A8235614340093DD7B /* ProxyGroupSpeedTestMenuItem.swift */; };
|
49D176A9235614340093DD7B /* ProxyGroupSpeedTestMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176A8235614340093DD7B /* ProxyGroupSpeedTestMenuItem.swift */; };
|
||||||
49D176AB23575BB20093DD7B /* ProxyGroupMenuItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176AA23575BB20093DD7B /* ProxyGroupMenuItemView.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 */; };
|
F915A4622366ADEF004840BE /* ClashConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F915A4612366ADEF004840BE /* ClashConnection.swift */; };
|
||||||
F9203A26236342820020D57D /* AppDelegate+..swift in Sources */ = {isa = PBXBuildFile; fileRef = F9203A25236342820020D57D /* AppDelegate+..swift */; };
|
F9203A26236342820020D57D /* AppDelegate+..swift in Sources */ = {isa = PBXBuildFile; fileRef = F9203A25236342820020D57D /* AppDelegate+..swift */; };
|
||||||
F92D0B24236BC12000575E15 /* SavedProxyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92D0B23236BC12000575E15 /* SavedProxyModel.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
F92D0B23236BC12000575E15 /* SavedProxyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedProxyModel.swift; sourceTree = "<group>"; };
|
||||||
|
@ -301,6 +303,7 @@
|
||||||
499A485922ED781100F6C675 /* RemoteConfigAddView.xib */,
|
499A485922ED781100F6C675 /* RemoteConfigAddView.xib */,
|
||||||
495340B220DE68C300B0D3FF /* StatusItemView.swift */,
|
495340B220DE68C300B0D3FF /* StatusItemView.swift */,
|
||||||
495340AF20DE5F7200B0D3FF /* StatusItemView.xib */,
|
495340AF20DE5F7200B0D3FF /* StatusItemView.xib */,
|
||||||
|
F910AA23240134AF00116E95 /* ProxyGroupMenu.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -668,6 +671,7 @@
|
||||||
499A486522EEA3FD00F6C675 /* Array+Safe.swift in Sources */,
|
499A486522EEA3FD00F6C675 /* Array+Safe.swift in Sources */,
|
||||||
F92D0B24236BC12000575E15 /* SavedProxyModel.swift in Sources */,
|
F92D0B24236BC12000575E15 /* SavedProxyModel.swift in Sources */,
|
||||||
F92D0B2A236C759100575E15 /* NSTextField+Vibrancy.swift in Sources */,
|
F92D0B2A236C759100575E15 /* NSTextField+Vibrancy.swift in Sources */,
|
||||||
|
F910AA24240134AF00116E95 /* ProxyGroupMenu.swift in Sources */,
|
||||||
4952C3BF2115C7CA004A4FA8 /* MenuItemFactory.swift in Sources */,
|
4952C3BF2115C7CA004A4FA8 /* MenuItemFactory.swift in Sources */,
|
||||||
F977FAAC2366790500C17F1F /* AutoUpgardeManager.swift in Sources */,
|
F977FAAC2366790500C17F1F /* AutoUpgardeManager.swift in Sources */,
|
||||||
499A485822ED715200F6C675 /* RemoteConfigModel.swift in Sources */,
|
499A485822ED715200F6C675 /* RemoteConfigModel.swift in Sources */,
|
||||||
|
|
|
@ -88,8 +88,7 @@ class MenuItemFactory {
|
||||||
if !ConfigManager.shared.disableShowCurrentProxyInMenu {
|
if !ConfigManager.shared.disableShowCurrentProxyInMenu {
|
||||||
menu.view = ProxyGroupMenuItemView(group: proxyGroup.name, targetProxy: selectedName)
|
menu.view = ProxyGroupMenuItemView(group: proxyGroup.name, targetProxy: selectedName)
|
||||||
}
|
}
|
||||||
let submenu = NSMenu(title: proxyGroup.name)
|
let submenu = ProxyGroupMenu(title: proxyGroup.name)
|
||||||
var hasSelected = false
|
|
||||||
|
|
||||||
for proxy in proxyGroup.all ?? [] {
|
for proxy in proxyGroup.all ?? [] {
|
||||||
guard let proxyModel = proxyMap[proxy] else { continue }
|
guard let proxyModel = proxyMap[proxy] else { continue }
|
||||||
|
@ -98,17 +97,11 @@ class MenuItemFactory {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let proxyItem = ProxyMenuItem(proxy: proxyModel, action: #selector(MenuItemFactory.actionSelectProxy(sender:)),
|
let proxyItem = ProxyMenuItem(proxy: proxyModel, action: #selector(MenuItemFactory.actionSelectProxy(sender:)),
|
||||||
maxProxyNameLength: proxyGroup.maxProxyNameLength)
|
selected: proxy == selectedName)
|
||||||
proxyItem.target = MenuItemFactory.self
|
proxyItem.target = MenuItemFactory.self
|
||||||
proxyItem.isSelected = proxy == selectedName
|
submenu.add(delegate: proxyItem)
|
||||||
|
|
||||||
if proxyItem.isSelected { hasSelected = true }
|
|
||||||
submenu.addItem(proxyItem)
|
submenu.addItem(proxyItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasSelected && submenu.items.count > 0 {
|
|
||||||
actionSelectProxy(sender: submenu.items[0] as! ProxyMenuItem)
|
|
||||||
}
|
|
||||||
addSpeedTestMenuItem(submenu, proxyGroup: proxyGroup)
|
addSpeedTestMenuItem(submenu, proxyGroup: proxyGroup)
|
||||||
menu.submenu = submenu
|
menu.submenu = submenu
|
||||||
if !ConfigManager.shared.disableShowCurrentProxyInMenu {
|
if !ConfigManager.shared.disableShowCurrentProxyInMenu {
|
||||||
|
@ -145,17 +138,18 @@ class MenuItemFactory {
|
||||||
return menu
|
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 }
|
guard proxyGroup.speedtestAble.count > 0 else { return }
|
||||||
let speedTestItem = ProxyGroupSpeedTestMenuItem(group: proxyGroup)
|
let speedTestItem = ProxyGroupSpeedTestMenuItem(group: proxyGroup)
|
||||||
let separator = NSMenuItem.separator()
|
let separator = NSMenuItem.separator()
|
||||||
if showSpeedTestItemAtTop {
|
if showSpeedTestItemAtTop {
|
||||||
menus.insertItem(separator, at: 0)
|
menu.insertItem(separator, at: 0)
|
||||||
menus.insertItem(speedTestItem, at: 0)
|
menu.insertItem(speedTestItem, at: 0)
|
||||||
} else {
|
} else {
|
||||||
menus.addItem(separator)
|
menu.addItem(separator)
|
||||||
menus.addItem(speedTestItem)
|
menu.addItem(speedTestItem)
|
||||||
}
|
}
|
||||||
|
(menu as? ProxyGroupMenu)?.add(delegate: speedTestItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func generateHistoryMenu(_ proxy: ClashProxy) -> NSMenu? {
|
private static func generateHistoryMenu(_ proxy: ClashProxy) -> NSMenu? {
|
||||||
|
@ -171,15 +165,15 @@ class MenuItemFactory {
|
||||||
let proxyMap = proxyInfo.proxiesMap
|
let proxyMap = proxyInfo.proxiesMap
|
||||||
|
|
||||||
let menu = NSMenuItem(title: proxyGroup.name, action: nil, keyEquivalent: "")
|
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 ?? [] {
|
for proxy in proxyGroup.all ?? [] {
|
||||||
guard let proxyModel = proxyMap[proxy] else { continue }
|
guard let proxyModel = proxyMap[proxy] else { continue }
|
||||||
let proxyItem = ProxyMenuItem(proxy: proxyModel,
|
let proxyItem = ProxyMenuItem(proxy: proxyModel,
|
||||||
action: #selector(empty),
|
action: #selector(empty),
|
||||||
maxProxyNameLength: proxyGroup.maxProxyNameLength)
|
selected: false)
|
||||||
proxyItem.isSelected = false
|
|
||||||
proxyItem.target = MenuItemFactory.self
|
proxyItem.target = MenuItemFactory.self
|
||||||
|
submenu.add(delegate: proxyItem)
|
||||||
submenu.addItem(proxyItem)
|
submenu.addItem(proxyItem)
|
||||||
}
|
}
|
||||||
addSpeedTestMenuItem(submenu, proxyGroup: proxyGroup)
|
addSpeedTestMenuItem(submenu, proxyGroup: proxyGroup)
|
||||||
|
|
|
@ -99,19 +99,6 @@ class ClashProxy: Codable {
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case type, all, history, now, name
|
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 {
|
class ClashProxyResp {
|
||||||
|
|
|
@ -6,22 +6,16 @@
|
||||||
// Copyright © 2019 west2online. All rights reserved.
|
// Copyright © 2019 west2online. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Carbon
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class MenuItemBaseView: NSView {
|
class MenuItemBaseView: NSView {
|
||||||
private var isMouseInsideView = false
|
private var isMouseInsideView = false
|
||||||
private var isMenuOpen = false
|
private var isMenuOpen = false
|
||||||
private var eventHandler: EventHandlerRef?
|
|
||||||
private let handleClick: Bool
|
|
||||||
private let autolayout: Bool
|
private let autolayout: Bool
|
||||||
|
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
|
|
||||||
var isHighlighted: Bool {
|
var isHighlighted: Bool = false
|
||||||
let enable = enclosingMenuItem?.isEnabled ?? true
|
|
||||||
return (isMouseInsideView || isMenuOpen) && enable
|
|
||||||
}
|
|
||||||
|
|
||||||
let effectView: NSVisualEffectView = {
|
let effectView: NSVisualEffectView = {
|
||||||
let effectView = NSVisualEffectView()
|
let effectView = NSVisualEffectView()
|
||||||
|
@ -32,17 +26,18 @@ class MenuItemBaseView: NSView {
|
||||||
return effectView
|
return effectView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var labels: [NSTextField] {
|
var cells: [NSCell?] {
|
||||||
assertionFailure("Please override")
|
assertionFailure("Please override")
|
||||||
return []
|
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),
|
static let labelFont = NSFont.menuBarFont(ofSize: 0)
|
||||||
handleClick: Bool,
|
|
||||||
autolayout: Bool) {
|
init(frame frameRect: NSRect = NSRect(x: 0, y: 0, width: 0, height: 20), autolayout: Bool) {
|
||||||
self.handleClick = handleClick
|
|
||||||
self.autolayout = autolayout
|
self.autolayout = autolayout
|
||||||
super.init(frame: frameRect)
|
super.init(frame: frameRect)
|
||||||
setupView()
|
setupView()
|
||||||
|
@ -60,10 +55,6 @@ class MenuItemBaseView: NSView {
|
||||||
assertionFailure("Please override this method")
|
assertionFailure("Please override this method")
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateBackground(_ label: NSTextField) {
|
|
||||||
label.cell?.backgroundStyle = isHighlighted ? .emphasized : .normal
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private func setupView() {
|
private func setupView() {
|
||||||
|
@ -82,21 +73,19 @@ class MenuItemBaseView: NSView {
|
||||||
|
|
||||||
override func draw(_ dirtyRect: NSRect) {
|
override func draw(_ dirtyRect: NSRect) {
|
||||||
super.draw(dirtyRect)
|
super.draw(dirtyRect)
|
||||||
effectView.material = isHighlighted ? .selection : .popover
|
labels.forEach { $0.textColor = (enclosingMenuItem?.isEnabled ?? true) ? NSColor.labelColor : NSColor.placeholderTextColor }
|
||||||
labels.forEach { updateBackground($0) }
|
let highlighted = isHighlighted && (enclosingMenuItem?.isEnabled ?? false)
|
||||||
|
effectView.material = highlighted ? .selection : .popover
|
||||||
|
cells.forEach { $0?.backgroundStyle = isHighlighted ? .emphasized : .normal }
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillMove(toWindow newWindow: NSWindow?) {
|
override func viewWillMove(toWindow newWindow: NSWindow?) {
|
||||||
super.viewWillMove(toWindow: newWindow)
|
super.viewWillMove(toWindow: newWindow)
|
||||||
if let newWindow = newWindow,!newWindow.isKeyWindow {
|
if let newWindow = newWindow, !newWindow.isKeyWindow {
|
||||||
newWindow.becomeKey()
|
newWindow.becomeKey()
|
||||||
}
|
}
|
||||||
updateTrackingAreas()
|
updateTrackingAreas()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidMoveToWindow() {
|
|
||||||
super.viewDidMoveToWindow()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidMoveToSuperview() {
|
override func viewDidMoveToSuperview() {
|
||||||
super.viewDidMoveToSuperview()
|
super.viewDidMoveToSuperview()
|
||||||
|
@ -108,38 +97,13 @@ class MenuItemBaseView: NSView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func updateTrackingAreas() {
|
||||||
|
super.updateTrackingAreas()
|
||||||
|
}
|
||||||
|
|
||||||
override func mouseUp(with event: NSEvent) {
|
override func mouseUp(with event: NSEvent) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.didClickView()
|
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 selectProxyLabel: NSTextField
|
||||||
let arrowLabel = NSTextField(labelWithString: "▶")
|
let arrowLabel = NSTextField(labelWithString: "▶")
|
||||||
|
|
||||||
override var labels: [NSTextField] {
|
override var cells: [NSCell?] {
|
||||||
return [groupNameLabel, selectProxyLabel, arrowLabel]
|
return [groupNameLabel.cell, selectProxyLabel.cell, arrowLabel.cell]
|
||||||
|
}
|
||||||
|
|
||||||
|
override var isHighlighted: Bool {
|
||||||
|
set {}
|
||||||
|
get {
|
||||||
|
return enclosingMenuItem?.isHighlighted ?? false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(group: ClashProxyName, targetProxy: ClashProxyName) {
|
init(group: ClashProxyName, targetProxy: ClashProxyName) {
|
||||||
groupNameLabel = VibrancyTextField(labelWithString: group)
|
groupNameLabel = VibrancyTextField(labelWithString: group)
|
||||||
selectProxyLabel = VibrancyTextField(labelWithString: targetProxy)
|
selectProxyLabel = VibrancyTextField(labelWithString: targetProxy)
|
||||||
super.init(handleClick: false, autolayout: true)
|
super.init(autolayout: true)
|
||||||
|
|
||||||
// arrow
|
// arrow
|
||||||
effectView.addSubview(arrowLabel)
|
effectView.addSubview(arrowLabel)
|
||||||
arrowLabel.translatesAutoresizingMaskIntoConstraints = false
|
arrowLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
arrowLabel.rightAnchor.constraint(equalTo: effectView.rightAnchor, constant: -10).isActive = true
|
arrowLabel.rightAnchor.constraint(equalTo: effectView.rightAnchor, constant: -10).isActive = true
|
||||||
arrowLabel.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
arrowLabel.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
||||||
|
arrowLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
arrowLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
// group
|
// group
|
||||||
groupNameLabel.translatesAutoresizingMaskIntoConstraints = false
|
groupNameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
effectView.addSubview(groupNameLabel)
|
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 {
|
fileprivate class ProxyGroupSpeedTestMenuItemView: MenuItemBaseView {
|
||||||
private let label: NSTextField
|
private let label: NSTextField
|
||||||
|
|
||||||
|
@ -57,7 +63,7 @@ fileprivate class ProxyGroupSpeedTestMenuItemView: MenuItemBaseView {
|
||||||
label.font = type(of: self).labelFont
|
label.font = type(of: self).labelFont
|
||||||
label.sizeToFit()
|
label.sizeToFit()
|
||||||
let rect = NSRect(x: 0, y: 0, width: label.bounds.width + 40, height: 20)
|
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)
|
addSubview(label)
|
||||||
label.frame = NSRect(x: 20, y: 0, width: label.bounds.width, height: 20)
|
label.frame = NSRect(x: 20, y: 0, width: label.bounds.width, height: 20)
|
||||||
label.textColor = NSColor.labelColor
|
label.textColor = NSColor.labelColor
|
||||||
|
@ -67,6 +73,10 @@ fileprivate class ProxyGroupSpeedTestMenuItemView: MenuItemBaseView {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var cells: [NSCell?] {
|
||||||
|
return [label.cell]
|
||||||
|
}
|
||||||
|
|
||||||
override var labels: [NSTextField] {
|
override var labels: [NSTextField] {
|
||||||
return [label]
|
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 {
|
extension ProxyGroupSpeedTestMenuItem {
|
||||||
|
|
|
@ -11,42 +11,54 @@ import Cocoa
|
||||||
class ProxyItemView: MenuItemBaseView {
|
class ProxyItemView: MenuItemBaseView {
|
||||||
let nameLabel: NSTextField
|
let nameLabel: NSTextField
|
||||||
let delayLabel: NSTextField
|
let delayLabel: NSTextField
|
||||||
|
let imageView: NSImageView?
|
||||||
|
|
||||||
deinit {
|
init(name: ClashProxyName, selected: Bool, delay: String?) {
|
||||||
NotificationCenter.default.removeObserver(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(name: ClashProxyName, history: ClashProxySpeedHistory) {
|
|
||||||
nameLabel = VibrancyTextField(labelWithString: name)
|
nameLabel = VibrancyTextField(labelWithString: name)
|
||||||
delayLabel = VibrancyTextField(labelWithString: history.delayDisplay)
|
delayLabel = VibrancyTextField(labelWithString: delay ?? " ")
|
||||||
super.init(handleClick: true, autolayout: true)
|
if selected {
|
||||||
|
imageView = NSImageView(image: NSImage(named: NSImage.menuOnStateTemplateName)!)
|
||||||
|
} else {
|
||||||
|
imageView = nil
|
||||||
|
}
|
||||||
|
super.init(autolayout: true)
|
||||||
effectView.addSubview(nameLabel)
|
effectView.addSubview(nameLabel)
|
||||||
effectView.addSubview(delayLabel)
|
effectView.addSubview(delayLabel)
|
||||||
|
if let imageView = imageView {
|
||||||
|
effectView.addSubview(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageView?.translatesAutoresizingMaskIntoConstraints = false
|
||||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
delayLabel.translatesAutoresizingMaskIntoConstraints = false
|
delayLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
delayLabel.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
delayLabel.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
||||||
nameLabel.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
|
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
|
delayLabel.leftAnchor.constraint(greaterThanOrEqualTo: nameLabel.rightAnchor, constant: 30).isActive = true
|
||||||
|
|
||||||
nameLabel.font = type(of: self).labelFont
|
nameLabel.font = type(of: self).labelFont
|
||||||
delayLabel.font = NSFont.menuFont(ofSize: 13)
|
delayLabel.font = NSFont.menuBarFont(ofSize: 12)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didClickView() {
|
func update(delay: String?) {
|
||||||
enclosingMenuItem?.menu?.cancelTracking()
|
delayLabel.stringValue = delay ?? " "
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override var labels: [NSTextField] {
|
override func didClickView() {
|
||||||
return [nameLabel, delayLabel]
|
(enclosingMenuItem as? ProxyMenuItem)?.didClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
override var cells: [NSCell?] {
|
||||||
|
return [nameLabel.cell, delayLabel.cell, imageView?.cell]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,23 +10,17 @@ import Cocoa
|
||||||
|
|
||||||
class ProxyMenuItem: NSMenuItem {
|
class ProxyMenuItem: NSMenuItem {
|
||||||
let proxyName: String
|
let proxyName: String
|
||||||
var maxProxyNameLength: CGFloat
|
|
||||||
|
|
||||||
var isSelected: Bool = false {
|
|
||||||
didSet {
|
|
||||||
state = isSelected ? .on : .off
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
NotificationCenter.default.removeObserver(self)
|
NotificationCenter.default.removeObserver(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(proxy: ClashProxy, action selector: Selector?, maxProxyNameLength: CGFloat) {
|
init(proxy: ClashProxy,
|
||||||
self.maxProxyNameLength = maxProxyNameLength
|
action selector: Selector?,
|
||||||
|
selected: Bool) {
|
||||||
proxyName = proxy.name
|
proxyName = proxy.name
|
||||||
super.init(title: proxyName, action: selector, keyEquivalent: "")
|
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)
|
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")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAttributedTitle(name: String, delay: String?) -> NSAttributedString {
|
func didClick() {
|
||||||
let paragraph = NSMutableParagraphStyle()
|
if let action = action {
|
||||||
paragraph.tabStops = [
|
_ = target?.perform(action, with: self)
|
||||||
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(" ")
|
|
||||||
}
|
}
|
||||||
|
menu?.cancelTracking()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func updateDelayNotification(note: Notification) {
|
@objc private func updateDelayNotification(note: Notification) {
|
||||||
|
@ -70,7 +40,13 @@ class ProxyMenuItem: NSMenuItem {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let delay = note.userInfo?["delay"] as? String {
|
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