// // KMCustomStepperView.swift // PDF Reader Pro // // Created by tangchao on 2023/6/8. // // 自定义步进器 class KMCustomStepperView: KMHoverView { // private lazy var content_stackView: NSStackView = { // let view = NSStackView() // // 排列方向 默认为水平(horizontal) // view.orientation = .horizontal //// view.distribution = .fillProportionally // // 对齐方式 //// view.alignment = .centerY // view.spacing = 1 // // return view // }() private var _contentView = NSView() private var _backgroundView = NSView() var backgroundView: NSView { get { return self._backgroundView } } private var _textField = KMTextField() var textField: KMTextField { get { return self._textField } } private var _right_v_line = NSView() private var _right_up_button = NSButton() private var _right_h_line = NSView() private var _right_down_button = NSButton() private var _right_up_hover = KMHoverView() private var _right_down_hover = KMHoverView() var contentInset: NSEdgeInsets = NSEdgeInsetsZero { didSet { self.needsLayout = true } } var contentOffset: CGFloat = 8 { didSet { self.needsLayout = true } } var upItemImage: NSImage? { didSet { self._right_up_button.image = self.upItemImage } } var downItemImage: NSImage? { didSet { self._right_down_button.image = self.downItemImage } } var itemWidth: CGFloat = 20 { didSet { self.needsLayout = true } } var itemHeight: CGFloat = 14 { didSet { self.needsLayout = true } } var horizontalDividerHeight: CGFloat = 1 { didSet { self.needsLayout = true } } var horizontalDividerColor: NSColor = .lightGray { didSet { self._right_h_line.wantsLayer = true self._right_h_line.layer?.backgroundColor = self.horizontalDividerColor.cgColor } } var verticalDividerWidth: CGFloat = 1 { didSet { self.needsLayout = true } } var verticalDividerColor: NSColor = .lightGray { didSet { self._right_v_line.wantsLayer = true self._right_v_line.layer?.backgroundColor = self.verticalDividerColor.cgColor } } var kmEnabled = true { didSet { self.textField.isEnabled = self.kmEnabled self._right_up_button.isEnabled = self.kmEnabled self._right_down_button.isEnabled = self.kmEnabled } } var minValue: Double = CGFLOAT_MIN var maxValue: Double = CGFLOAT_MAX var increment: Double = 1 var autorepeat = true // var valueWraps = false private var _kmValue: Double = 0 var kmValue: Double { get { return self._kmValue } set { self._kmValue = newValue self.textField.stringValue = String(format: "%.f", self._kmValue) } } var rightHoverAction: ((NSButton, KMHoverAction)->Void)? weak var delegate: KMTextFieldDelegate? override var isFlipped: Bool { return true } override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.initSubViews() self.initDefaultValue() } required init?(coder: NSCoder) { super.init(coder: coder) self.initSubViews() self.initDefaultValue() } func initSubViews() { self.addSubview(self._contentView) self._contentView.addSubview(self._backgroundView) self._contentView.addSubview(self._textField) self._contentView.addSubview(self._right_v_line) self._contentView.addSubview(self._right_up_button) self._contentView.addSubview(self._right_h_line) self._contentView.addSubview(self._right_down_button) self._right_up_button.target = self self._right_up_button.action = #selector(_right_up_button_action) self._right_down_button.target = self self._right_down_button.action = #selector(_right_down_button_action) self._textField.firstResponderHandler = { [unowned self] result in self.delegate?.km_didBecomeFirstResponder(textField: self) } self._contentView.addSubview(self._right_up_hover, positioned: .below, relativeTo: self._right_up_button) self._contentView.addSubview(self._right_down_hover, positioned: .below, relativeTo: self._right_down_button) self._right_up_hover.hoverAction = { [unowned self] _, action in guard let callback = self.rightHoverAction else { return } callback(self._right_up_button, action) } self._right_down_hover.hoverAction = { [unowned self] _, action in guard let callback = self.rightHoverAction else { return } callback(self._right_down_button, action) } } func initDefaultValue() { self.horizontalDividerColor = .lightGray self.verticalDividerColor = .lightGray self._textField.isBezeled = false self._textField.focusRingType = .none self._textField.lineBreakMode = .byTruncatingTail self._textField.drawsBackground = false self._textField.delegate = self self._textField.formatter = TextFieldFormatter() self._right_up_button.isBordered = false self._right_up_button.title = "" self._right_down_button.isBordered = false self._right_down_button.title = "" if let _image = NSImage(named: "btn_arrow_gray_up_s_norm_on") { self.upItemImage = _image } if let _image = NSImage(named: "btn_arrow_gray_down_s_norm_on") { self.downItemImage = _image } } override func layout() { super.layout() let width: CGFloat = NSWidth(self.bounds) let height: CGFloat = NSHeight(self.bounds) let leftMargin: CGFloat = self.contentInset.left let topMargin: CGFloat = self.contentInset.top let rightMargin: CGFloat = self.contentInset.right let bottomMargin: CGFloat = self.contentInset.bottom let contentW: CGFloat = width - leftMargin - rightMargin let contentH: CGFloat = height - topMargin - bottomMargin self._contentView.frame = self.bounds self._backgroundView.frame = NSMakeRect(leftMargin, topMargin, contentW, contentH) let itemW = self.itemWidth let itemH = self.itemHeight var tfH: CGFloat = 22 if let font = self._textField.font { tfH = font.pointSize * 1.5 } let tfX = leftMargin + self.contentOffset self._textField.frame = NSMakeRect(tfX, (height-tfH)*0.5, width-tfX-rightMargin-itemW-self.verticalDividerWidth-self.contentOffset, tfH) let itemX: CGFloat = width-rightMargin-itemW-1 self._right_v_line.frame = NSMakeRect(itemX-self.verticalDividerWidth*0.5, 0, self.verticalDividerWidth, contentH) self._right_up_button.frame = NSMakeRect(itemX, height*0.5+self.horizontalDividerHeight*0.5, itemW, itemH) self._right_h_line.frame = NSMakeRect(itemX, (height-self.horizontalDividerHeight)*0.5, itemW, self.horizontalDividerHeight) self._right_down_button.frame = NSMakeRect(itemX, 1.5, itemW, itemH) self._right_up_hover.frame = self._right_up_button.frame self._right_down_hover.frame = self._right_down_button.frame } @objc private func _right_up_button_action() { let value = self.kmValue + self.increment if (value > self.maxValue) { self.kmValue = self.autorepeat ? self.minValue : self.maxValue } else { self.kmValue = value } self.delegate?.km_controlTextDidChange(textField: self) } @objc private func _right_down_button_action() { let value = self.kmValue - self.increment if (value < self.minValue) { self.kmValue = self.autorepeat ? self.maxValue : self.minValue } else { self.kmValue = value } self.delegate?.km_controlTextDidChange(textField: self) } } extension KMCustomStepperView: NSTextFieldDelegate { func control(_ control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool { if let should = self.delegate?.km_controlTextShouldBeginEditing(textField: self) { return should } return true } func controlTextDidBeginEditing(_ obj: Notification) { if (!self._textField.isEqual(to: obj.object)) { return } self.delegate?.km_controlTextDidBeginEditing(textField: self) } func controlTextDidChange(_ obj: Notification) { if (!self._textField.isEqual(to: obj.object)) { return } if (self._textField.doubleValue > self.maxValue) { self.kmValue = self.maxValue } else if (self._textField.doubleValue < self.minValue) { self.kmValue = self.minValue } else { self.kmValue = self.textField.doubleValue } self.delegate?.km_controlTextDidChange(textField: self) } func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool { if let should = self.delegate?.km_controlTextShouldEndEditing(textField: self) { return should } return true } func controlTextDidEndEditing(_ obj: Notification) { if (!self._textField.isEqual(to: obj.object)) { return } self.delegate?.km_controlTextDidEndEditing(textField: self) } }