// // 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) ?? .imageLeft } 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 } } weak var pdfView: CPDFListView? 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 let data = self.selectedImage { self.imageViewBtn.image = data } else { if let data = self.alternateImage { self.imageViewBtn.image = data } } } if (self.nameBtn.superview != nil) { self.nameBtn.setTitleColor(color: Self.fetchTextSelectedColor()) } if(self.needExpandAction) { self.needExpandButton.image = NSImage(named: "KMImageNameUXIconBtnTriDownSel") } } else { if (self.needExpandAction) { self.needExpandButton.image = NSImage(named: "KMImageNameUXIconBtnTriDownNor") } if let data = self.image { self.imageViewBtn.image = data } if (self.nameBtn.superview != nil) { self.nameBtn.setTitleColor(color: Self.fetchTextNormalColor()) } } 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() self.itemWidth = self._calculateWidth() } } 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.fetchTextNormalColor()) self.itemWidth = self._calculateWidth() } } weak var target: AnyObject? { didSet { self.clickButton.target = self.target } } var btnAction: Selector? { didSet { self.clickButton.action = self.btnAction } } var needExpandAction = false { didSet { self.itemWidth = self._calculateWidth() } } var btnTag = 0 { didSet { self.clickButton.tag = self.btnTag } } var customizeView: NSView? { didSet { self._layoutView() self.itemWidth = self._calculateWidth() } } private var promptView_: NSView? var promptView: NSView? { get { return self.promptView_ } } private var promptIdentifier_: String? var promptIdentifier: String? { get { return self.promptIdentifier_ } set { if self.promptIdentifier_ != newValue { self.promptIdentifier_ = newValue if let data = KMDataManager.ud_object(forKey: newValue ?? "") as? Bool { self.isShowPrompt = !data } else { self.isShowPrompt = true } } } } private var isShowPrompt_: Bool = false var isShowPrompt: Bool { get { return self.isShowPrompt_ } set { self.isShowPrompt_ = newValue self.promptView?.isHidden = !newValue } } var normalBackgroundColor: NSColor = .clear var selectedBackgroundColor: NSColor = KMAppearance.Status.selColor() var selectBackgroundType: KMToolbarItemViewSelectBackgroundType = .none var itemWidth: CGFloat = 0 var itemHeight: CGFloat = 0 var isPopToolTip = false private var area_: NSTrackingArea? static let kDividerWidth: CGFloat = 8 static let kHSpace: CGFloat = 4 static let kExpandWidth: CGFloat = 8 static let kRightMargin: CGFloat = 4 static let kImageAboveMinWidth: CGFloat = 32 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 }() lazy var imageViewBtn: NSButton = { let view = NSButton() view.bezelStyle = .regularSquare view.isBordered = false view.imagePosition = .imageOnly return view }() var nameBtn: NSButton = { let view = NSButton() view.bezelStyle = .regularSquare view.isBordered = false view.imagePosition = .imageOnly view.title = "" return view }() var needExpandButton: NSButton = { let view = NSButton() view.bezelStyle = .regularSquare view.isBordered = false view.imagePosition = .imageOnly view.image = NSImage(named: "KMImageNameUXIconBtnTriDownNor") return view }() private var _kNormalImage: NSImage? private var _originalHelpTip: String? deinit { if (self.area_ != nil) { self.removeTrackingArea(self.area_!) self.area_ = nil } NotificationCenter.default.removeObserver(self) } class var textFont: NSFont { get { .systemFont(ofSize: 12) } } // class var textNormalColor: NSColor { // get { // KMAppearance.titleColor() // } // } class func fetchTextNormalColor() -> NSColor { return KMAppearance.titleColor() } class func fetchTextSelectedColor() -> NSColor { return 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 let promptV = NSView() self.promptView_ = promptV self.addSubview(promptV) promptV.wantsLayer = true promptV.layer?.cornerRadius = 3 promptV.layer?.backgroundColor = NSColor(red: 1, green: 56/255.0, blue: 25/255.0, alpha: 1).cgColor self.isShowPrompt = false } override func draw(_ dirtyRect: NSRect) { if (self.itemIdentifier == KMToolbarDividerItemIdentifier) { let context = NSGraphicsContext.current?.cgContext KMContextSaveGState(context) KMContextTranslateCTM(context, NSWidth(dirtyRect)/2.0, NSHeight(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 updateTrackingAreas() { super.updateTrackingAreas() let areaBound = NSRect(x: 0, y: 5, width: self.bounds.size.width, height: self.bounds.size.height) if let _area = self.area_, _area.rect.isEmpty == false { if (_area.rect.equalTo(areaBound)) { return } } if (self.area_ != nil) { self.removeTrackingArea(self.area_!) self.area_ = nil } // inVisibleRect activeInKeyWindow self.area_ = NSTrackingArea(rect: areaBound, options: [.mouseEnteredAndExited,.mouseMoved, .activeAlways], owner: self) self.addTrackingArea(self.area_!) } override func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) if let data = self.window?.isKeyWindow, !data { 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.fetchTextNormalColor()) } } } } 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") } if let data = self.window?.isKeyWindow, !data { return } if(self.nameBtn.superview != nil && !self.isSelected) { self.nameBtn.setTitleColor(color: Self.fetchTextNormalColor()) } } public func calculateWidth() -> CGFloat { let iWidth = self._calculateWidth() return iWidth } 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 self.layer?.backgroundColor = self.normalBackgroundColor.cgColor } } } override func interfaceThemeDidChanged(_ appearance: NSAppearance.Name) { super.interfaceThemeDidChanged(appearance) self.nameBtn.setTitleColor(Self.fetchTextNormalColor()) } } // MARK: - Private Methods extension KMToolbarItemView { 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.promptView, view.superview != nil { view.removeFromSuperview() } if let view = self.customizeView { if view.superview != nil { view.removeFromSuperview() } let iWidth = NSWidth(view.bounds) self.addSubview(view) view.km_add_leading_constraint() view.km_add_trailing_constraint() view.km_add_centerY_constraint() view.km_add_width_constraint(constant: iWidth) view.km_add_height_constraint(constant: NSHeight(view.bounds)) self.itemHeight = 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: Self.kDividerWidth) self.itemHeight = 40 return } let offset = Self.kRightMargin let offsetY: CGFloat = 2.0 let offsetX = Self.kHSpace if self.boxImagePosition == .imageOnly { let iWidth = (self.imageViewBtn.image?.size ?? .zero).width + offsetX * 2 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: 0, bottom: offsetY, right: 0)) self.itemHeight = 24 } 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-Self.kExpandWidth) } 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: Self.kExpandWidth) self.needExpandButton.km_add_right_constraint(constant: -offset) } self.layer?.cornerRadius = 6 self.itemHeight = 24 } 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: Self.kExpandWidth) 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) self.itemHeight = 24 } 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()(Self.kImageAboveMinWidth) make?.height.mas_equalTo()(14) 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: Self.kImageAboveMinWidth) 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.5, left: offset, bottom: 0, right: offset)) self.itemHeight = 40 } self.imageViewBox.borderWidth = 1.0 self.addSubview(self.clickButton) self.clickButton.km_add_inset_constraint() if let view = self.promptView { self.addSubview(view) } } private func _calculateWidth() -> CGFloat { if let v = self.customizeView { return NSWidth(v.bounds) } else if self.itemIdentifier == KMToolbarDividerItemIdentifier { return Self.kDividerWidth } else { if self.boxImagePosition == .imageOnly { return (self.imageViewBtn.image?.size ?? .zero).width + Self.kHSpace * 2 } else if self.boxImagePosition == .imageLeft { // Compress 98 = 28(4+20+4) + 62 + 8(4+4) var iWidth = (self.imageViewBtn.image?.size.width ?? 0) + Self.kHSpace * 2 self.nameBtn.sizeToFit() iWidth += self.nameBtn.frame.size.width iWidth += (2 * Self.kHSpace) if self.needExpandAction { iWidth += 8 } return iWidth } else if self.boxImagePosition == .imageExpandLeft { // Security 87 var iWidth = (self.imageViewBtn.image?.size.width ?? 0) + Self.kHSpace self.nameBtn.sizeToFit() iWidth += self.nameBtn.frame.size.width iWidth += Self.kExpandWidth iWidth += Self.kRightMargin return iWidth } else if self.boxImagePosition == .imageAbove { // Panel 33 self.nameBtn.sizeToFit() let iWidth = NSWidth(self.nameBtn.bounds) return max(Self.kImageAboveMinWidth, iWidth) } } return 0 } override func layout() { super.layout() if let view = self.promptView, view.superview != nil { let wh: CGFloat = 6 let y: CGFloat = 3 view.frame = NSMakeRect(NSWidth(self.bounds)-wh, NSHeight(self.bounds)-wh-y, wh, wh) } } }