// // KMDesignSelect.swift // PDF Reader Pro // // Created by wanjun on 2023/2/22. // import Cocoa @objc protocol KMSelectPopButtonDelegate: NSObjectProtocol { @objc optional func km_controlTextDidEndEditing(_ obj: KMDesignSelect) @objc optional func km_controlTextDidChange(_ obj: KMDesignSelect) @objc optional func km_SelectPopoverWillShow(_ obj: KMDesignSelect) func km_comboBoxSelectionDidChange(_ obj: KMDesignSelect) } @objc enum SelectType : Int { case PopButton = 0 case Combox } class KMSelectCell: NSTextFieldCell { var borderThickness: CGFloat = 1 var offset: CGFloat = 0.0 override func drawingRect(forBounds theRect: NSRect) -> NSRect { var newRect:NSRect = super.drawingRect(forBounds: theRect) let textSize:NSSize = self.cellSize(forBounds: theRect) // let heightDelta:CGFloat = newRect.size.height - textSize.height // if heightDelta > 0 { // newRect.size.height = textSize.height // newRect.origin.y += heightDelta * 0.5 // } else { // newRect.size.height = textSize.height // newRect.origin.y += heightDelta // } let originY = ((self.controlView?.bounds.size.height)! - textSize.height) / 2 newRect.size.height = textSize.height newRect.origin.y += originY newRect.origin.x += offset newRect.size.width = theRect.width - offset*2 // newRect.fill() return newRect } override func draw(withFrame cellFrame: NSRect, in controlView: NSView) { // Area that covers the NSTextField itself. That is the total height minus our custom border size. let interiorFrame = NSRect(x: 0, y: 0, width: cellFrame.width, height: cellFrame.height - borderThickness) let path = NSBezierPath() path.lineWidth = borderThickness // Line width is at the center of the line. path.move(to: NSPoint(x: 0, y: cellFrame.height)) path.line(to: NSPoint(x: cellFrame.width, y: cellFrame.height)) path.line(to: NSPoint(x: cellFrame.width, y: 0)) path.line(to: NSPoint(x: 0, y: 0)) NSColor.clear.setStroke() path.stroke() // Pass in area minus the border thickness in the height drawInterior(withFrame: interiorFrame, in: controlView) } } @objcMembers class KMDesignSelect: NSViewController { @IBOutlet weak var mainBox: KMBox! @IBOutlet weak var selectBox: NSBox! @IBOutlet weak var textField: NSTextField! @IBOutlet weak var imageView: NSImageView! @IBOutlet weak var horizontalPadding_spacing: NSLayoutConstraint! @IBOutlet weak var itemSpacing_spacing: NSLayoutConstraint! @IBOutlet weak var imageViewWidth_spacing: NSLayoutConstraint! @IBOutlet weak var imageViewHeight_spacing: NSLayoutConstraint! var imageWidth: Float = 12.0 // 图片宽度 var imageHeight: Float = 12.0 // 图片高度 var horizontalPadding: Float = 8.0 var itemSpacing: Float = 8.0 var borderColor: NSColor = .clear// 边框颜色 var borderColor_hov: NSColor = .clear// 边框颜色 var borderColor_focus: NSColor = .clear// 边框颜色 var borderColor_disabled: NSColor = .clear// 边框颜色 var borderColor_errordef: NSColor = .clear// 边框颜色 var borderColor_errorfocus: NSColor = .clear// 边框颜色 var cornerRadius: Float = 0.0// 边框圆角 var cornerRadius_hov: Float = 0.0// 边框圆角 var cornerRadius_focus: Float = 0.0// 边框圆角 var cornerRadius_disabled: Float = 0.0// 边框圆角 var cornerRadius_errordef: Float = 0.0// 边框圆角 var cornerRadius_errorfocus: Float = 0.0// 边框圆角 var borderWidth: Float = 1.0// 边框宽度 var borderWidth_hov: Float = 1.0// 边框宽度 var borderWidth_focus: Float = 1.0// 边框宽度 var borderWidth_disabled: Float = 1.0// 边框宽度 var borderWidth_errordef: Float = 1.0// 边框宽度 var borderWidth_errorfocus: Float = 1.0// 边框宽度 var background: NSColor = .clear// 背景颜色 var background_hov: NSColor = .clear// 背景颜色 var background_focus: NSColor = .clear// 背景颜色 var background_disabled: NSColor = .clear// 背景颜色 var background_errordef: NSColor = .clear// 背景颜色 var background_errorfocus: NSColor = .clear// 背景颜色 var textColor: NSColor = .black // 内容颜色 var textColor_hov: NSColor = .black // 内容颜色 var textColor_focus: NSColor = .black // 内容颜色 var textColor_disabled: NSColor = .black // 内容颜色 var textColor_errordef: NSColor = .black // 内容颜色 var textColor_errorfocus: NSColor = .black // 内容颜色 var lineHeight: CGFloat = 20.0 // 默认 内容行高 var lineHeight_hov: CGFloat = 20.0 // 默认 内容行高 var lineHeight_focus: CGFloat = 20.0 // 默认 内容行高 var lineHeight_disabled: CGFloat = 20.0 // 默认 内容行高 var lineHeight_errordef: CGFloat = 20.0 // 默认 内容行高 var lineHeight_errorfocus: CGFloat = 20.0 // 默认 内容行高 var font: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体 var font_hov: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体 var font_focus: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体 var font_disabled: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体 var font_errordef: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体 var font_errorfocus: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体 var textbg_errorfocus: NSColor = .clear // 正在输入高亮 var _image: NSImage = NSImage(named: "icon_btn_arrow_gray_down_s_norm_false")! var _stringValue: String = ""// 内容 var _toolTip: String = "" // 提示文字 // button 通用属性 var action: Selector? // 点击事件 var target: AnyObject? // 对象目标 var _enabled: Bool = true // 是否可点击 var _state: KMDesignTokenState = .Norm var canHover: Bool = true // 是否可悬浮 var _isHidden: Bool = false // 是否隐藏 var _editable: Bool = false // 是否允许编辑 var _alignment: NSTextAlignment = .left //对齐 var placeholderString: String = "" { // 预设文字 didSet { self.textField.placeholderString = self.placeholderString } } var lineBreakMode: NSLineBreakMode = .byTruncatingTail //文字超出显示 var buttonType: SelectType = .PopButton var items: [String] = [] open weak var delete: KMSelectPopButtonDelegate? var _indexOfSelectedItem: Int = 0 var _numberOfItems: Int = 0 var disItems: [String] = [] var isScrollPop = false var createFilePopover: NSPopover = NSPopover.init() var popoverBehavior: NSPopover.Behavior = .semitransient var popViewController: KMHomePopViewController? var popViewControllerBackground: NSColor? var popViewControllerTextColor: NSColor? var popViewControllerEnterFillColor: NSColor? init(withType type: SelectType) { super.init(nibName: "KMDesignSelect", bundle: nil) self.buttonType = type } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() // Do view setup here. textField.isSelectable = false if (buttonType == .PopButton) { self.mainBox.contentView = selectBox textField.delegate = self self.select(bg: "select.m.bg.norm", text: "select.m.mac-text.act") self.select(bg: "select.m.bg.hov", text: "select.m.mac-text.act", state: .Hov) self.select(bg: "select.m.bg.focus", text: "select.m.mac-text.act", state: .Focus) self.select(bg: "select.m.bg.dis", text: "select.m.mac-text.dis", state: .Disabled) self.select(bg: "select.m.bg.error-def", text: "select.m.mac-text.act", state: .Error_def) self.select(bg: "select.m.bg.error-focus", text: "select.m.mac-text.act", textbg: "select.m.bg-text", state: .Error_focus) } else if (buttonType == .Combox) { self.mainBox.contentView = selectBox textField.delegate = self self.select(bg: "select.m.bg.norm", text: "select.m.mac-text.act") self.select(bg: "select.m.bg.hov", text: "select.m.mac-text.act", state: .Hov) self.select(bg: "select.m.bg.focus", text: "select.m.mac-text.act", state: .Focus) self.select(bg: "select.m.bg.dis", text: "select.m.mac-text.dis", state: .Disabled) self.select(bg: "select.m.bg.error-def", text: "select.m.mac-text.act", state: .Error_def) self.select(bg: "select.m.bg.error-focus", text: "select.m.mac-text.act", textbg: "select.m.bg-text", state: .Error_focus) } self.mainBox.canHover = true self.mainBox.canClick = true self.mainBox.downCallback = { [unowned self](downEntered, mouseBox, event) -> Void in if self.enabled { if downEntered { self.canHover = false self.mainBoxAction(mouseBox) } } } self.mainBox.moveCallback = { [unowned self](mouseEntered: Bool, mouseBox: KMBox) -> Void in if !self.createFilePopover.isShown && self.canHover && (self.state != .Disabled) { self.canHover = true if mouseEntered { self.state = .Hov } else { self.state = .Norm } self.updateUI() } } createFilePopover.delegate = self } // MARK: Get、Set var stringValue: String { get { return _stringValue } set { _stringValue = newValue let paragraphStyle = NSMutableParagraphStyle() if (state == .Norm) { paragraphStyle.lineSpacing = lineHeight } else if (state == .Hov) { paragraphStyle.lineSpacing = lineHeight_hov } else if (state == .Focus) { paragraphStyle.lineSpacing = lineHeight_focus } else if (state == .Disabled) { paragraphStyle.lineSpacing = lineHeight_disabled } else if (state == .Error_def) { paragraphStyle.lineSpacing = lineHeight_errordef } else if (state == .Error_focus) { paragraphStyle.lineSpacing = lineHeight_errorfocus } paragraphStyle.lineBreakMode = .byTruncatingTail paragraphStyle.alignment = alignment textField.attributedStringValue = NSAttributedString(string: _stringValue, attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]) } } var alert: Bool = false { didSet { updateUI() } } var enabled: Bool { get { return _enabled } set { _enabled = newValue if _enabled { _state = .Norm } else { _state = .Disabled } updateUI() } } var state: KMDesignTokenState { get { return _state } set { _state = newValue updateUI() } } var isHidden: Bool { get { return _isHidden } set { _isHidden = newValue self.view.isHidden = _isHidden } } var editable: Bool { get { return _editable } set { _editable = newValue textField.isEditable = _editable } } var indexOfSelectedItem: Int { get { return _indexOfSelectedItem } set { _indexOfSelectedItem = newValue } } var numberOfItems: Int { get { return items.count } } var image: NSImage { get { return _image } set { _image = newValue imageView.image = _image } } var toolTip: String { get { return _toolTip } set { _toolTip = newValue if _toolTip != "" { mainBox.toolTip = _toolTip } } } var alignment: NSTextAlignment { get { return _alignment } set { _alignment = newValue textField.alignment = alignment } } // MARK: Private Methods func updateUI() -> Void { if (state == .Norm) { selectBox.fillColor = background selectBox.borderWidth = CGFloat(borderWidth) selectBox.cornerRadius = CGFloat(cornerRadius) selectBox.borderColor = borderColor textField.textColor = textColor textField.font = font imageView.image = NSImage(named: "KMImageNameSelectNormal") } else if (state == .Hov) { selectBox.fillColor = background_hov selectBox.borderWidth = CGFloat(borderWidth_hov) selectBox.cornerRadius = CGFloat(cornerRadius_hov) selectBox.borderColor = borderColor_hov textField.textColor = textColor_hov textField.font = font_hov imageView.image = NSImage(named: "KMImageNameSelectHover") } else if (state == .Focus) { selectBox.fillColor = background_focus selectBox.borderWidth = CGFloat(borderWidth_focus) selectBox.cornerRadius = CGFloat(cornerRadius_focus) selectBox.borderColor = borderColor_focus textField.textColor = textColor_focus textField.font = font_focus imageView.image = NSImage(named: "KMImageNameSelectFocus") } else if (state == .Disabled) { selectBox.fillColor = background_disabled selectBox.borderWidth = CGFloat(borderWidth_disabled) selectBox.cornerRadius = CGFloat(cornerRadius_disabled) selectBox.borderColor = borderColor_disabled textField.textColor = textColor_disabled textField.font = font_disabled imageView.image = NSImage(named: "KMImageNameSelectDisable") } else if (state == .Error_def) { selectBox.fillColor = background_errordef selectBox.borderWidth = CGFloat(borderWidth_errordef) selectBox.cornerRadius = CGFloat(cornerRadius_errordef) selectBox.borderColor = borderColor_errordef textField.textColor = textColor_errordef textField.font = font_errordef imageView.image = NSImage(named: "KMImageNameSelectNormal") } else if (state == .Error_focus) { selectBox.fillColor = background_errorfocus selectBox.borderWidth = CGFloat(borderWidth_errorfocus) selectBox.cornerRadius = CGFloat(cornerRadius_errorfocus) selectBox.borderColor = borderColor_errorfocus textField.textColor = textColor_errorfocus textField.font = font_errorfocus imageView.image = NSImage(named: "KMImageNameSelectNormal") } if alert == true { selectBox.borderColor = NSColor.km_init(hex: "#F3465B") } else { selectBox.borderColor = selectBox.borderColor } imageViewWidth_spacing.constant = CGFloat(imageWidth) imageViewHeight_spacing.constant = CGFloat(imageHeight) horizontalPadding_spacing.constant = CGFloat(horizontalPadding) itemSpacing_spacing.constant = CGFloat(itemSpacing) textField.isEditable = editable // textField.placeholderString = placeholderString textField.lineBreakMode = lineBreakMode popViewController?.updateUI() } func removeAllItems() { items = [] } func addItems(withObjectValues objects: [String]) { items = objects } func selectItem(at index: Int) { if items.count > 0 && index < items.count { stringValue = items[index] self.indexOfSelectedItem = index } } // MARK: Action func mainBoxAction(_ sender: Any) -> Void { self.delete?.km_SelectPopoverWillShow?(self) if createFilePopover.isShown { createFilePopover.close() } else { var vc: KMHomePopViewController? if (self.isScrollPop) { vc = KMScrollPopViewController() } else { vc = KMHomePopViewController() } if (self.popViewControllerBackground != nil) { vc?.background = self.popViewControllerBackground! } if self.popViewControllerBackground != nil { vc?.textColor = self.popViewControllerTextColor! } if self.popViewControllerBackground != nil { vc?.enterFillColor = self.popViewControllerEnterFillColor! } let _ = vc?.initWithPopViewDataArr(items) self.popViewController = vc createFilePopover.contentViewController = vc if (self.stringValue.isEmpty == false) { vc?.selectedItems = [self.stringValue] } createFilePopover.animates = true createFilePopover.behavior = self.popoverBehavior createFilePopover.setValue(true, forKey: "shouldHideAnchor") createFilePopover.show(relativeTo: CGRect(x: view.bounds.origin.x, y: 10, width: view.bounds.size.width, height: view.bounds.size.height), of: view, preferredEdge: .minY) var width = mainBox.frame.width for i in items { let w = i.getTextRectSize(font: .systemFont(ofSize: 14.0), size: CGSize(width: CGFloat(MAXFLOAT), height: 32.0)).width+12*2 if width < w { width = w } } vc?.customBoxWidthLayoutConstraint.constant = width vc?.downCallback = { [unowned self] (downEntered: Bool, count: String) -> Void in if downEntered { self.stringValue = count let current = self.items.firstIndex(of: count) ?? 0 self.indexOfSelectedItem = current self.delete?.km_comboBoxSelectionDidChange(self) self.updateUI() self.createFilePopover.close() } } } } } extension KMDesignSelect: NSTextFieldDelegate { func controlTextDidChange(_ obj: Notification) { let textfield = obj.object as! NSTextField self.stringValue = textfield.stringValue self.delete?.km_controlTextDidChange?(self) } func controlTextDidEndEditing(_ obj: Notification) { let textfield = obj.object as! NSTextField self.stringValue = textfield.stringValue self.delete?.km_controlTextDidEndEditing?(self) } } extension KMDesignSelect: NSPopoverDelegate { func popoverWillShow(_ notification: Notification) { let popover = notification.object as! NSPopover if (createFilePopover == popover) { self.state = .Focus self.canHover = false let vc = createFilePopover.contentViewController! as! KMHomePopViewController vc.disItems = disItems } } func popoverWillClose(_ notification: Notification) { let popover = notification.object as! NSPopover if (createFilePopover == popover) { self.state = .Norm self.canHover = true } } }