// // KMToolbarItemView.swift // PDF Reader Pro // // Created by tangchao on 2023/10/24. // import Cocoa enum KMToolbarItemViewSelectBackgroundType: Int { case none = 0 case imageBox } class KMToolbarClickButton: NSButton { weak var clickObject: AnyObject? } extension NSControl.ImagePosition { static let imageExpandLeft: NSControl.ImagePosition = .init(rawValue: 100)! } private let KMPopOverClosedByWindowNotificationName = "KMPopOverClosedByWindowNotification" extension KMToolbarItemView { public static let popOverClosedNotificationName = Notification.Name(KMPopOverClosedByWindowNotificationName) } @objcMembers class KMToolbarItemView: NSView { var menuFormRepresentation: NSMenuItem? private var _itemIdentifier: String? var itemIdentifier: String? { get { return self._itemIdentifier } } lazy var clickButton: KMToolbarClickButton = { let view = KMToolbarClickButton() view.bezelStyle = .regularSquare view.isBordered = false view.imagePosition = .imageOnly view.clickObject = self return view }() var isSelected = false { didSet { if self.itemIdentifier != KMToolbarDividerItemIdentifier { if (isSelected) { if(self.image != nil && self.alternateImage != nil) { if (self.selectedImage != nil) { self.imageViewBtn.image = self.selectedImage! } else { self.imageViewBtn.image = self.alternateImage! } } if (self.nameBtn.superview != nil) { self.nameBtn.setTitleColor(color: Self.textSelectedColor) } if(self.needExpandAction) { self.needExpandButton.image = NSImage(named: "KMImageNameUXIconBtnTriDownSel") } } else { if (self.needExpandAction) { self.needExpandButton.image = NSImage(named: "KMImageNameUXIconBtnTriDownNor") } if (self.image != nil) { self.imageViewBtn.image = self.image! } if (self.nameBtn.superview != nil) { self.nameBtn.setTitleColor(color: Self.textNormalColor) } } self._updateSelectBackground() } } } var unEnabled = false { didSet { self.clickButton.isEnabled = !self.unEnabled self.nameBtn.isEnabled = !self.unEnabled self.imageViewBtn.isEnabled = !self.unEnabled self.needExpandButton.isEnabled = !self.unEnabled } } var isShowCustomToolTip = false { didSet { if (self.isShowCustomToolTip) { self.clickButton.toolTip = "" } } } var boxImagePosition: NSControl.ImagePosition = .imageLeft { didSet { self._layoutView() } } var image: NSImage? { didSet { self.imageViewBtn.image = self.image } } var selectedImage: NSImage? var alternateImage: NSImage? var titleName: String? { didSet { self.nameBtn.title = self.titleName ?? " " self.nameBtn.setTitleColor(color: Self.textNormalColor) } } weak var target: AnyObject? { didSet { self.clickButton.target = self.target } } var btnAction: Selector? { didSet { self.clickButton.action = self.btnAction } } var needExpandAction = false var btnTag = 0 { didSet { self.clickButton.tag = self.btnTag } } var customizeView: NSView? { didSet { self._layoutView() } } var normalBackgroundColor: NSColor = .clear var selectedBackgroundColor: NSColor = KMAppearance.Status.selColor() var selectBackgroundType: KMToolbarItemViewSelectBackgroundType = .none lazy var imageViewBox: NSBox = { let view = NSBox() view.borderWidth = 0 view.contentViewMargins = NSSize.zero view.boxType = .custom view.borderColor = .clear view.cornerRadius = 7.0 return view }() private lazy var imageViewBtn: NSButton = { let view = NSButton() view.bezelStyle = .regularSquare view.isBordered = false view.imagePosition = .imageOnly return view }() private var nameBtn: NSButton = { let view = NSButton() view.bezelStyle = .regularSquare view.isBordered = false view.imagePosition = .imageOnly view.title = "" return view }() private var needExpandButton: NSButton = { let view = NSButton() view.bezelStyle = .regularSquare view.isBordered = false view.imagePosition = .imageOnly view.image = NSImage(named: "KMImageNameUXIconBtnTriDownNor") return view }() private var _popOver: NSPopover? var popOver: NSPopover? { get { return self._popOver } set { if self._popOver == nil || self._popOver!.isEqual(to: newValue) == false { self._popOver = newValue if (newValue != nil) { self.layer?.backgroundColor = Self.selectedBackgroundColor.cgColor } else { self._updateSelectBackground() } } } } private var _menuViewController: KMCustomButtonPopMenuViewController? private var _kNormalImage: NSImage? private var _originalHelpTip: String? deinit { // Swift.debugPrint("KMToolbarItemView deinit") NotificationCenter.default.removeObserver(self) } class var textFont: NSFont { get { .systemFont(ofSize: 12) } } class var textNormalColor: NSColor { get { KMAppearance.titleColor() } } class var textSelectedColor: NSColor { get { KMAppearance.titleColor() } } class var selectedBackgroundColor: NSColor { get { return KMAppearance.Status.selColor() } } class var normalBackgroundColor: NSColor { get { return .clear } } convenience init(itemIdentifier: String) { self.init() self._itemIdentifier = itemIdentifier self.boxImagePosition = .imageLeft self.wantsLayer = true self.layer?.cornerRadius = 5 self.layer?.masksToBounds = true self.nameBtn.font = Self.textFont self._addTrackingArea() NotificationCenter.default.addObserver(self, selector: #selector(_windowClosedPop), name: KMToolbarItemView.popOverClosedNotificationName, object: nil) } convenience init(itemIdentifier: String, postition imagePositionImagePosition: NSControl.ImagePosition, withPopMenu popMenuViewController: KMCustomButtonPopMenuViewController?) { self.init() self.boxImagePosition = imagePositionImagePosition self._menuViewController = popMenuViewController self._itemIdentifier = itemIdentifier self.wantsLayer = true self.layer?.cornerRadius = 4 self.layer?.masksToBounds = true self.nameBtn.font = Self.textFont self._addTrackingArea() self._layoutView() if (popMenuViewController != nil) { NotificationCenter.default.addObserver(self, selector: #selector(_windowClosedPop), name: KMToolbarItemView.popOverClosedNotificationName, object: nil) } } override func draw(_ dirtyRect: NSRect) { if (self.itemIdentifier == KMToolbarDividerItemIdentifier) { let context = NSGraphicsContext.current?.cgContext KMContextSaveGState(context) KMContextTranslateCTM(context, CGRectGetWidth(dirtyRect)/2.0, CGRectGetHeight(dirtyRect)/2.0-10) KMContextMoveToPoint(context, 0, 0) KMContextAddLineToPoint(context, 0, 20) KMContextSetStrokeColorWithColor(context, NSColor(red: 0, green: 0, blue: 0, alpha: 0.1).cgColor) KMContextStrokePath(context) KMContextRestoreGState(context) } } override var toolTip: String? { get { return self._originalHelpTip } set { self.clickButton.toolTip = newValue ?? "" self._originalHelpTip = self.clickButton.toolTip if(self.isShowCustomToolTip) { self.clickButton.toolTip = "" } } } override func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) guard let _window = self.window else { return } if (!_window.isKeyWindow) { return } if (self.itemIdentifier == KMToolbarDividerItemIdentifier || self.customizeView != nil || self.image == nil) { return } if self.unEnabled { return } if (!self.isSelected) { if self.selectBackgroundType == .none { self.layer?.backgroundColor = Self.selectedBackgroundColor.cgColor } else { self.imageViewBox.fillColor = Self.selectedBackgroundColor } if(self.image != nil && self.alternateImage != nil) { self._kNormalImage = self.image self.imageViewBtn.image = self.alternateImage if(self.nameBtn.superview != nil) { self.nameBtn.setTitleColor(color: Self.textNormalColor) } } } if (self.needExpandAction) { // KMImageNameUXIconBtnTriDownSel self.needExpandButton.image = NSImage(named: "KMImageNameUXIconBtnTriDownNor") self._showPop(self) }else if (self.isShowCustomToolTip) { self.perform(#selector(_showHUDHint), with: nil, afterDelay: 0.1) } } override func mouseExited(with event: NSEvent) { super.mouseExited(with: event) if (!self.isSelected && !self.needExpandAction) { if self.selectBackgroundType == .none { self.layer?.backgroundColor = self.normalBackgroundColor.cgColor } else { self.imageViewBox.fillColor = self.normalBackgroundColor } if(self.image != nil && self.alternateImage != nil) { self.imageViewBtn.image = self._kNormalImage ?? self.image! } } if(self.needExpandAction && !self.isSelected) { if self.selectBackgroundType == .none { self.layer?.backgroundColor = self.normalBackgroundColor.cgColor } else { self.imageViewBox.fillColor = self.normalBackgroundColor } if(self.image != nil && self.alternateImage != nil) { self.imageViewBtn.image = self._kNormalImage ?? self.image! } self.needExpandButton.image = NSImage(named: "KMImageNameUXIconBtnTriDownNor") } guard let _window = self.window else { return } if (!_window.isKeyWindow) { return } if(self.nameBtn.superview != nil && !self.isSelected) { self.nameBtn.setTitleColor(color: Self.textNormalColor) } if (self.isShowCustomToolTip && !self.needExpandAction) { NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(_showHUDHint), object: nil) self._closePop() } } private func _updateSelectBackground() { if self.selectBackgroundType == .none { if self.isSelected { self.layer?.backgroundColor = Self.selectedBackgroundColor.cgColor } else { self.layer?.backgroundColor = self.normalBackgroundColor.cgColor } } else if self.selectBackgroundType == .imageBox { if self.isSelected { self.imageViewBox.fillColor = Self.selectedBackgroundColor } else { self.imageViewBox.fillColor = self.normalBackgroundColor } } } // override func updateLayer() { // super.updateLayer() // // self.wantsLayer = true // self.layer?.backgroundColor = KMAppearance.Layout.l_2Color().cgColor // } } // MARK: - Private Methods extension KMToolbarItemView { private func _addTrackingArea() { let trackingArea = NSTrackingArea(rect: self.bounds, options: [.mouseEnteredAndExited, .inVisibleRect, .activeInKeyWindow], owner: self) self.addTrackingArea(trackingArea) } private func _showPop(_ sender: NSView) { if (self._popOver != nil) { return } let menuViewController = KMCustomButtonPopMenuViewController() menuViewController.delegate = self menuViewController.dataSources = self 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 = sender.bounds sourcesRect = sender.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: CGRectInset(self.bounds, 0, 5), of: self, preferredEdge: .minY) } private func _closePop() { self.popOver?.close() self.popOver = nil } @objc private func _showHUDHint() { // KMToolbarItemPopViewController *popViewController = [[[KMToolbarItemPopViewController alloc] init] autorelease]; // self.popOver = [[[NSPopover alloc] init] autorelease]; // self.popOver.contentViewController = popViewController; // self.popOver.animates = NO; // self.popOver.behavior = NSPopoverBehaviorSemitransient; // self.popOver.backgroundColor = [KMAppearance KMBluegrey01Color]; // // self.popOver.contentSize = popViewController.view.frame.size; // [popViewController updateWithHelpTip:self.originalHelpTip]; // [self.popOver showRelativeToRect:self.bounds ofView:self preferredEdge:NSRectEdgeMinY]; } @objc private func _windowClosedPop(sender: Notification) { if let data = self._popOver?.isEqual(to: sender.object), data { self.popOver = nil } } private func _layoutView() { if self.nameBtn.superview != nil { self.nameBtn.removeFromSuperview() } if self.imageViewBox.superview != nil { self.imageViewBox.removeFromSuperview() } if self.imageViewBtn.superview != nil { self.imageViewBtn.removeFromSuperview() } if let view = self.customizeView { if view.superview != nil { view.removeFromSuperview() } self.addSubview(view) view.km_add_leading_constraint() view.km_add_trailing_constraint() view.km_add_centerY_constraint() view.km_add_width_constraint(constant: NSWidth(view.bounds)) view.km_add_height_constraint(constant: NSHeight(view.bounds)) return } else if (self.itemIdentifier == KMToolbarDividerItemIdentifier) { self.addSubview(self.imageViewBox) self.imageViewBox.km_add_inset_constraint(inset: NSEdgeInsetsZero) self.imageViewBox.km_add_width_constraint(constant: 8) return } let offset = 4.0 let offsetY = 2.0 let offsetX = 4.0 if self.boxImagePosition == .imageOnly { self.addSubview(self.imageViewBox) self.imageViewBox.km_add_inset_constraint() self.imageViewBox.contentView?.addSubview(self.imageViewBtn) self.imageViewBtn.km_add_inset_constraint(equalTo: self.imageViewBox, inset: NSEdgeInsets(top: offsetY, left: offsetX, bottom: offsetY, right: offsetX)) } else if (self.boxImagePosition == .imageLeft) { self.addSubview(self.imageViewBox) self.imageViewBox.km_add_leading_constraint() self.imageViewBox.km_add_top_constraint() self.imageViewBox.km_add_bottom_constraint() self.imageViewBox.contentView?.addSubview(self.imageViewBtn) self.imageViewBtn.km_add_inset_constraint(equalTo: self.imageViewBox, inset: NSEdgeInsets(top: offsetY, left: 2*offsetX-2, bottom: offsetY, right: 2)) self.addSubview(self.nameBtn) self.nameBtn.km_add_centerY_constraint() self.nameBtn.km_add_leading_constraint(equalTo: self.imageViewBox, attribute: .trailing) if (self.needExpandAction) { self.nameBtn.km_add_right_constraint(constant: -2*offsetX-8) } else { self.nameBtn.km_add_right_constraint(constant: -2*offsetX) } if(self.needExpandAction) { self.needExpandButton.image = NSImage(named: "KMImageNameUXIconBtnTriDownNor") self.addSubview(self.needExpandButton) self.needExpandButton.km_add_centerY_constraint() self.needExpandButton.km_add_width_constraint(constant: 8) self.needExpandButton.km_add_right_constraint(constant: -offset) } self.layer?.cornerRadius = 6 } else if (self.boxImagePosition == .imageExpandLeft) { self.addSubview(self.imageViewBox) self.imageViewBox.km_add_leading_constraint() self.imageViewBox.km_add_top_constraint() self.imageViewBox.km_add_bottom_constraint() self.imageViewBox.contentView?.addSubview(self.imageViewBtn) self.imageViewBtn.km_add_inset_constraint(equalTo: self.imageViewBox, inset: NSEdgeInsets(top: offsetY, left: offsetX, bottom: offsetY, right: 0)) self.needExpandButton.image = NSImage(named: "KMImageNameUXIconBtnTriDownNor") self.addSubview(self.needExpandButton) self.needExpandButton.km_add_centerY_constraint() self.needExpandButton.km_add_width_constraint(constant: 8) self.needExpandButton.km_add_right_constraint(constant: -offset) self.addSubview(self.nameBtn) self.nameBtn.km_add_centerY_constraint() self.nameBtn.km_add_leading_constraint(equalTo: self.imageViewBox, attribute: .trailing) self.nameBtn.km_add_trailing_constraint(equalTo: self.needExpandButton, attribute: .leading) } else if (self.boxImagePosition == .imageAbove) { self.addSubview(self.nameBtn) self.nameBtn.alignment = .center self.nameBtn.mas_makeConstraints { make in make?.left.right().equalTo()(0) make?.width.greaterThanOrEqualTo()(32) make?.bottom.equalTo()(self.mas_bottom)?.offset()(0) } // self.nameBtn.km_add_leading_constraint() // self.nameBtn.km_add_trailing_constraint() // self.nameBtn.km_add_bottom_constraint() self.addSubview(self.imageViewBox) self.imageViewBox.km_add_top_constraint() self.imageViewBox.km_add_width_constraint(constant: 32) self.imageViewBox.km_add_centerX_constraint() self.imageViewBox.km_add_bottom_constraint(equalTo: self.nameBtn, attribute: .top, constant: 0) self.imageViewBox.contentView?.addSubview(self.imageViewBtn) self.imageViewBtn.km_add_inset_constraint(inset: .init(top: 0, left: offset, bottom: 0, right: offset)) } self.imageViewBox.borderWidth = 1.0 self.addSubview(self.clickButton) self.clickButton.km_add_inset_constraint() } } extension KMToolbarItemView: NSPopoverDelegate { func popoverDidClose(_ notification: Notification) { if let data = self.popOver?.isEqual(to: notification.object), data { self._closePop() } } } extension KMToolbarItemView: KMCustomButtonPopMenuViewControllerDelegate, KMCustomButtonPopMenuViewControllerDataSources { func customViewButtonPopDidSelectIndex(_ index: Int) { self._closePop() // if (self.itemIdentifier == KMToolbarPageEditPageRangeItemIdentifier) { if let items = self.menuFormRepresentation?.submenu?.items { let item = items[index] _ = item.target?.perform(item.action, with: item) } // } } func numberOfLine() -> Int { // if (self.itemIdentifier == KMToolbarPageEditPageRangeItemIdentifier) { if let items = self.menuFormRepresentation?.submenu?.items { return items.count } // } return 0 } func stringForLine(at index: Int) -> String? { // if (self.itemIdentifier == KMToolbarPageEditPageRangeItemIdentifier) { if let items = self.menuFormRepresentation?.submenu?.items { return items[index].title } // } return nil } func needInsertSeperateLine(at index: Int) -> Bool { if let items = self.menuFormRepresentation?.submenu?.items { return items[index].isSeparatorItem } return false } func needHightLightLine(at index: Int) -> Bool { return false } func imageForLine(at index: Int) -> NSImage? { if let items = self.menuFormRepresentation?.submenu?.items { return items[index].image } return nil } func itemEnable(at index: Int) -> Bool { return true } }