// // KMCustomViewButton.swift // PDF Reader Pro // // Created by tangchao on 2023/11/2. // import Cocoa @objc enum KMCustomViewButtonType: Int { case batchToolbar = 0 case menuItem case normal } @objc enum KMCustomViewButtonState: Int { case normal = 0 case mouseIn case hightLighted } @objc protocol KMCustomButtonViewPopDataSource: NSObjectProtocol { /// pop框有多少行 func numberOfLine(in button: KMCustomViewButton) -> Int /// pop框某行显示的文字 func string(for button: KMCustomViewButton, index: Int) -> String? /// 某行是否需要下划线 func needInsertSeperateLine(_ button: KMCustomViewButton, index: Int) -> Bool /// 某行是否需要选取 func needHightLightLine(_ button: KMCustomViewButton, index: Int) -> Bool } @objc protocol KMCustomButtonViewPopDelegate: NSObjectProtocol { func customViewButton(_ button: KMCustomViewButton, didSelectIndex index: Int) } @objcMembers class KMCustomViewButton: NSView { /// constraint var frontImageLeftConstraint: MASConstraint? //左边图片距离左边约束 var frontImageTopConstraint: MASConstraint? //左边图片顶部约束 var titleLeftConstraint: MASConstraint? //标题距离左边约束 var titleTopConstraint: MASConstraint? //标题顶部约束 var backImageLeftConstraint: MASConstraint? //右边图片距离左边约束 /// view lazy var imageView: NSImageView = { let view = NSImageView() return view }() lazy var titleTextField: NSTextField = { let view = NSTextField() view.isBordered = false view.drawsBackground = true view.backgroundColor = .clear view.isEditable = false return view }() lazy var indicateImageView: NSImageView = { let view = NSImageView() return view }() weak var layoutReferenceView: NSView? /// layer var backLayer: CALayer? /// state var isSelected = false { didSet { if (self.isSelected) { self._toolbarButtonSelected(true) if let color = self.selectColor { self.backLayer?.backgroundColor = color.cgColor } } else { self._toolbarButtonSelected(true) self.backLayer?.backgroundColor = .clear } } } var mouseInColor: NSColor? var highLightColor: NSColor? var selectColor: NSColor? /// popover var popOver: NSPopover? var enable = false /// title Attribute var titleAttributeDict: [String : Any]? weak var dataSource: KMCustomButtonViewPopDataSource? { didSet { NotificationCenter.default.removeObserver(self) NotificationCenter.default.addObserver(self, selector: #selector(_closePop), name: NSNotification.Name("KMCloseCustomViewButtonPopNotification"), object: nil) } } weak var delegate: KMCustomButtonViewPopDelegate? private var _showMenuFlag = false private var _type: KMCustomViewButtonType = .batchToolbar private var _state: KMCustomViewButtonState = .normal { didSet { if (self._state == .normal) { if (self.isSelected) { self.backLayer?.backgroundColor = self.selectColor != nil ? self.selectColor!.cgColor : .clear } else { self._toolbarButtonSelected(true) self.backLayer?.backgroundColor = .clear } } else if (self._state == .mouseIn) { self._toolbarButtonSelected(true) if (self.isSelected) { self.backLayer?.backgroundColor = self.selectColor != nil ? self.selectColor!.cgColor : (self.mouseInColor ?? NSColor.clear).cgColor } else { self.backLayer?.backgroundColor = (self.mouseInColor ?? NSColor.clear).cgColor } } else if (self._state == .hightLighted) { if (self.isSelected) { self.backLayer?.backgroundColor = self.selectColor != nil ? self.selectColor!.cgColor : (self.highLightColor ?? NSColor.clear).cgColor } else { self.backLayer?.backgroundColor = (self.highLightColor ?? NSColor.clear).cgColor } } if (self.titleAttributeDict != nil) { self.titleTextField.textColor = self.titleAttributeDict?["\(self._state.rawValue)"] as? NSColor } } } private weak var _target: AnyObject? private var _action: Selector? deinit { KMPrint("KMCustomViewButton deinit.") NotificationCenter.default.removeObserver(self) } /** * 初始化方法 * @param frontImage 头部图片 * @param backImage 尾部图片 * @param title 标题 * @param title 样式 * */ convenience init(frontImage: NSImage?, backImage: NSImage?, title: String?, type: KMCustomViewButtonType) { if frontImage == nil && backImage == nil && title == nil { // return } self.init() self.wantsLayer = true self.enable = false self._addTrackingArea() self.layoutReferenceView = self var leftMargin = 0.0 if (frontImage != nil) { self.addSubview(self.imageView) self.imageView.image = frontImage leftMargin = 8 self.imageView.mas_makeConstraints { make in self.frontImageLeftConstraint = make?.left.equalTo()(self.layoutReferenceView)?.offset()(leftMargin) make?.centerY.top().equalTo()(self.layoutReferenceView) self.frontImageTopConstraint = make?.top.equalTo()(self.layoutReferenceView)?.offset()(3) } self.layoutReferenceView = self.imageView } if (title != nil) { self.addSubview(self.titleTextField) self.titleTextField.stringValue = title! self.titleTextField.font = .systemFont(ofSize: 12) self.titleTextField.textColor = KMAppearance.Layout.h0Color() self.titleTextField.mas_makeConstraints { make in if self.isEqual(to: self.layoutReferenceView) { leftMargin = 30 self.titleLeftConstraint = make?.left.equalTo()(self)?.offset()(leftMargin) self.titleTopConstraint = make?.top.equalTo()(self)?.offset()(3) make?.centerY.equalTo()(self) } else { leftMargin = 8 self.titleLeftConstraint = make?.left.equalTo()(self.imageView.mas_right)?.offset()(leftMargin) make?.centerY.equalTo()(self) } } self.layoutReferenceView = self.titleTextField } if (backImage != nil) { self.addSubview(self.indicateImageView) self.indicateImageView.image = backImage self.indicateImageView.mas_makeConstraints { make in self.backImageLeftConstraint = make?.left.equalTo()(self.layoutReferenceView?.mas_right)?.offset()(2) make?.centerY.equalTo()(self) } self.layoutReferenceView = self.indicateImageView } self.layoutReferenceView?.mas_makeConstraints({ make in make?.right.equalTo()(self)?.offset()(-leftMargin) }) self.configuAppearance(type: type) self._type = type self._state = .normal self.isSelected = false } override func mouseEntered(with event: NSEvent) { if (self.enable) { super.mouseEntered(with: event) self._showPop() if (self.dataSource == nil) { NotificationCenter.default.post(name: NSNotification.Name("KMCloseCustomViewButtonPopNotification"), object: nil) } self._state = .mouseIn } } // MARK: - Public Methods /// 设置设置target-action func addTarget(_ target: AnyObject?, action: Selector?) { self._target = target self._action = action } /// 关闭pop func closePop() { self._closePop() } override func mouseExited(with event: NSEvent) { if (self.enable) { super.mouseExited(with: event) if (self._showMenuFlag) { } else { self._state = .normal } } } override func mouseDown(with event: NSEvent) { if (self.enable) { super.mouseDown(with: event) self._state = .hightLighted } } override func mouseUp(with event: NSEvent) { if (self.enable) { super.mouseUp(with: event) if (self._showMenuFlag) { } else { self._state = .normal } /// 响应事件 if let data = (self._target as? NSObject)?.responds(to: self._action), data { (self._target as? NSObject)?.perform(self._action, with: self) } } } } // MARK: - Private Methods extension KMCustomViewButton { private func _toolbarButtonSelected(_ isSelected: Bool) { // if (isSelected) { // if ([self.titleTextField.stringValue isEqualToString:NSLocalizedString(@"Convert")]) { // self.imageView.image = [NSImage imageNamed:@"KMImageNameUXIconBatchConvert"]; // } else if ([self.titleTextField.stringValue isEqualToString:NSLocalizedString(@"Merge")]) { // self.imageView.image = [NSImage imageNamed:@"KMImageNameUXIconBatchMerge"]; // } else if ([self.titleTextField.stringValue isEqualToString:NSLocalizedString(@"Compress")]) { // self.imageView.image = [NSImage imageNamed:@"KMImageNameUXIconBatchOptimize"]; // } else if ([self.titleTextField.stringValue isEqualToString:NSLocalizedString(@"OCR")]) { // self.imageView.image = [NSImage imageNamed:@"KMImageNameUXIconBatchConvertOCR"]; // } else if ([self.titleTextField.stringValue isEqualToString:NSLocalizedString(@"Security")]) { // self.imageView.image = [NSImage imageNamed:@"KMImageNameUXIconBatchSafe"]; // } else if ([self.titleTextField.stringValue isEqualToString:NSLocalizedString(@"Watermark")]) { // self.imageView.image = [NSImage imageNamed:@"KMImageNameUXIconBatchWatermark"]; // } else if ([self.titleTextField.stringValue isEqualToString:NSLocalizedString(@"Background")]) { // self.imageView.image = [NSImage imageNamed:@"KMImageNameUXIconBatchBackground"]; // } else if ([self.titleTextField.stringValue isEqualToString:NSLocalizedString(@"Header & Footer")]) { // self.imageView.image = [NSImage imageNamed:@"KMImageNameUXIconBatchHeaderandfooter"]; // } else if ([self.titleTextField.stringValue isEqualToString:NSLocalizedString(@"Bates Numbers")]) { // self.imageView.image = [NSImage imageNamed:@"KMImageNameUXIconBatchBates"]; // } // } else { if self.titleTextField.stringValue == KMLocalizedString("Convert") { self.imageView.image = NSImage(named: "KMImageNameUXIconBatchConvertNor") } else if self.titleTextField.stringValue == KMLocalizedString("Merge") { self.imageView.image = NSImage(named: "KMImageNameUXIconBatchMergeNor") } else if self.titleTextField.stringValue == KMLocalizedString("Compress") { self.imageView.image = NSImage(named: "KMImageNameUXIconBatchOptimizeNor") } else if self.titleTextField.stringValue == KMLocalizedString("OCR") { self.imageView.image = NSImage(named: "KMImageNameUXIconBatchConvertOCRNor") } else if self.titleTextField.stringValue == KMLocalizedString("Security") { self.imageView.image = NSImage(named: "KMImageNameUXIconBatchSafeNor") } else if self.titleTextField.stringValue == KMLocalizedString("Watermark") { self.imageView.image = NSImage(named: "KMImageNameUXIconBatchWatermarkNor") } else if self.titleTextField.stringValue == KMLocalizedString("Background") { self.imageView.image = NSImage(named: "KMImageNameUXIconBatchBackgroundNor") } else if self.titleTextField.stringValue == KMLocalizedString("Header & Footer") { self.imageView.image = NSImage(named: "KMImageNameUXIconBatchHeaderandfooterNor") } else if self.titleTextField.stringValue == KMLocalizedString("Bates Numbers") { self.imageView.image = NSImage(named: "KMImageNameUXIconBatchBatesNor") } // } } @objc private func _closePop() { self.popOver?.close() self._state = .normal } private func _showPop() { if (self.delegate == nil || self.dataSource == nil) { return } let menuViewController = KMCustomButtonPopMenuViewController() menuViewController.delegate = self menuViewController.dataSources = self if (self.popOver == nil) { self.popOver = NSPopover() } self.popOver?.delegate = self self.popOver?.contentViewController = menuViewController self.popOver?.animates = false self.popOver?.behavior = .semitransient self.popOver?.contentSize = menuViewController.view.frame.size var sourcesRect = self.bounds sourcesRect = self.convert(sourcesRect, to: nil) sourcesRect.origin.y -= 20 sourcesRect.size.height+=20 self.window?.popover = self.popOver self.window?.sourcesRect = sourcesRect self.popOver?.show(relativeTo: KMRectInset(self.bounds, 0, 5), of: self, preferredEdge: .minY) } private func _addTrackingArea() { let trackingArea = NSTrackingArea(rect: self.bounds, options: [.mouseEnteredAndExited, .inVisibleRect, .activeAlways,.mouseMoved], owner: self) self.addTrackingArea(trackingArea) } } // MARK: - NSPopoverDelegate extension KMCustomViewButton: NSPopoverDelegate { func popoverDidShow(_ notification: Notification) { self._showMenuFlag = true } func popoverDidClose(_ notification: Notification) { self._showMenuFlag = false self._state = .normal } } // MARK: - KMCustomButtonPopMenuViewControllerDelegate, KMCustomButtonPopMenuViewControllerDataSources extension KMCustomViewButton: KMCustomButtonPopMenuViewControllerDelegate, KMCustomButtonPopMenuViewControllerDataSources { func customViewButtonPopDidSelectIndex(_ index: Int) { if let _ = self.delegate?.customViewButton(self, didSelectIndex: index) { self._closePop() } } func numberOfLine() -> Int { if let data = self.dataSource?.numberOfLine(in: self) { return data } return 0 } /// pop框某行显示的文字 func stringForLine(at index: Int) -> String? { if let data = self.dataSource?.string(for: self, index: index) { return data } return nil; } /// 某行是否需要下划线 func needInsertSeperateLine(at index: Int) -> Bool { if let data = self.dataSource?.needInsertSeperateLine(self, index: index) { return data } return false } /// 某行是否需要选取 func needHightLightLine(at index: Int) -> Bool { if let data = self.dataSource?.needHightLightLine(self, index: index) { return data } return false } func imageForLine(at index: Int) -> NSImage? { return nil } func itemEnable(at index: Int) -> Bool { return true } } // MARK: - Appearance extension KMCustomViewButton { func configuAppearance(type: KMCustomViewButtonType) { if (type == .batchToolbar) { self.backLayer = CALayer() self.backLayer?.frame = self.layer?.frame ?? NSRect.zero self.backLayer?.autoresizingMask = [.layerWidthSizable, .layerHeightSizable] self.layer?.addSublayer(self.backLayer!) self.backLayer?.cornerRadius = 6.0; self.selectColor = KMAppearance.Status.selColor() self.highLightColor = KMAppearance.Status.selColor() self.mouseInColor = KMAppearance.Status.selColor() } else if (type == .menuItem) { self.alignCenter(margin: 10) self.changeTopMargin(5) } } /// 调整按钮左右间距并居中 func alignCenter(margin: CGFloat) { self.changeLeftMargin(margin) self.changeRightMargin(margin) } /// 调整顶部间距离 func changeTopMargin(_ topMargin: CGFloat) { let v = self._leftestView() if self.imageView.isEqual(to: v) { self.imageView.mas_updateConstraints { make in make?.top.equalTo()(self)?.offset()(topMargin) } } else if self.titleTextField.isEqual(to: v) { self.titleTextField.mas_updateConstraints { make in make?.top.equalTo()(self)?.offset()(topMargin) } } else if self.indicateImageView.isEqual(to: v) { } } /// 调整按钮左边间距 func changeLeftMargin(_ leftMargin: CGFloat) { let v = self._leftestView() if self.imageView.isEqual(to: v) { self.imageView.mas_updateConstraints { make in self.frontImageLeftConstraint = make?.left.equalTo()(self)?.offset()(leftMargin) } } else if self.titleTextField.isEqual(to: v) { self.titleTextField.mas_updateConstraints { make in self.titleLeftConstraint = make?.left.equalTo()(self)?.offset()(leftMargin) } } else if self.indicateImageView.isEqual(to: v) { self.indicateImageView.mas_updateConstraints { make in self.backImageLeftConstraint = make?.left.equalTo()(self)?.offset()(leftMargin) } } } /// 调整按钮右边间距 func changeRightMargin(_ rightMargin: CGFloat) { let v = self._rightestView() if self.imageView.isEqual(to: v) { self.imageView.mas_updateConstraints { make in make?.right.equalTo()(self)?.offset()(-rightMargin) } } else if self.titleTextField.isEqual(to: v) { self.titleTextField.mas_updateConstraints { make in make?.right.equalTo()(self)?.offset()(-rightMargin) } } else if self.indicateImageView.isEqual(to: v) { self.indicateImageView.mas_updateConstraints { make in make?.right.equalTo()(self)?.offset()(-rightMargin) } } } private func _leftestView() -> NSView? { if self.subviews.contains(self.imageView) { return self.imageView } else if self.subviews.contains(self.titleTextField) { return self.titleTextField } else if (self.subviews.contains(self.indicateImageView)) { return self.indicateImageView } return nil } private func _rightestView() -> NSView? { if self.subviews.contains(self.indicateImageView) { return self.indicateImageView } else if self.subviews.contains(self.titleTextField) { return self.titleTextField } else if self.subviews.contains(self.imageView) { return self.imageView } return nil } }