feat: adapt macOS 11 look
This commit is contained in:
parent
d9d225d0f8
commit
3e165db995
@ -955,6 +955,7 @@
|
||||
F9A7C06E2306E874007163C7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = x86_64;
|
||||
CODE_SIGN_ENTITLEMENTS = ProxyConfigHelper/com.west2online.ClashX.ProxyConfigHelper.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
@ -985,6 +986,7 @@
|
||||
F9A7C06F2306E874007163C7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = x86_64;
|
||||
CODE_SIGN_ENTITLEMENTS = ProxyConfigHelper/com.west2online.ClashX.ProxyConfigHelper.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
|
@ -687,6 +687,7 @@ extension AppDelegate {
|
||||
@IBAction func actionUpdateProxyGroupMenu(_ sender: Any) {
|
||||
ConfigManager.shared.disableShowCurrentProxyInMenu = !ConfigManager.shared.disableShowCurrentProxyInMenu
|
||||
updateExperimentalFeatureStatus()
|
||||
MenuItemFactory.recreateProxyMenuItems()
|
||||
}
|
||||
|
||||
@IBAction func actionSetBenchmarkUrl(_ sender: Any) {
|
||||
@ -806,6 +807,14 @@ extension AppDelegate {
|
||||
self?.syncConfig()
|
||||
}
|
||||
}
|
||||
|
||||
func hasMenuSelected() -> Bool {
|
||||
if #available(macOS 11, *) {
|
||||
return statusMenu.items.contains { $0.state == .on }
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: NSMenuDelegate
|
||||
@ -815,6 +824,9 @@ extension AppDelegate: NSMenuDelegate {
|
||||
MenuItemFactory.refreshExistingMenuItems()
|
||||
updateConfigFiles()
|
||||
syncConfig()
|
||||
NotificationCenter.default.post(name: .proxyMeneViewShowLeftPadding,
|
||||
object: nil,
|
||||
userInfo: ["show": hasMenuSelected()])
|
||||
}
|
||||
|
||||
func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) {
|
||||
@ -839,9 +851,9 @@ extension AppDelegate {
|
||||
}
|
||||
|
||||
guard let components = URLComponents(string: url),
|
||||
let scheme = components.scheme,
|
||||
scheme.hasPrefix("clash"),
|
||||
let host = components.host
|
||||
let scheme = components.scheme,
|
||||
scheme.hasPrefix("clash"),
|
||||
let host = components.host
|
||||
else { return }
|
||||
|
||||
if host == "install-config" {
|
||||
|
@ -45,13 +45,14 @@ class MenuItemFactory {
|
||||
}
|
||||
|
||||
static func refreshMenuItems(mergedData proxyInfo: ClashProxyResp?) {
|
||||
let leftPadding = AppDelegate.shared.hasMenuSelected()
|
||||
guard let proxyInfo = proxyInfo else { return }
|
||||
var menuItems = [NSMenuItem]()
|
||||
for proxy in proxyInfo.proxyGroups {
|
||||
var menu: NSMenuItem?
|
||||
switch proxy.type {
|
||||
case .select: menu = generateSelectorMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
|
||||
case .urltest, .fallback: menu = generateUrlTestFallBackMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
|
||||
case .select: menu = generateSelectorMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo, leftPadding: leftPadding)
|
||||
case .urltest, .fallback: menu = generateUrlTestFallBackMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo, leftPadding: leftPadding)
|
||||
case .loadBalance:
|
||||
menu = generateLoadBalanceMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
|
||||
case .relay:
|
||||
@ -106,7 +107,9 @@ class MenuItemFactory {
|
||||
// MARK: Generators
|
||||
|
||||
private static func generateSelectorMenuItem(proxyGroup: ClashProxy,
|
||||
proxyInfo: ClashProxyResp) -> NSMenuItem? {
|
||||
proxyInfo: ClashProxyResp,
|
||||
leftPadding: Bool) -> NSMenuItem?
|
||||
{
|
||||
let proxyMap = proxyInfo.proxiesMap
|
||||
|
||||
let isGlobalMode = ConfigManager.shared.currentConfig?.mode == .global
|
||||
@ -117,7 +120,7 @@ class MenuItemFactory {
|
||||
let menu = NSMenuItem(title: proxyGroup.name, action: nil, keyEquivalent: "")
|
||||
let selectedName = proxyGroup.now ?? ""
|
||||
if !ConfigManager.shared.disableShowCurrentProxyInMenu {
|
||||
menu.view = ProxyGroupMenuItemView(group: proxyGroup.name, targetProxy: selectedName)
|
||||
menu.view = ProxyGroupMenuItemView(group: proxyGroup.name, targetProxy: selectedName, hasLeftPadding: leftPadding)
|
||||
}
|
||||
let submenu = ProxyGroupMenu(title: proxyGroup.name)
|
||||
|
||||
@ -137,18 +140,18 @@ class MenuItemFactory {
|
||||
|
||||
addSpeedTestMenuItem(submenu, proxyGroup: proxyGroup)
|
||||
menu.submenu = submenu
|
||||
if !ConfigManager.shared.disableShowCurrentProxyInMenu {
|
||||
menu.view = ProxyGroupMenuItemView(group: proxyGroup.name, targetProxy: selectedName)
|
||||
}
|
||||
return menu
|
||||
}
|
||||
|
||||
private static func generateUrlTestFallBackMenuItem(proxyGroup: ClashProxy, proxyInfo: ClashProxyResp) -> NSMenuItem? {
|
||||
private static func generateUrlTestFallBackMenuItem(proxyGroup: ClashProxy,
|
||||
proxyInfo: ClashProxyResp,
|
||||
leftPadding: Bool) -> NSMenuItem?
|
||||
{
|
||||
let proxyMap = proxyInfo.proxiesMap
|
||||
let selectedName = proxyGroup.now ?? ""
|
||||
let menu = NSMenuItem(title: proxyGroup.name, action: nil, keyEquivalent: "")
|
||||
if !ConfigManager.shared.disableShowCurrentProxyInMenu {
|
||||
menu.view = ProxyGroupMenuItemView(group: proxyGroup.name, targetProxy: selectedName)
|
||||
menu.view = ProxyGroupMenuItemView(group: proxyGroup.name, targetProxy: selectedName, hasLeftPadding: leftPadding)
|
||||
}
|
||||
let submenu = NSMenu(title: proxyGroup.name)
|
||||
|
||||
|
@ -13,8 +13,10 @@ extension Notification.Name {
|
||||
static let reloadDashboard = Notification.Name("kReloadDashboard")
|
||||
static let systemNetworkStatusIPUpdate = Notification.Name("systemNetworkStatusIPUpdate")
|
||||
static let systemNetworkStatusDidChange = Notification.Name("kSystemNetworkStatusDidChange")
|
||||
static let proxyMeneViewShowLeftPadding = Notification.Name("kProxyMeneViewShowLeftPadding")
|
||||
|
||||
static func proxyUpdate(for name: ClashProxyName) -> Notification.Name {
|
||||
return Notification.Name("kProxyUpdate_\(name)")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,13 +15,7 @@ class MenuItemBaseView: NSView {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var isHighlighted: Bool = false {
|
||||
didSet {
|
||||
if #available(macOS 11, *), isHighlighted != oldValue {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
var isHighlighted: Bool = false
|
||||
|
||||
let effectView: NSVisualEffectView = {
|
||||
let effectView = NSVisualEffectView()
|
||||
@ -41,9 +35,22 @@ class MenuItemBaseView: NSView {
|
||||
return []
|
||||
}
|
||||
|
||||
static let labelFont: NSFont = NSFont.monospacedDigitSystemFont(ofSize: 14, weight: .regular)
|
||||
static let menuBarHeight: CGFloat = {
|
||||
if #available(macOS 11, *) {
|
||||
return 22
|
||||
} else {
|
||||
return 20
|
||||
}
|
||||
}()
|
||||
|
||||
init(frame frameRect: NSRect = NSRect(x: 0, y: 0, width: 0, height: 20), autolayout: Bool) {
|
||||
static let labelFont: NSFont = {
|
||||
if #available(macOS 11, *) {
|
||||
return NSFont.menuFont(ofSize: 0)
|
||||
}
|
||||
return NSFont.monospacedDigitSystemFont(ofSize: 14, weight: .regular)
|
||||
}()
|
||||
|
||||
init(frame frameRect: NSRect = NSRect(x: 0, y: 0, width: 0, height: menuBarHeight), autolayout: Bool) {
|
||||
self.autolayout = autolayout
|
||||
super.init(frame: frameRect)
|
||||
setupView()
|
||||
@ -65,13 +72,24 @@ class MenuItemBaseView: NSView {
|
||||
|
||||
private func setupView() {
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
heightAnchor.constraint(equalToConstant: type(of: self).menuBarHeight).isActive = true
|
||||
// background
|
||||
addSubview(effectView)
|
||||
effectView.translatesAutoresizingMaskIntoConstraints = false
|
||||
if #available(macOS 11, *) {
|
||||
effectView.wantsLayer = true
|
||||
effectView.layer?.cornerRadius = 3
|
||||
effectView.layer?.masksToBounds = true
|
||||
}
|
||||
if autolayout {
|
||||
effectView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
effectView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
let padding: CGFloat
|
||||
if #available(macOS 11, *) {
|
||||
padding = 5
|
||||
} else {
|
||||
padding = 0
|
||||
}
|
||||
effectView.leftAnchor.constraint(equalTo: leftAnchor, constant: padding).isActive = true
|
||||
effectView.rightAnchor.constraint(equalTo: rightAnchor, constant: -padding).isActive = true
|
||||
effectView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
effectView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
}
|
||||
@ -82,7 +100,11 @@ class MenuItemBaseView: NSView {
|
||||
override func layout() {
|
||||
super.layout()
|
||||
if !autolayout {
|
||||
effectView.frame = bounds
|
||||
if #available(macOS 11, *) {
|
||||
effectView.frame = CGRect(x: 5, y: 0, width: bounds.width - 10, height: bounds.height)
|
||||
} else {
|
||||
effectView.frame = bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,15 +9,29 @@
|
||||
import Cocoa
|
||||
|
||||
class ProxyGroupMenuItemView: MenuItemBaseView {
|
||||
let groupNameLabel: NSTextField
|
||||
let selectProxyLabel: NSTextField
|
||||
let arrowLabel = NSTextField(labelWithString: "▶")
|
||||
private let groupNameLabel: NSTextField
|
||||
private let selectProxyLabel: NSTextField
|
||||
private let arrowLabel: NSControl = {
|
||||
if #available(macOS 11, *) {
|
||||
let image = NSImage(named: NSImage.goForwardTemplateName)!.withSymbolConfiguration(NSImage.SymbolConfiguration(pointSize: 14, weight: .bold, scale: .small))!
|
||||
return NSImageView(image: image)
|
||||
} else {
|
||||
let label = NSTextField(labelWithString: "▶")
|
||||
label.setContentHuggingPriority(.required, for: .horizontal)
|
||||
label.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
label.textColor = NSColor.labelColor
|
||||
return label
|
||||
}
|
||||
}()
|
||||
|
||||
private var leftPaddingConstraint: NSLayoutConstraint?
|
||||
private let leftPadding: CGFloat = 20
|
||||
|
||||
override var cells: [NSCell?] {
|
||||
return [groupNameLabel.cell, selectProxyLabel.cell, arrowLabel.cell]
|
||||
}
|
||||
|
||||
init(group: ClashProxyName, targetProxy: ClashProxyName) {
|
||||
init(group: ClashProxyName, targetProxy: ClashProxyName, hasLeftPadding: Bool) {
|
||||
groupNameLabel = VibrancyTextField(labelWithString: group)
|
||||
selectProxyLabel = VibrancyTextField(labelWithString: targetProxy)
|
||||
super.init(autolayout: true)
|
||||
@ -25,14 +39,20 @@ class ProxyGroupMenuItemView: MenuItemBaseView {
|
||||
// arrow
|
||||
effectView.addSubview(arrowLabel)
|
||||
arrowLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
arrowLabel.rightAnchor.constraint(equalTo: effectView.rightAnchor, constant: -10).isActive = true
|
||||
let rightConstraint: CGFloat
|
||||
if #available(macOS 11, *) {
|
||||
rightConstraint = -8
|
||||
} else {
|
||||
rightConstraint = -10
|
||||
}
|
||||
arrowLabel.rightAnchor.constraint(equalTo: effectView.rightAnchor, constant: rightConstraint).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)
|
||||
groupNameLabel.leftAnchor.constraint(equalTo: effectView.leftAnchor, constant: 20).isActive = true
|
||||
leftPaddingConstraint = groupNameLabel.leftAnchor.constraint(equalTo: effectView.leftAnchor, constant: leftPadding)
|
||||
leftPaddingConstraint?.isActive = true
|
||||
groupNameLabel.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
||||
groupNameLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
|
||||
@ -53,10 +73,21 @@ class ProxyGroupMenuItemView: MenuItemBaseView {
|
||||
selectProxyLabel.font = type(of: self).labelFont
|
||||
groupNameLabel.textColor = NSColor.labelColor
|
||||
selectProxyLabel.textColor = NSColor.secondaryLabelColor
|
||||
arrowLabel.textColor = NSColor.labelColor
|
||||
|
||||
// noti
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(proxyInfoDidUpdate(note:)), name: .proxyUpdate(for: group), object: nil)
|
||||
|
||||
if #available(macOS 11, *) {
|
||||
updateLeftMenuPadding(show: hasLeftPadding)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(showLeftPaddingUpdate(note:)), name: .proxyMeneViewShowLeftPadding, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateLeftMenuPadding(show: Bool) {
|
||||
if show {
|
||||
leftPaddingConstraint?.constant = leftPadding
|
||||
} else {
|
||||
leftPaddingConstraint?.constant = 10
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -71,6 +102,11 @@ class ProxyGroupMenuItemView: MenuItemBaseView {
|
||||
guard let info = note.object as? ClashProxy else { assertionFailure(); return }
|
||||
selectProxyLabel.stringValue = info.now ?? ""
|
||||
}
|
||||
|
||||
@objc private func showLeftPaddingUpdate(note: NSNotification) {
|
||||
guard let show = note.userInfo?["show"] as? Bool else { assertionFailure(); return }
|
||||
updateLeftMenuPadding(show: show)
|
||||
}
|
||||
}
|
||||
|
||||
extension ProxyGroupMenuItemView: ProxyGroupMenuHighlightDelegate {
|
||||
|
@ -20,7 +20,11 @@ class ProxyItemView: MenuItemBaseView {
|
||||
delayLabel = VibrancyTextField(labelWithString: "").setup(allowsVibrancy: false)
|
||||
let cell = PaddedNSTextFieldCell()
|
||||
cell.widthPadding = 2
|
||||
cell.heightPadding = 1
|
||||
if #available(macOS 11, *) {
|
||||
cell.heightPadding = 2
|
||||
} else {
|
||||
cell.heightPadding = 1
|
||||
}
|
||||
delayLabel.cell = cell
|
||||
super.init(autolayout: false)
|
||||
effectView.addSubview(nameLabel)
|
||||
@ -30,7 +34,11 @@ class ProxyItemView: MenuItemBaseView {
|
||||
delayLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
nameLabel.font = type(of: self).labelFont
|
||||
delayLabel.font = NSFont.monospacedDigitSystemFont(ofSize: 10, weight: .medium)
|
||||
if #available(macOS 11, *) {
|
||||
delayLabel.font = NSFont.monospacedDigitSystemFont(ofSize: 9, weight: .medium)
|
||||
} else {
|
||||
delayLabel.font = NSFont.monospacedDigitSystemFont(ofSize: 10, weight: .medium)
|
||||
}
|
||||
nameLabel.alignment = .left
|
||||
delayLabel.alignment = .right
|
||||
|
||||
@ -45,13 +53,13 @@ class ProxyItemView: MenuItemBaseView {
|
||||
super.layout()
|
||||
nameLabel.sizeToFit()
|
||||
delayLabel.sizeToFit()
|
||||
imageView?.frame = CGRect(x: 5, y: bounds.height / 2 - 6, width: 12, height: 12)
|
||||
imageView?.frame = CGRect(x: 5, y: effectView.bounds.height / 2 - 6, width: 12, height: 12)
|
||||
nameLabel.frame = CGRect(x: 18,
|
||||
y: (bounds.height - nameLabel.bounds.height) / 2,
|
||||
y: (effectView.bounds.height - nameLabel.bounds.height) / 2,
|
||||
width: nameLabel.bounds.width,
|
||||
height: nameLabel.bounds.height)
|
||||
delayLabel.frame = CGRect(x: bounds.width - delayLabel.bounds.width - 8,
|
||||
y: (bounds.height - delayLabel.bounds.height) / 2,
|
||||
delayLabel.frame = CGRect(x: effectView.bounds.width - delayLabel.bounds.width - 8,
|
||||
y: (effectView.bounds.height - delayLabel.bounds.height) / 2,
|
||||
width: delayLabel.bounds.width,
|
||||
height: delayLabel.bounds.height)
|
||||
}
|
||||
@ -77,7 +85,13 @@ class ProxyItemView: MenuItemBaseView {
|
||||
func update(selected: Bool) {
|
||||
if selected {
|
||||
if imageView == nil {
|
||||
imageView = NSImageView(image: NSImage(named: NSImage.menuOnStateTemplateName)!)
|
||||
let image: NSImage
|
||||
if #available(OSX 11.0, *) {
|
||||
image = NSImage(named: NSImage.menuOnStateTemplateName)!.withSymbolConfiguration(NSImage.SymbolConfiguration(pointSize: 13, weight: .bold, scale: .small))!
|
||||
} else {
|
||||
image = NSImage(named: NSImage.menuOnStateTemplateName)!
|
||||
}
|
||||
imageView = NSImageView(image: image)
|
||||
imageView?.translatesAutoresizingMaskIntoConstraints = false
|
||||
effectView.addSubview(imageView!)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user