Refactor menuitem views

This commit is contained in:
yicheng 2019-11-02 00:04:31 +08:00
parent 9ad3565242
commit c2ba79c543
4 changed files with 190 additions and 190 deletions

View File

@ -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 */,

View 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()
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}