Refactor menuitem views
This commit is contained in:
parent
9ad3565242
commit
c2ba79c543
@ -58,6 +58,7 @@
|
||||
F9203A26236342820020D57D /* AppDelegate+..swift in Sources */ = {isa = PBXBuildFile; fileRef = F9203A25236342820020D57D /* AppDelegate+..swift */; };
|
||||
F92D0B24236BC12000575E15 /* SavedProxyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92D0B23236BC12000575E15 /* SavedProxyModel.swift */; };
|
||||
F92D0B2A236C759100575E15 /* NSTextField+Vibrancy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92D0B29236C759100575E15 /* NSTextField+Vibrancy.swift */; };
|
||||
F92D0B2C236C7C3600575E15 /* MenuItemBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92D0B2B236C7C3600575E15 /* MenuItemBaseView.swift */; };
|
||||
F935B2F02307C52E009E4D33 /* com.west2online.ClashX.ProxyConfigHelper in Copy Files */ = {isa = PBXBuildFile; fileRef = F9A7C0692306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
F935B2F42307CD32009E4D33 /* ProxyConfigHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = F935B2F32307CD32009E4D33 /* ProxyConfigHelper.m */; };
|
||||
F935B2FA23083EE6009E4D33 /* ProxySettingTool.m in Sources */ = {isa = PBXBuildFile; fileRef = F935B2F923083EE6009E4D33 /* ProxySettingTool.m */; };
|
||||
@ -166,6 +167,7 @@
|
||||
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>"; };
|
||||
F92D0B29236C759100575E15 /* NSTextField+Vibrancy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextField+Vibrancy.swift"; sourceTree = "<group>"; };
|
||||
F92D0B2B236C7C3600575E15 /* MenuItemBaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemBaseView.swift; sourceTree = "<group>"; };
|
||||
F935B2EA2307B6BA009E4D33 /* Helper-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Helper-Info.plist"; sourceTree = "<group>"; };
|
||||
F935B2F12307C802009E4D33 /* Helper-Launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Helper-Launchd.plist"; sourceTree = "<group>"; };
|
||||
F935B2F22307CD32009E4D33 /* ProxyConfigHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProxyConfigHelper.h; sourceTree = "<group>"; };
|
||||
@ -281,6 +283,7 @@
|
||||
499A485922ED781100F6C675 /* RemoteConfigAddView.xib */,
|
||||
49D176A8235614340093DD7B /* ProxyGroupSpeedTestMenuItem.swift */,
|
||||
49D176AA23575BB20093DD7B /* ProxyGroupMenuItemView.swift */,
|
||||
F92D0B2B236C7C3600575E15 /* MenuItemBaseView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@ -638,6 +641,7 @@
|
||||
F976275C23634DF8000EDEFE /* LoginServiceKit.swift in Sources */,
|
||||
4966E9E6211824F300A391FB /* NSImage+extension.swift in Sources */,
|
||||
49B1086A216A356D0064FFCE /* String+Extension.swift in Sources */,
|
||||
F92D0B2C236C7C3600575E15 /* MenuItemBaseView.swift in Sources */,
|
||||
4960A6DB2136529200B940C9 /* JSBridgeHandler.swift in Sources */,
|
||||
493AEAE5221AE7230016FE98 /* ProxyMenuItem.swift in Sources */,
|
||||
499A485E22ED9B7C00F6C675 /* NSTableView+Reload.swift in Sources */,
|
||||
|
161
ClashX/Views/MenuItemBaseView.swift
Normal file
161
ClashX/Views/MenuItemBaseView.swift
Normal file
@ -0,0 +1,161 @@
|
||||
//
|
||||
// MenuItemBaseView.swift
|
||||
// ClashX
|
||||
//
|
||||
// Created by yicheng on 2019/11/1.
|
||||
// Copyright © 2019 west2online. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Carbon
|
||||
|
||||
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 {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
return isMouseInsideView || isMenuOpen
|
||||
} else {
|
||||
return enclosingMenuItem?.isHighlighted ?? false
|
||||
}
|
||||
}
|
||||
|
||||
let effectView: NSVisualEffectView = {
|
||||
let effectView = NSVisualEffectView()
|
||||
effectView.material = .popover
|
||||
effectView.state = .active
|
||||
effectView.isEmphasized = true
|
||||
effectView.blendingMode = .behindWindow
|
||||
return effectView
|
||||
}()
|
||||
|
||||
static let labelFont = NSFont.menuFont(ofSize: 14)
|
||||
|
||||
init(frame frameRect: NSRect = .zero, handleClick:Bool, autolayout:Bool) {
|
||||
self.handleClick = handleClick
|
||||
self.autolayout = autolayout
|
||||
super.init(frame: frameRect)
|
||||
setupView()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setNeedsDisplay() {
|
||||
setNeedsDisplay(bounds)
|
||||
}
|
||||
|
||||
func didClickView() {
|
||||
assertionFailure("Please override this method")
|
||||
}
|
||||
|
||||
func updateBackground(_ label: NSTextField) {
|
||||
label.cell?.backgroundStyle = isHighlighted ? .emphasized : .normal
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
private func setupView() {
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
// background
|
||||
addSubview(effectView)
|
||||
effectView.translatesAutoresizingMaskIntoConstraints = false
|
||||
effectView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
effectView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
effectView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
effectView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
|
||||
}
|
||||
|
||||
private func updateCarbon() {
|
||||
if window != nil {
|
||||
if let dispatcher = GetEventDispatcherTarget() {
|
||||
let eventHandlerCallback: EventHandlerUPP = { eventHandlerCallRef, eventRef, userData in
|
||||
guard let userData = userData else { return 0 }
|
||||
let itemView: MenuItemBaseView = bridge(ptr: userData)
|
||||
itemView.didClickView()
|
||||
return 0
|
||||
}
|
||||
|
||||
let eventSpecs = [EventTypeSpec(eventClass: OSType(kEventClassMouse), eventKind: UInt32(kEventMouseUp))]
|
||||
|
||||
InstallEventHandler(dispatcher, eventHandlerCallback, 1, eventSpecs, bridge(obj: self), &eventHandler)
|
||||
}
|
||||
} else {
|
||||
RemoveEventHandler(eventHandler)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: Override
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
super.draw(dirtyRect)
|
||||
effectView.material = isHighlighted ? .selection : .popover
|
||||
}
|
||||
|
||||
override func viewDidMoveToWindow() {
|
||||
super.viewDidMoveToWindow()
|
||||
if handleClick {
|
||||
updateCarbon()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidMoveToSuperview() {
|
||||
super.viewDidMoveToSuperview()
|
||||
guard autolayout else { return }
|
||||
if #available(macOS 10.15, *) {} else {
|
||||
if let view = superview {
|
||||
view.autoresizingMask = [.width]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func updateTrackingAreas() {
|
||||
super.updateTrackingAreas()
|
||||
if #available(macOS 10.15.1, *) {
|
||||
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) {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isMouseInsideView = true
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override func mouseExited(with event: NSEvent) {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isMouseInsideView = false
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MenuItemBaseView: NSMenuDelegate {
|
||||
func menuWillOpen(_ menu: NSMenu) {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isMenuOpen = true
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
func menuDidClose(_ menu: NSMenu) {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isMenuOpen = false
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,40 +8,16 @@
|
||||
|
||||
import Cocoa
|
||||
|
||||
class ProxyGroupMenuItemView: NSView {
|
||||
class ProxyGroupMenuItemView: MenuItemBaseView {
|
||||
let groupNameLabel: NSTextField
|
||||
let selectProxyLabel: NSTextField
|
||||
let arrowLabel = NSTextField(labelWithString: "▶")
|
||||
var isMouseInsideView = false
|
||||
var isMenuOpen = false
|
||||
let effectView: NSVisualEffectView = {
|
||||
let effectView = NSVisualEffectView()
|
||||
effectView.material = .popover
|
||||
effectView.state = .active
|
||||
effectView.isEmphasized = true
|
||||
effectView.blendingMode = .behindWindow
|
||||
return effectView
|
||||
}()
|
||||
|
||||
init(group: ClashProxyName, targetProxy: ClashProxyName) {
|
||||
groupNameLabel = VibrancyTextField(labelWithString: group)
|
||||
selectProxyLabel = VibrancyTextField(labelWithString: targetProxy)
|
||||
if #available(macOS 10.15, *) {
|
||||
super.init(frame: .zero)
|
||||
} else {
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: 0, height: 20))
|
||||
}
|
||||
// self
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
|
||||
// background
|
||||
addSubview(effectView)
|
||||
effectView.translatesAutoresizingMaskIntoConstraints = false
|
||||
effectView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
effectView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
effectView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
effectView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
let rect = NSRect(x: 0, y: 0, width: 0, height: 20) // requeie for system before 10.15
|
||||
super.init(frame: rect, handleClick:false, autolayout: true)
|
||||
|
||||
// arrow
|
||||
effectView.addSubview(arrowLabel)
|
||||
@ -54,9 +30,9 @@ class ProxyGroupMenuItemView: NSView {
|
||||
effectView.addSubview(groupNameLabel)
|
||||
groupNameLabel.leftAnchor.constraint(equalTo: effectView.leftAnchor, constant: 20).isActive = true
|
||||
groupNameLabel.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
||||
|
||||
// select
|
||||
selectProxyLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
effectView.addSubview(selectProxyLabel)
|
||||
selectProxyLabel.rightAnchor.constraint(equalTo: effectView.rightAnchor, constant: -30).isActive = true
|
||||
selectProxyLabel.centerYAnchor.constraint(equalTo: effectView.centerYAnchor).isActive = true
|
||||
@ -64,13 +40,11 @@ class ProxyGroupMenuItemView: NSView {
|
||||
// space
|
||||
selectProxyLabel.leftAnchor.constraint(greaterThanOrEqualTo: groupNameLabel.rightAnchor, constant: 30).isActive = true
|
||||
|
||||
// font
|
||||
let font = NSFont.menuFont(ofSize: 14)
|
||||
groupNameLabel.font = font
|
||||
selectProxyLabel.font = font
|
||||
|
||||
// font & color
|
||||
groupNameLabel.font = type(of: self).labelFont
|
||||
selectProxyLabel.font = type(of: self).labelFont
|
||||
groupNameLabel.textColor = NSColor.labelColor
|
||||
selectProxyLabel.textColor = NSColor.secondaryLabelColor
|
||||
selectProxyLabel.textColor = NSColor.tertiaryLabelColor
|
||||
arrowLabel.textColor = NSColor.labelColor
|
||||
}
|
||||
|
||||
@ -78,69 +52,13 @@ class ProxyGroupMenuItemView: NSView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func updateTrackingAreas() {
|
||||
super.updateTrackingAreas()
|
||||
if #available(macOS 10.15.1, *) {
|
||||
trackingAreas.forEach { removeTrackingArea($0) }
|
||||
enclosingMenuItem?.submenu?.delegate = self
|
||||
addTrackingArea(NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeAlways], owner: self, userInfo: nil))
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidMoveToSuperview() {
|
||||
super.viewDidMoveToSuperview()
|
||||
if #available(macOS 10.15, *) {} else {
|
||||
if let view = superview {
|
||||
view.autoresizingMask = [.width]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func mouseEntered(with event: NSEvent) {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isMouseInsideView = true
|
||||
setNeedsDisplay(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
override func mouseExited(with event: NSEvent) {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isMouseInsideView = false
|
||||
setNeedsDisplay(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
super.draw(dirtyRect)
|
||||
guard let menu = enclosingMenuItem else { return }
|
||||
let isHighlighted: Bool
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isHighlighted = isMouseInsideView || isMenuOpen
|
||||
} else {
|
||||
isHighlighted = menu.isHighlighted
|
||||
}
|
||||
let labelBgStyle: NSView.BackgroundStyle = isHighlighted ? .emphasized : .normal
|
||||
groupNameLabel.cell?.backgroundStyle = labelBgStyle
|
||||
selectProxyLabel.cell?.backgroundStyle = labelBgStyle
|
||||
arrowLabel.cell?.backgroundStyle = labelBgStyle
|
||||
effectView.material = isHighlighted ? .selection : .popover
|
||||
}
|
||||
}
|
||||
|
||||
extension ProxyGroupMenuItemView: NSMenuDelegate {
|
||||
func menuWillOpen(_ menu: NSMenu) {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isMenuOpen = true
|
||||
setNeedsDisplay(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
func menuDidClose(_ menu: NSMenu) {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isMenuOpen = false
|
||||
setNeedsDisplay(bounds)
|
||||
}
|
||||
updateBackground(groupNameLabel)
|
||||
updateBackground(selectProxyLabel)
|
||||
updateBackground(arrowLabel)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -41,26 +41,27 @@ class ProxyGroupSpeedTestMenuItem: NSMenuItem {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate class ProxyGroupSpeedTestMenuItemView: NSView {
|
||||
fileprivate class ProxyGroupSpeedTestMenuItemView: MenuItemBaseView {
|
||||
private let label: NSTextField
|
||||
private let font = NSFont.menuFont(ofSize: 14)
|
||||
private var isMouseInsideView = false
|
||||
private var eventHandler: EventHandlerRef?
|
||||
|
||||
init(_ title: String) {
|
||||
label = NSTextField(labelWithString: title)
|
||||
label.font = font
|
||||
label.font = Self.labelFont
|
||||
label.sizeToFit()
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: label.bounds.width + 40, height: 20))
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
let rect = NSRect(x: 0, y: 0, width: label.bounds.width + 40, height: 20)
|
||||
super.init(frame: rect, handleClick: true, autolayout: false)
|
||||
addSubview(label)
|
||||
label.frame = NSRect(x: 20, y: 0, width: label.bounds.width, height: 20)
|
||||
label.textColor = NSColor.labelColor
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func didClickView() {
|
||||
startBenchmark()
|
||||
}
|
||||
|
||||
private func startBenchmark() {
|
||||
guard let group = (enclosingMenuItem as? ProxyGroupSpeedTestMenuItem)?.proxyGroup
|
||||
@ -84,99 +85,15 @@ fileprivate class ProxyGroupSpeedTestMenuItemView: NSView {
|
||||
[weak self] in
|
||||
guard let self = self, let menu = self.enclosingMenuItem else { return }
|
||||
self.label.stringValue = menu.title
|
||||
self.label.textColor = NSColor.labelColor
|
||||
menu.isEnabled = true
|
||||
self.setNeedsDisplay(self.bounds)
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override func updateTrackingAreas() {
|
||||
super.updateTrackingAreas()
|
||||
if #available(macOS 10.15.1, *) {
|
||||
trackingAreas.forEach { removeTrackingArea($0) }
|
||||
addTrackingArea(NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeAlways], owner: self, userInfo: nil))
|
||||
addTrackingArea(NSTrackingArea(rect: bounds, options: [.mouseMoved, .activeAlways], owner: self, userInfo: nil))
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidMoveToWindow() {
|
||||
super.viewDidMoveToWindow()
|
||||
if #available(macOS 10.15.1, *) {
|
||||
setupCarbon()
|
||||
}
|
||||
}
|
||||
|
||||
// https://gist.github.com/p0deje/da5e5cfda6be8cb87c2e7caad3a3df63
|
||||
// https://stackoverflow.com/questions/53273191/custom-carbon-key-event-handler-fails-after-mouse-events
|
||||
@available(macOS 10.15.1, *)
|
||||
private func setupCarbon() {
|
||||
if window != nil {
|
||||
if let dispatcher = GetEventDispatcherTarget() {
|
||||
let eventHandlerCallback: EventHandlerUPP = { eventHandlerCallRef, eventRef, userData in
|
||||
guard let userData = userData else { return 0 }
|
||||
let itemView: ProxyGroupSpeedTestMenuItemView = bridge(ptr: userData)
|
||||
itemView.startBenchmark()
|
||||
return 0
|
||||
}
|
||||
|
||||
let eventSpecs = [EventTypeSpec(eventClass: OSType(kEventClassMouse), eventKind: UInt32(kEventMouseUp))]
|
||||
|
||||
InstallEventHandler(dispatcher, eventHandlerCallback, 1, eventSpecs, bridge(obj: self), &eventHandler)
|
||||
}
|
||||
} else {
|
||||
RemoveEventHandler(eventHandler)
|
||||
}
|
||||
}
|
||||
|
||||
override func mouseEntered(with event: NSEvent) {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isMouseInsideView = true
|
||||
setNeedsDisplay(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
override func mouseExited(with event: NSEvent) {
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isMouseInsideView = false
|
||||
setNeedsDisplay(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: NSPoint) -> NSView? {
|
||||
if bounds.contains(point) {
|
||||
return label
|
||||
}
|
||||
return super.hitTest(point)
|
||||
}
|
||||
|
||||
override func mouseUp(with event: NSEvent) {
|
||||
if #available(macOS 10.15.1, *) {} else {
|
||||
startBenchmark()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
super.draw(dirtyRect)
|
||||
guard let menu = enclosingMenuItem else { return }
|
||||
|
||||
let isHighlighted: Bool
|
||||
if #available(macOS 10.15.1, *) {
|
||||
isHighlighted = isMouseInsideView
|
||||
} else {
|
||||
isHighlighted = menu.isHighlighted
|
||||
}
|
||||
if isHighlighted && menu.isEnabled {
|
||||
NSColor.selectedMenuItemColor.setFill()
|
||||
label.textColor = NSColor.white
|
||||
} else {
|
||||
NSColor.clear.setFill()
|
||||
if enclosingMenuItem?.isEnabled ?? true {
|
||||
label.textColor = NSColor.labelColor
|
||||
} else {
|
||||
label.textColor = NSColor.secondaryLabelColor
|
||||
}
|
||||
}
|
||||
dirtyRect.fill()
|
||||
label.textColor = (enclosingMenuItem?.isEnabled ?? true) ? NSColor.labelColor : NSColor.placeholderTextColor
|
||||
updateBackground(label)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user