// // KMToolbarView.swift // PDF Reader Pro // // Created by tangchao on 2023/10/24. // import Cocoa private let KMToolbarSpaceItemWidth = 30.0 private let KMToolbarLineItemWidth = 1.0 private let KMToolbarItemSpace = 8.0 @objc protocol KMToolbarViewDelegate: NSObjectProtocol { @objc optional func toolbar(_ toolbar: KMToolbarView, itemFor identifier: String) -> KMToolbarItemView @objc optional func toolbarAllowedItemIdentifiers(_ toolbar: KMToolbarView) -> [String] @objc optional func toolbarDefaultItemIdentifiers(_ toolbar: KMToolbarView) -> [String] @objc optional func toolbarLeftDefaultItemIdentifiers(_ toolbar: KMToolbarView) -> [String] @objc optional func toolbarRightDefaultItemIdentifiers(_ toolbar: KMToolbarView) -> [String] } @objcMembers class KMToolbarView: NSView { private var _toolbarIdentifier: String? var toolbarIdentifier: String? { get { return self._toolbarIdentifier } } private var _items: [KMToolbarItemView] = [] var items: [KMToolbarItemView] { get { return self._items } } weak var delegate: KMToolbarViewDelegate? private var _visibleItems: [KMToolbarItemView] = [] private var _invisibleItems: [KMToolbarItemView] = [] private var _contentView: NSView? var contentView: NSView { get { if (self._contentView == nil) { self._contentView = NSView() } return self._contentView! } } lazy var moreButton: NSButton = { let _moreButton = NSButton() _moreButton.bezelStyle = .regularSquare _moreButton.isBordered = false _moreButton.font = .systemFont(ofSize: 20) _moreButton.title = " »" _moreButton.isHidden = true _moreButton.target = self _moreButton.action = #selector(_moreButtonAction) _moreButton.wantsLayer = true _moreButton.layer?.backgroundColor = NSColor.clear.cgColor return _moreButton }() var contentInset: NSEdgeInsets = NSEdgeInsetsZero private lazy var centerView: NSView = { let view = NSView() return view }() private lazy var leftView: NSView = { let view = NSView() return view }() private lazy var rightView: NSView = { let view = NSView() return view }() static let kLeftMargin: CGFloat = 8 static let kHSpace: CGFloat = 8 static let kRightMargin: CGFloat = 8 static let kMoreWidth: CGFloat = 30 private var _leftWidth: CGFloat = 0 private var _centerWidth: CGFloat = 0 private var _rightWidth: CGFloat = 0 var allowsUserCustomization = false private var configWindowC_: KMToolbarConfigWindowController? deinit { KMPrint("KMToolbarView deinit") } convenience init(identifier: String) { self.init() self._toolbarIdentifier = identifier self._addTrackingArea() } override func menu(for event: NSEvent) -> NSMenu? { if self.allowsUserCustomization == false { return super.menu(for: event) } let menu = NSMenu() let item = NSMenuItem(title: NSLocalizedString("Customize Toolbar", comment: ""), action: #selector(_customToolbarItemAction), keyEquivalent: "") menu.addItem(item) return menu } // MARK: - Private Methods @objc private func _customToolbarItemAction(_ sender: NSMenuItem?) { if self.configWindowC_ == nil { self.configWindowC_ = KMToolbarConfigWindowController() } var leftCellIdentifiers = self.delegate?.toolbarLeftDefaultItemIdentifiers?(self) ?? [] if leftCellIdentifiers.contains(KMDocumentHomeToolbarItemIdentifier) { leftCellIdentifiers.removeObject(KMDocumentHomeToolbarItemIdentifier) } self.configWindowC_?.leftCellIdentifiers = leftCellIdentifiers self.configWindowC_?.centerCellIdentifiers = (self.delegate?.toolbarDefaultItemIdentifiers?(self) ?? []) self.configWindowC_?.rightCellIdentifiers = (self.delegate?.toolbarRightDefaultItemIdentifiers?(self) ?? []) self.configWindowC_?.defaultCellIdentifiers = self.delegate?.toolbarAllowedItemIdentifiers?(self) ?? [] self.window?.beginSheet((self.configWindowC_?.window)!, completionHandler: { [weak self] resp in self?.configWindowC_ = nil if resp == .OK { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { KMDataManager.default.toolbarConfigDataUpdated = true self?.reloadData() KMDataManager.default.toolbarConfigDataUpdated = false } } }) } private func _addTrackingArea() { let trackingArea = NSTrackingArea(rect: self.bounds, options: [.inVisibleRect, .activeInKeyWindow, .mouseMoved], owner: self) self.addTrackingArea(trackingArea) } @objc private func _moreButtonAction() { let menu = NSMenu() for item in self._invisibleItems { if (item.itemIdentifier == KMToolbarDividerItemIdentifier) { menu.addItem(NSMenuItem.separator()) } else if (item.menuFormRepresentation != nil) { menu.addItem(item.menuFormRepresentation!) } } menu.popUp(positioning: menu.items.first, at: NSMakePoint(NSMaxX(self.moreButton.frame), NSMaxY(self.moreButton.frame)-5), in: self) } private func _updateMainView(_ left: Bool = true, _ right: Bool = true) { if (self.centerView.superview == nil) { self.addSubview(self.centerView) } if left { self._updateLeftView() } if right { self._updateRightView() } if(self.contentView.superview == nil) { self.centerView.addSubview(self.contentView) } for item in self.items { item.removeFromSuperview() } self._items = [] var items: [KMToolbarItemView] = [] let itemIdentifiers = self.delegate?.toolbarDefaultItemIdentifiers?(self) ?? [] var lastItem: KMToolbarItemView? var iWidth: CGFloat = 0 // -self._invisibleItems.count for i in 0 ..< (itemIdentifiers.count) { let itemIdentifier = itemIdentifiers[i] var item: KMToolbarItemView? if itemIdentifier == KMToolbarDividerItemIdentifier { item = KMToolbarCustomItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) } else if itemIdentifier == KMNewToolbarSpaceItemIdentifier { item = KMToolbarCustomItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) item?.layer?.backgroundColor = .clear item?.imageViewBox.borderColor = .clear item?.layer?.cornerRadius = 0.0 } else if let data = self.delegate?.toolbar?(self, itemFor: itemIdentifier) { item = data } guard let data = item else { continue } self.contentView.addSubview(data) items.append(data) iWidth += data.itemWidth if lastItem != nil { // + 前一个的间距 iWidth += Self.kHSpace } lastItem = item } self._items = items self._centerWidth = iWidth self.addSubview(self.moreButton) } // UI 20 16 // item w 28 h 24 private func _updateView(_ left: Bool = true, _ right: Bool = true) { if left { self._updateLeftView() } if right { self._updateRightView() } if(self.contentView.superview == nil) { self.addSubview(self.contentView) } for item in self.items { item.removeFromSuperview() } self._items = [] var items: [KMToolbarItemView] = [] let itemIdentifiers = self.delegate?.toolbarDefaultItemIdentifiers?(self) ?? [] var lastItem: KMToolbarItemView? // -self._invisibleItems.count var iWidth: CGFloat = 0 for i in 0 ..< itemIdentifiers.count { let itemIdentifier = itemIdentifiers[i] var item: KMToolbarItemView? if itemIdentifier == KMToolbarDividerItemIdentifier { item = KMToolbarCustomItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) } else if itemIdentifier == KMNewToolbarSpaceItemIdentifier { item = KMToolbarCustomItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) item?.layer?.backgroundColor = .clear item?.imageViewBox.borderColor = .clear item?.layer?.cornerRadius = 0.0 } else if let data = self.delegate?.toolbar?(self, itemFor: itemIdentifier) { item = data } guard let data = item else { continue } self.contentView.addSubview(data) items.append(data) iWidth += data.itemWidth if lastItem != nil { // + 前一个的间距 iWidth += Self.kHSpace } lastItem = item } self._items = items self._centerWidth = iWidth self.addSubview(self.moreButton) } private func _updateLeftView() { // 刷新前移除视图 for item in self.leftView.subviews { item.removeFromSuperview() } if (self.leftView.superview != nil) { self.leftView.removeFromSuperview() } var iWidth: CGFloat = 0 if let itemIdentifiers = self.delegate?.toolbarLeftDefaultItemIdentifiers?(self), itemIdentifiers.count > 0 { if (self.leftView.superview == nil) { self.addSubview(self.leftView) } self.leftView.mas_remakeConstraints { make in make?.left.equalTo()(self.mas_left)?.offset()(0) make?.top.equalTo()(self.mas_top)?.offset()(0) make?.bottom.equalTo()(self.mas_bottom)?.offset()(0) } var lastItem: KMToolbarItemView? for i in 0 ..< itemIdentifiers.count { let itemIdentifier = itemIdentifiers[i] var item: KMToolbarItemView? if itemIdentifier == KMToolbarDividerItemIdentifier { item = KMToolbarCustomItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) } else if itemIdentifier == KMNewToolbarSpaceItemIdentifier { item = KMToolbarCustomItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) item?.layer?.backgroundColor = .clear item?.imageViewBox.borderColor = .clear item?.layer?.cornerRadius = 0.0 } else if let data = self.delegate?.toolbar?(self, itemFor: itemIdentifier) { item = data } guard let data = item else { continue } self.leftView.addSubview(data) iWidth += data.itemWidth if lastItem != nil { // + 前一个的间距 iWidth += Self.kHSpace } lastItem = item } iWidth += (Self.kLeftMargin + Self.kHSpace) } self._leftWidth = iWidth } private func _updateRightView() { // 刷新前移除视图 for item in self.rightView.subviews { item.removeFromSuperview() } if (self.rightView.superview != nil) { self.rightView.removeFromSuperview() } var iWidth: CGFloat = 0 if let itemIdentifiers = self.delegate?.toolbarRightDefaultItemIdentifiers?(self), itemIdentifiers.isEmpty == false { if (self.rightView.superview == nil) { self.addSubview(self.rightView) } var lastItem: KMToolbarItemView? for i in 0 ..< itemIdentifiers.count { let itemIdentifier = itemIdentifiers[i] var item: KMToolbarItemView? if itemIdentifier == KMToolbarDividerItemIdentifier { item = KMToolbarCustomItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) } else if itemIdentifier == KMNewToolbarSpaceItemIdentifier { item = KMToolbarCustomItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) item?.layer?.backgroundColor = .clear item?.imageViewBox.borderColor = .clear item?.layer?.cornerRadius = 0.0; } else if let data = self.delegate?.toolbar?(self, itemFor: itemIdentifier) { item = data } guard let data = item else { continue } self.rightView.addSubview(data) iWidth += data.itemWidth if lastItem != nil { // + 前一个的间距 iWidth += Self.kHSpace } lastItem = item } iWidth += Self.kRightMargin } self._rightWidth = iWidth } override func updateLayer() { super.updateLayer() if KMAppearance.isDarkMode() { self.layer?.backgroundColor = KMAppearance.Layout.l0Color().cgColor } else { self.layer?.backgroundColor = .clear } for item in self._items { item.interfaceThemeDidChanged(self.window?.appearance?.name ?? .aqua) item.customizeView?.interfaceThemeDidChanged(self.appearance?.name ?? .aqua) } } // MARK: - View Methods override func viewWillMove(toWindow newWindow: NSWindow?) { super.viewWillMove(toWindow: newWindow) if (newWindow != nil && self._contentView == nil) { let leftitemIdentifiers = self.delegate?.toolbarLeftDefaultItemIdentifiers?(self) ?? [] if (leftitemIdentifiers.count > 0) { self._updateMainView() }else { self._updateView() } self.resizeSubviews(withOldSize: self.frame.size) let topLine = NSBox(frame: NSMakeRect(0, 0, self.frame.size.width, 0.5)) topLine.boxType = .separator topLine.fillColor = .black self.addSubview(topLine) topLine.mas_makeConstraints { make in make?.left.right().equalTo()(self) make?.bottom.equalTo()(self.mas_bottom)?.offset()(-0.5) make?.height.offset()(0.5) } let bottomLine = NSBox(frame: NSMakeRect(0, 0, self.frame.size.width, 0.5)) bottomLine.boxType = .separator bottomLine.fillColor = .black self.addSubview(bottomLine) bottomLine.mas_makeConstraints { make in make?.left.right().equalTo()(self) make?.top.equalTo()(self.mas_top)?.offset()(0.1) make?.height.offset()(0.5) } } if (newWindow != nil) { self._addTrackingArea() } } private func _layoutSubViewsIfNeed() { if (self._contentView == nil) { return } let moreButtonWidth = 18.0 let leftW = self._leftWidth let contentW = self._centerWidth let rightW = self._rightWidth // KMPrint("width: \(leftW), \(contentW), \(rightW)") // KMPrint("\(self.frame.size.width)") let width = NSWidth(self.bounds) - self.contentInset.left - self.contentInset.right let height = NSHeight(self.bounds) let leftMargin = Self.kLeftMargin let hSpace = Self.kHSpace let itemH: CGFloat = 40 var itemY: CGFloat = (height-itemH)*0.5 let contentLength = leftW + contentW + rightW if (contentLength <= 0) { return } self._invisibleItems.removeAll() if width >= contentLength { // 左 + 中 + 右 显示(全) self.moreButton.isHidden = true let offset = width - contentLength self.leftView.frame = NSMakeRect(0, 0, leftW, height) var itemX: CGFloat = leftMargin for sv in self.leftView.subviews { guard let item = sv as? KMToolbarItemView else { continue } item.isHidden = false itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace } if self.isMain() { self.centerView.frame = NSMakeRect(leftW, 0, contentW + offset, height) self.contentView.frame = NSMakeRect(offset*0.5, 0, contentW, height) } else { self.contentView.frame = NSMakeRect(offset*0.5, 0, contentW, height) } itemX = 0 for sv in self.contentView.subviews { guard let item = sv as? KMToolbarItemView else { continue } item.isHidden = false itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace } self.rightView.frame = NSMakeRect(leftW+contentW+offset, 0, rightW, height) itemX = 0 for sv in self.rightView.subviews { guard let item = sv as? KMToolbarItemView else { continue } item.isHidden = false itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace } return } else { // 显示更多 self.moreButton.isHidden = false self.moreButton.mas_remakeConstraints { make in make?.top.bottom().equalTo()(0) make?.width.mas_equalTo()(moreButtonWidth) make?.right.mas_equalTo()(-self.contentInset.right) } if width >= leftW + contentW { // 左 + 中 显示(全) 右显示不全 let offset = width - leftW - contentW self.leftView.frame = NSMakeRect(0, 0, leftW, height) var itemX: CGFloat = leftMargin for sv in self.leftView.subviews { guard let item = sv as? KMToolbarItemView else { continue } item.isHidden = false itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace } if self.isMain() { self.centerView.frame = NSMakeRect(leftW, 0, contentW, height) self.contentView.frame = NSMakeRect(0, 0, contentW + offset, height) } else { self.contentView.frame = NSMakeRect(leftW, 0, contentW, height) } itemX = 0 for sv in self.contentView.subviews { guard let item = sv as? KMToolbarItemView else { continue } item.isHidden = false itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace } self.rightView.frame = NSMakeRect(leftW+contentW, 0, offset, height) itemX = 0 for sv in self.rightView.subviews { guard let item = sv as? KMToolbarItemView else { continue } itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace let itemMinX = NSMinX(self.rightView.frame) + itemX if itemMinX <= width { item.isHidden = false } else { item.isHidden = true self._invisibleItems.append(item) } } return } else if width >= leftW { // 左 显示(全) 中 显示不全 右不显示 let offset = width - leftW self.leftView.frame = NSMakeRect(0, 0, leftW, height) var itemX: CGFloat = leftMargin for sv in self.leftView.subviews { guard let item = sv as? KMToolbarItemView else { continue } itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace item.isHidden = false } if self.isMain() { self.centerView.frame = NSMakeRect(leftW, 0, offset, height) self.contentView.frame = NSMakeRect(0, 0, offset, height) } else { self.contentView.frame = NSMakeRect(leftW, 0, offset, height) } itemX = 0 for sv in self.contentView.subviews { guard let item = sv as? KMToolbarItemView else { continue } itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace let itemMinX = leftW + itemX if itemMinX <= width { item.isHidden = false } else { item.isHidden = true self._invisibleItems.append(item) } } self.rightView.frame = NSMakeRect(leftW+contentW, 0, 0, height) itemX = 0 for sv in self.rightView.subviews { guard let item = sv as? KMToolbarItemView else { continue } itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace item.isHidden = true self._invisibleItems.append(item) } return } else { // 左显示不全 中 + 右 不显示 let offset = width - leftW self.leftView.frame = NSMakeRect(0, 0, offset, height) var itemX: CGFloat = leftMargin for sv in self.leftView.subviews { guard let item = sv as? KMToolbarItemView else { continue } itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace if itemX <= width { item.isHidden = false } else { item.isHidden = true self._invisibleItems.append(item) } } if self.isMain() { self.centerView.frame = NSMakeRect(leftW, 0, 0, height) self.contentView.frame = NSMakeRect(0, 0, 0, height) } else { self.contentView.frame = NSMakeRect(leftW, 0, 0, height) } itemX = 0 for sv in self.contentView.subviews { guard let item = sv as? KMToolbarItemView else { continue } itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace item.isHidden = true self._invisibleItems.append(item) } self.rightView.frame = NSMakeRect(leftW+contentW, 0, 0, height) itemX = 0 for sv in self.rightView.subviews { guard let item = sv as? KMToolbarItemView else { continue } itemY = (height-item.itemHeight)*0.5 item.frame = NSMakeRect(itemX, itemY, item.itemWidth, item.itemHeight) itemX += item.itemWidth + hSpace item.isHidden = true self._invisibleItems.append(item) } return } } } func hasLeft() -> Bool { if let items = self.delegate?.toolbarLeftDefaultItemIdentifiers?(self) { return items.count > 0 } return false } func hasRight() -> Bool { if let items = self.delegate?.toolbarRightDefaultItemIdentifiers?(self) { return items.count > 0 } return false } func hasCenter() -> Bool { if let items = self.delegate?.toolbarDefaultItemIdentifiers?(self) { return items.count > 0 } return false } func isMain() -> Bool { if self.contentView.superview == self { return false } return true } override func resizeSubviews(withOldSize oldSize: NSSize) { super.resizeSubviews(withOldSize: oldSize) self._layoutSubViewsIfNeed() } func reloadData() { let leftitemIdentifiers = self.delegate?.toolbarLeftDefaultItemIdentifiers?(self) ?? [] if (leftitemIdentifiers.count > 0) { self._updateMainView() } else { self._updateView() } self.resizeSubviews(withOldSize: self.frame.size) } func itemForItemIdentifier(_ itemIdentifier: String) -> KMToolbarItemView? { for item in self.items { if (item.itemIdentifier == itemIdentifier) { return item } } return nil } }