// // KMToolbarView.swift // PDF Master // // 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(red: 223.0/255.0, green: 225.0/255.0, blue: 229.0/255.0, alpha: 1).cgColor return _moreButton }() private lazy var centerView: NSView = { let view = NSView() return view }() private lazy var leftView: NSView = { let view = NSView() view.wantsLayer = true view.layer?.backgroundColor = NSColor(red: 247/255.0, green: 248/255.0, blue: 250/255.0 , alpha: 1).cgColor return view }() private class KMToolbarView_rightView: NSView {} private lazy var rightView: NSView = { let view = KMToolbarView_rightView() view.wantsLayer = true view.layer?.backgroundColor = NSColor(red: 247/255.0, green: 248/255.0, blue: 250/255.0, alpha: 1).cgColor return view }() convenience init(identifier: String) { self.init() self._toolbarIdentifier = identifier self._addTrackingArea() NotificationCenter.default.addObserver(self, selector: #selector(toolbarCustomChangeNotification), name: KMToolbarCustomChangeNotification, object: nil) } deinit { Swift.debugPrint("KMToolbarView deinit") NotificationCenter.default.removeObserver(self) } // MARK: - Private Methods private func _addTrackingArea() { let trackingArea = NSTrackingArea(rect: self.bounds, options: [.inVisibleRect, .activeInKeyWindow, .mouseMoved], owner: self) self.addTrackingArea(trackingArea) } override func mouseMoved(with event: NSEvent) { // self.window?.mouseMoved(with: event) super.mouseMoved(with: event) } @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(CGRectGetMaxX(self.moreButton.frame), CGRectGetMaxY(self.moreButton.frame)-5), in: self) } private func _updateMainView() { if (self.centerView.superview == nil) { self.addSubview(self.centerView) } self._updateLeftView() self._updateRightView() self.centerView.mas_remakeConstraints { make in make?.right.equalTo()(0) make?.top.equalTo()(self.mas_top)?.offset()(0) make?.bottom.equalTo()(self.mas_bottom)?.offset()(0) make?.left.mas_equalTo()(0) } if(self.contentView.superview == nil) { self.centerView.addSubview(self.contentView) self.contentView.mas_makeConstraints { make in make?.centerX.equalTo()(0) make?.centerY.equalTo()(0) } } for item in self.items { item.removeFromSuperview() } self._items = [] var posX = 0.0 var items: [KMToolbarItemView] = [] var itemIdentifiers = self.delegate?.toolbarDefaultItemIdentifiers?(self) ?? [] var lastItem: KMToolbarItemView? for i in 0 ..< (itemIdentifiers.count-self._invisibleItems.count) { let itemIdentifier = itemIdentifiers[i] // if (![itemIdentifier isKindOfClass:[NSString class]]) { // return; // } var item: KMToolbarItemView? if itemIdentifier == KMToolbarDividerItemIdentifier { item = KMToolbarItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) } else if itemIdentifier == KMNewToolbarSpaceItemIdentifier { item = KMToolbarItemView(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 } if (item == nil) { continue } self.contentView.addSubview(item!) if (lastItem != nil) { if (i == itemIdentifiers.count - 1) { item?.mas_makeConstraints({ make in make?.top.bottom().equalTo()(0) make?.left.equalTo()(lastItem!.mas_right)?.offset()(KMToolbarItemSpace) make?.right.equalTo()(self.contentView.mas_right)?.offset()(0) }) } else { item?.mas_makeConstraints({ make in make?.top.bottom().equalTo()(0) make?.left.equalTo()(lastItem!.mas_right)?.offset()(KMToolbarItemSpace) }) } } else { if (item?.image != nil) { item?.mas_makeConstraints({ make in make?.top.bottom().equalTo()(0) make?.left.equalTo()(0) }) } else { item?.mas_makeConstraints({ make in make?.top.bottom().equalTo()(0) make?.left.equalTo()(0) make?.height.offset()(40) }) } } items.append(item!) lastItem = item posX += item!.frame.size.width } self._items = items self.addSubview(self.moreButton) } // UI 20 16 // item w 28 h 24 private func _updateView() { self._updateLeftView() self._updateRightView() if(self.contentView.superview == nil) { self.addSubview(self.contentView) self.contentView.mas_makeConstraints { make in make?.centerX.equalTo()(0) make?.centerY.equalTo()(0) } } for item in self.items { item.removeFromSuperview() } self._items = [] var posX = 0.0 var items: [KMToolbarItemView] = [] var itemIdentifiers = self.delegate?.toolbarDefaultItemIdentifiers?(self) ?? [] var lastItem: KMToolbarItemView? for i in 0 ..< (itemIdentifiers.count-self._invisibleItems.count) { let itemIdentifier = itemIdentifiers[i] // if (![itemIdentifier isKindOfClass:[NSString class]]) { // return; // } var item: KMToolbarItemView? if itemIdentifier == KMToolbarDividerItemIdentifier { item = KMToolbarItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) } else if itemIdentifier == KMNewToolbarSpaceItemIdentifier { item = KMToolbarItemView(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 } if (item == nil) { continue } self.contentView.addSubview(item!) if (lastItem != nil) { if (i == itemIdentifiers.count - 1) { item?.mas_makeConstraints({ make in make?.top.bottom().equalTo()(0) make?.left.equalTo()(lastItem!.mas_right)?.offset()(KMToolbarItemSpace) make?.right.equalTo()(self.contentView.mas_right)?.offset()(0) }) } else { if itemIdentifier == KMNewToolbarSpaceItemIdentifier { // if ([itemIdentifier isEqualToString:]) { item?.mas_makeConstraints({ make in make?.top.bottom().equalTo()(0) make?.left.equalTo()(lastItem!.mas_right)?.offset()(2) }) } else { if lastItem?.itemIdentifier == KMNewToolbarSpaceItemIdentifier { // if ([lastItem.itemIdentifier isEqualToString:]) { item?.mas_makeConstraints({ make in make?.top.bottom().equalTo()(0) make?.left.equalTo()(lastItem!.mas_right)?.offset()(2) }) } else { item?.mas_makeConstraints({ make in make?.top.bottom().equalTo()(0) make?.left.equalTo()(lastItem!.mas_right)?.offset()(KMToolbarItemSpace) }) } } } } else { if (item?.image != nil) { item?.mas_makeConstraints({ make in make?.top.bottom().equalTo()(0) make?.left.equalTo()(0) }) } else { item?.mas_makeConstraints({ make in make?.top.bottom().equalTo()(0) make?.left.equalTo()(0) make?.height.offset()(40) }) } } items.append(item!) lastItem = item posX += item!.frame.size.width } self._items = items; self.addSubview(self.moreButton) } private func _updateLeftView() { // 刷新前移除视图 for item in self.leftView.subviews { item.removeFromSuperview() } if let itemIdentifiers = self.delegate?.toolbarLeftDefaultItemIdentifiers?(self), itemIdentifiers.count > 0 { if (self.leftView.superview == nil) { self.addSubview(self.leftView) self.leftView.mas_makeConstraints { 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 = KMToolbarItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) } else if itemIdentifier == KMNewToolbarSpaceItemIdentifier { item = KMToolbarItemView(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 } if (item == nil) { continue } self.leftView.addSubview(item!) if (lastItem != nil) { if (i == itemIdentifiers.count - 1) { item?.mas_makeConstraints({ make in make?.top.equalTo()(0) make?.bottom.equalTo()(0) make?.left.equalTo()(lastItem!.mas_right)?.offset()(KMToolbarItemSpace) make?.right.equalTo()(self.leftView.mas_right)?.offset()(0) }) } else { item?.mas_makeConstraints({ make in make?.top.equalTo()(0) make?.bottom.equalTo()(0) make?.left.equalTo()(lastItem!.mas_right)?.offset()(KMToolbarItemSpace) }) } } else { if (i == itemIdentifiers.count - 1) { item?.mas_makeConstraints({ make in make?.top.equalTo()(0) make?.bottom.equalTo()(0) make?.left.equalTo()(self.leftView.mas_left)?.offset()(KMToolbarItemSpace) make?.right.equalTo()(self.leftView.mas_right)?.offset()(-20) }) } else { item?.mas_makeConstraints({ make in make?.top.equalTo()(0) make?.bottom.equalTo()(0) make?.left.equalTo()(20) }) } } lastItem = item } } } private func _updateRightView() { // 刷新前移除视图 for item in self.rightView.subviews { item.removeFromSuperview() } if let itemIdentifiers = self.delegate?.toolbarRightDefaultItemIdentifiers?(self) { if (self.rightView.superview == nil) { self.addSubview(self.rightView) self.rightView.mas_makeConstraints { make in make?.right.equalTo()(self.mas_right)?.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 = KMToolbarItemView(itemIdentifier: itemIdentifier, postition: .imageOnly, withPopMenu: nil) } else if itemIdentifier == KMNewToolbarSpaceItemIdentifier { item = KMToolbarItemView(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 } if (item == nil) { continue } self.rightView.addSubview(item!) if (lastItem != nil) { if (i == itemIdentifiers.count - 1) { item?.mas_makeConstraints({ make in make?.top.equalTo()(0) make?.bottom.equalTo()(0) make?.left.equalTo()(lastItem!.mas_right)?.offset()(KMToolbarItemSpace) make?.right.equalTo()(self.rightView.mas_right)?.offset()(-20) }) } else { item?.mas_makeConstraints({ make in make?.top.equalTo()(0) make?.bottom.equalTo()(0) make?.left.equalTo()(lastItem!.mas_right)?.offset()(KMToolbarItemSpace) }) } } else { if (i == itemIdentifiers.count - 1) { item?.mas_makeConstraints({ make in make?.top.equalTo()(0) make?.bottom.equalTo()(0) make?.left.equalTo()(self.rightView.mas_left)?.offset()(KMToolbarItemSpace) make?.right.equalTo()(self.rightView.mas_right)?.offset()(-20) }) } else { item?.mas_makeConstraints({ make in make?.top.equalTo()(0) make?.bottom.equalTo()(0) make?.left.equalTo()(0) }) } } lastItem = item } } } override func updateLayer() { super.updateLayer() self.layer?.backgroundColor = .clear } // 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) // NotificationCenter.default.addObserver(self, selector: #selector(toolbarCustomChangeNotification), name: KMToolbarCustomChangeNotification, object: nil) let topLine = NSBox(frame: CGRectMake(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) } // NSBox *bottomLine = [[NSBox alloc] initWithFrame:]; let bottomLine = NSBox(frame: CGRectMake(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() } } override func resizeSubviews(withOldSize oldSize: NSSize) { super.resizeSubviews(withOldSize: oldSize) if (self._contentView == nil) { return; } let moreButtonWidth = 30.0 let leftButtonWidth = 10.0 var frame = self.contentView.frame var count = self.items.count var idx = count-1 for item in self.items.reversed() { if (CGRectGetMaxX(item.frame) <= self.frame.size.width-moreButtonWidth-leftButtonWidth) { count = idx break } idx -= 1 } if (count >= self.items.count) { return } frame.size.width = CGRectGetMaxX(self.items[count].frame) if (count == self.items.count-1) { frame.origin.x = (self.frame.size.width-frame.size.width)/2.0; self.moreButton.isHidden = true self._visibleItems = self.items self._invisibleItems = [] self.contentView.mas_remakeConstraints { make in make?.centerX.equalTo()(0) make?.centerY.equalTo()(0) } } else { frame.origin.x = leftButtonWidth self.moreButton.mas_makeConstraints { make in make?.top.bottom().equalTo()(0) make?.width.offset()(moreButtonWidth) make?.right.equalTo()(self.mas_right)?.offset()(0) } self.moreButton.isHidden = false self._visibleItems.removeAll() for i in 0 ... count+1 { self._visibleItems.append(self.items[i]) } // self.invisibleItems = [self.items subarrayWithRange:NSMakeRange(count+1, self.items.count-count-1)]; self.contentView.mas_remakeConstraints { make in make?.left.equalTo()(self.mas_left)?.offset()(10) make?.centerY.equalTo()(0) } } self._invisibleItems.removeAll() for item in self.items.reversed() { if (CGRectGetMaxX(item.frame) <= self.frame.size.width-moreButtonWidth-leftButtonWidth) { item.isHidden = false } else { item.isHidden = true self._invisibleItems.append(item) } } } 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 } @objc func toolbarCustomChangeNotification() { self.reloadData() } }