KMDesignSelect.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. //
  2. // KMDesignSelect.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by wanjun on 2023/2/22.
  6. //
  7. import Cocoa
  8. @objc protocol KMSelectPopButtonDelegate: NSObjectProtocol {
  9. @objc optional func km_controlTextDidEndEditing(_ obj: KMDesignSelect)
  10. @objc optional func km_controlTextDidChange(_ obj: KMDesignSelect)
  11. @objc optional func km_SelectPopoverWillShow(_ obj: KMDesignSelect)
  12. func km_comboBoxSelectionDidChange(_ obj: KMDesignSelect)
  13. }
  14. @objc enum SelectType : Int {
  15. case PopButton = 0
  16. case Combox
  17. }
  18. class KMSelectCell: NSTextFieldCell {
  19. var borderThickness: CGFloat = 1
  20. var offset: CGFloat = 0.0
  21. override func drawingRect(forBounds theRect: NSRect) -> NSRect {
  22. var newRect:NSRect = super.drawingRect(forBounds: theRect)
  23. let textSize:NSSize = self.cellSize(forBounds: theRect)
  24. // let heightDelta:CGFloat = newRect.size.height - textSize.height
  25. // if heightDelta > 0 {
  26. // newRect.size.height = textSize.height
  27. // newRect.origin.y += heightDelta * 0.5
  28. // } else {
  29. // newRect.size.height = textSize.height
  30. // newRect.origin.y += heightDelta
  31. // }
  32. let originY = ((self.controlView?.bounds.size.height)! - textSize.height) / 2
  33. newRect.size.height = textSize.height
  34. newRect.origin.y += originY
  35. newRect.origin.x += offset
  36. newRect.size.width = theRect.width - offset*2
  37. // newRect.fill()
  38. return newRect
  39. }
  40. override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
  41. // Area that covers the NSTextField itself. That is the total height minus our custom border size.
  42. let interiorFrame = NSRect(x: 0, y: 0, width: cellFrame.width, height: cellFrame.height - borderThickness)
  43. let path = NSBezierPath()
  44. path.lineWidth = borderThickness
  45. // Line width is at the center of the line.
  46. path.move(to: NSPoint(x: 0, y: cellFrame.height))
  47. path.line(to: NSPoint(x: cellFrame.width, y: cellFrame.height))
  48. path.line(to: NSPoint(x: cellFrame.width, y: 0))
  49. path.line(to: NSPoint(x: 0, y: 0))
  50. NSColor.clear.setStroke()
  51. path.stroke()
  52. // Pass in area minus the border thickness in the height
  53. drawInterior(withFrame: interiorFrame, in: controlView)
  54. }
  55. }
  56. @objcMembers class KMDesignSelect: NSViewController {
  57. @IBOutlet weak var mainBox: KMBox!
  58. @IBOutlet weak var selectBox: NSBox!
  59. @IBOutlet weak var textField: NSTextField!
  60. @IBOutlet weak var imageView: NSImageView!
  61. @IBOutlet weak var horizontalPadding_spacing: NSLayoutConstraint!
  62. @IBOutlet weak var itemSpacing_spacing: NSLayoutConstraint!
  63. @IBOutlet weak var imageViewWidth_spacing: NSLayoutConstraint!
  64. @IBOutlet weak var imageViewHeight_spacing: NSLayoutConstraint!
  65. var imageWidth: Float = 12.0 // 图片宽度
  66. var imageHeight: Float = 12.0 // 图片高度
  67. var horizontalPadding: Float = 8.0
  68. var itemSpacing: Float = 8.0
  69. var borderColor: NSColor = .clear// 边框颜色
  70. var borderColor_hov: NSColor = .clear// 边框颜色
  71. var borderColor_focus: NSColor = .clear// 边框颜色
  72. var borderColor_disabled: NSColor = .clear// 边框颜色
  73. var borderColor_errordef: NSColor = .clear// 边框颜色
  74. var borderColor_errorfocus: NSColor = .clear// 边框颜色
  75. var cornerRadius: Float = 0.0// 边框圆角
  76. var cornerRadius_hov: Float = 0.0// 边框圆角
  77. var cornerRadius_focus: Float = 0.0// 边框圆角
  78. var cornerRadius_disabled: Float = 0.0// 边框圆角
  79. var cornerRadius_errordef: Float = 0.0// 边框圆角
  80. var cornerRadius_errorfocus: Float = 0.0// 边框圆角
  81. var borderWidth: Float = 1.0// 边框宽度
  82. var borderWidth_hov: Float = 1.0// 边框宽度
  83. var borderWidth_focus: Float = 1.0// 边框宽度
  84. var borderWidth_disabled: Float = 1.0// 边框宽度
  85. var borderWidth_errordef: Float = 1.0// 边框宽度
  86. var borderWidth_errorfocus: Float = 1.0// 边框宽度
  87. var background: NSColor = .clear// 背景颜色
  88. var background_hov: NSColor = .clear// 背景颜色
  89. var background_focus: NSColor = .clear// 背景颜色
  90. var background_disabled: NSColor = .clear// 背景颜色
  91. var background_errordef: NSColor = .clear// 背景颜色
  92. var background_errorfocus: NSColor = .clear// 背景颜色
  93. var textColor: NSColor = .black // 内容颜色
  94. var textColor_hov: NSColor = .black // 内容颜色
  95. var textColor_focus: NSColor = .black // 内容颜色
  96. var textColor_disabled: NSColor = .black // 内容颜色
  97. var textColor_errordef: NSColor = .black // 内容颜色
  98. var textColor_errorfocus: NSColor = .black // 内容颜色
  99. var lineHeight: CGFloat = 20.0 // 默认 内容行高
  100. var lineHeight_hov: CGFloat = 20.0 // 默认 内容行高
  101. var lineHeight_focus: CGFloat = 20.0 // 默认 内容行高
  102. var lineHeight_disabled: CGFloat = 20.0 // 默认 内容行高
  103. var lineHeight_errordef: CGFloat = 20.0 // 默认 内容行高
  104. var lineHeight_errorfocus: CGFloat = 20.0 // 默认 内容行高
  105. var font: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体
  106. var font_hov: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体
  107. var font_focus: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体
  108. var font_disabled: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体
  109. var font_errordef: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体
  110. var font_errorfocus: NSFont = NSFont.systemFont(ofSize: 14.0) // 内容字体
  111. var textbg_errorfocus: NSColor = .clear // 正在输入高亮
  112. var _image: NSImage = NSImage(named: "icon_btn_arrow_gray_down_s_norm_false")!
  113. var _stringValue: String = ""// 内容
  114. var _toolTip: String = "" // 提示文字
  115. // button 通用属性
  116. var action: Selector? // 点击事件
  117. var target: AnyObject? // 对象目标
  118. var _enabled: Bool = true // 是否可点击
  119. var _state: KMDesignTokenState = .Norm
  120. var canHover: Bool = true // 是否可悬浮
  121. var _isHidden: Bool = false // 是否隐藏
  122. var _editable: Bool = false // 是否允许编辑
  123. var _alignment: NSTextAlignment = .left //对齐
  124. var placeholderString: String = "" { // 预设文字
  125. didSet {
  126. self.textField.placeholderString = self.placeholderString
  127. }
  128. }
  129. var lineBreakMode: NSLineBreakMode = .byTruncatingTail //文字超出显示
  130. var buttonType: SelectType = .PopButton
  131. var items: [String] = []
  132. open weak var delete: KMSelectPopButtonDelegate?
  133. var _indexOfSelectedItem: Int = 0
  134. var _numberOfItems: Int = 0
  135. var disItems: [String] = []
  136. var isScrollPop = false
  137. var createFilePopover: NSPopover = NSPopover.init()
  138. var popoverBehavior: NSPopover.Behavior = .semitransient
  139. var popViewController: KMHomePopViewController?
  140. var popViewControllerBackground: NSColor?
  141. var popViewControllerTextColor: NSColor?
  142. var popViewControllerEnterFillColor: NSColor?
  143. init(withType type: SelectType) {
  144. super.init(nibName: "KMDesignSelect", bundle: nil)
  145. self.buttonType = type
  146. }
  147. required init?(coder: NSCoder) {
  148. fatalError("init(coder:) has not been implemented")
  149. }
  150. override func viewDidLoad() {
  151. super.viewDidLoad()
  152. // Do view setup here.
  153. textField.isSelectable = false
  154. if (buttonType == .PopButton) {
  155. self.mainBox.contentView = selectBox
  156. textField.delegate = self
  157. self.select(bg: "select.m.bg.norm", text: "select.m.mac-text.act")
  158. self.select(bg: "select.m.bg.hov", text: "select.m.mac-text.act", state: .Hov)
  159. self.select(bg: "select.m.bg.focus", text: "select.m.mac-text.act", state: .Focus)
  160. self.select(bg: "select.m.bg.dis", text: "select.m.mac-text.dis", state: .Disabled)
  161. self.select(bg: "select.m.bg.error-def", text: "select.m.mac-text.act", state: .Error_def)
  162. self.select(bg: "select.m.bg.error-focus", text: "select.m.mac-text.act", textbg: "select.m.bg-text", state: .Error_focus)
  163. } else if (buttonType == .Combox) {
  164. self.mainBox.contentView = selectBox
  165. textField.delegate = self
  166. self.select(bg: "select.m.bg.norm", text: "select.m.mac-text.act")
  167. self.select(bg: "select.m.bg.hov", text: "select.m.mac-text.act", state: .Hov)
  168. self.select(bg: "select.m.bg.focus", text: "select.m.mac-text.act", state: .Focus)
  169. self.select(bg: "select.m.bg.dis", text: "select.m.mac-text.dis", state: .Disabled)
  170. self.select(bg: "select.m.bg.error-def", text: "select.m.mac-text.act", state: .Error_def)
  171. self.select(bg: "select.m.bg.error-focus", text: "select.m.mac-text.act", textbg: "select.m.bg-text", state: .Error_focus)
  172. }
  173. self.mainBox.canHover = true
  174. self.mainBox.canClick = true
  175. self.mainBox.downCallback = { [unowned self](downEntered, mouseBox, event) -> Void in
  176. if self.enabled {
  177. if downEntered {
  178. self.canHover = false
  179. self.mainBoxAction(mouseBox)
  180. }
  181. }
  182. }
  183. self.mainBox.moveCallback = { [unowned self](mouseEntered: Bool, mouseBox: KMBox) -> Void in
  184. if !self.createFilePopover.isShown && self.canHover && (self.state != .Disabled) {
  185. self.canHover = true
  186. if mouseEntered {
  187. self.state = .Hov
  188. } else {
  189. self.state = .Norm
  190. }
  191. self.updateUI()
  192. }
  193. }
  194. createFilePopover.delegate = self
  195. }
  196. // MARK: Get、Set
  197. var stringValue: String {
  198. get {
  199. return _stringValue
  200. }
  201. set {
  202. _stringValue = newValue
  203. let paragraphStyle = NSMutableParagraphStyle()
  204. if (state == .Norm) {
  205. paragraphStyle.lineSpacing = lineHeight
  206. } else if (state == .Hov) {
  207. paragraphStyle.lineSpacing = lineHeight_hov
  208. } else if (state == .Focus) {
  209. paragraphStyle.lineSpacing = lineHeight_focus
  210. } else if (state == .Disabled) {
  211. paragraphStyle.lineSpacing = lineHeight_disabled
  212. } else if (state == .Error_def) {
  213. paragraphStyle.lineSpacing = lineHeight_errordef
  214. } else if (state == .Error_focus) {
  215. paragraphStyle.lineSpacing = lineHeight_errorfocus
  216. }
  217. paragraphStyle.lineBreakMode = .byTruncatingTail
  218. paragraphStyle.alignment = alignment
  219. textField.attributedStringValue = NSAttributedString(string: _stringValue, attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])
  220. }
  221. }
  222. var alert: Bool = false {
  223. didSet {
  224. updateUI()
  225. }
  226. }
  227. var enabled: Bool {
  228. get {
  229. return _enabled
  230. }
  231. set {
  232. _enabled = newValue
  233. if _enabled {
  234. _state = .Norm
  235. } else {
  236. _state = .Disabled
  237. }
  238. updateUI()
  239. }
  240. }
  241. var state: KMDesignTokenState {
  242. get {
  243. return _state
  244. }
  245. set {
  246. _state = newValue
  247. updateUI()
  248. }
  249. }
  250. var isHidden: Bool {
  251. get {
  252. return _isHidden
  253. }
  254. set {
  255. _isHidden = newValue
  256. self.view.isHidden = _isHidden
  257. }
  258. }
  259. var editable: Bool {
  260. get {
  261. return _editable
  262. }
  263. set {
  264. _editable = newValue
  265. textField.isEditable = _editable
  266. }
  267. }
  268. var indexOfSelectedItem: Int {
  269. get {
  270. return _indexOfSelectedItem
  271. }
  272. set {
  273. _indexOfSelectedItem = newValue
  274. }
  275. }
  276. var numberOfItems: Int {
  277. get {
  278. return items.count
  279. }
  280. }
  281. var image: NSImage {
  282. get {
  283. return _image
  284. }
  285. set {
  286. _image = newValue
  287. imageView.image = _image
  288. }
  289. }
  290. var toolTip: String {
  291. get {
  292. return _toolTip
  293. }
  294. set {
  295. _toolTip = newValue
  296. if _toolTip != "" {
  297. mainBox.toolTip = _toolTip
  298. }
  299. }
  300. }
  301. var alignment: NSTextAlignment {
  302. get {
  303. return _alignment
  304. }
  305. set {
  306. _alignment = newValue
  307. textField.alignment = alignment
  308. }
  309. }
  310. // MARK: Private Methods
  311. func updateUI() -> Void {
  312. if (state == .Norm) {
  313. selectBox.fillColor = background
  314. selectBox.borderWidth = CGFloat(borderWidth)
  315. selectBox.cornerRadius = CGFloat(cornerRadius)
  316. selectBox.borderColor = borderColor
  317. textField.textColor = textColor
  318. textField.font = font
  319. imageView.image = NSImage(named: "KMImageNameSelectNormal")
  320. } else if (state == .Hov) {
  321. selectBox.fillColor = background_hov
  322. selectBox.borderWidth = CGFloat(borderWidth_hov)
  323. selectBox.cornerRadius = CGFloat(cornerRadius_hov)
  324. selectBox.borderColor = borderColor_hov
  325. textField.textColor = textColor_hov
  326. textField.font = font_hov
  327. imageView.image = NSImage(named: "KMImageNameSelectHover")
  328. } else if (state == .Focus) {
  329. selectBox.fillColor = background_focus
  330. selectBox.borderWidth = CGFloat(borderWidth_focus)
  331. selectBox.cornerRadius = CGFloat(cornerRadius_focus)
  332. selectBox.borderColor = borderColor_focus
  333. textField.textColor = textColor_focus
  334. textField.font = font_focus
  335. imageView.image = NSImage(named: "KMImageNameSelectFocus")
  336. } else if (state == .Disabled) {
  337. selectBox.fillColor = background_disabled
  338. selectBox.borderWidth = CGFloat(borderWidth_disabled)
  339. selectBox.cornerRadius = CGFloat(cornerRadius_disabled)
  340. selectBox.borderColor = borderColor_disabled
  341. textField.textColor = textColor_disabled
  342. textField.font = font_disabled
  343. imageView.image = NSImage(named: "KMImageNameSelectDisable")
  344. } else if (state == .Error_def) {
  345. selectBox.fillColor = background_errordef
  346. selectBox.borderWidth = CGFloat(borderWidth_errordef)
  347. selectBox.cornerRadius = CGFloat(cornerRadius_errordef)
  348. selectBox.borderColor = borderColor_errordef
  349. textField.textColor = textColor_errordef
  350. textField.font = font_errordef
  351. imageView.image = NSImage(named: "KMImageNameSelectNormal")
  352. } else if (state == .Error_focus) {
  353. selectBox.fillColor = background_errorfocus
  354. selectBox.borderWidth = CGFloat(borderWidth_errorfocus)
  355. selectBox.cornerRadius = CGFloat(cornerRadius_errorfocus)
  356. selectBox.borderColor = borderColor_errorfocus
  357. textField.textColor = textColor_errorfocus
  358. textField.font = font_errorfocus
  359. imageView.image = NSImage(named: "KMImageNameSelectNormal")
  360. }
  361. if alert == true {
  362. selectBox.borderColor = NSColor.km_init(hex: "#F3465B")
  363. } else {
  364. selectBox.borderColor = selectBox.borderColor
  365. }
  366. imageViewWidth_spacing.constant = CGFloat(imageWidth)
  367. imageViewHeight_spacing.constant = CGFloat(imageHeight)
  368. horizontalPadding_spacing.constant = CGFloat(horizontalPadding)
  369. itemSpacing_spacing.constant = CGFloat(itemSpacing)
  370. textField.isEditable = editable
  371. // textField.placeholderString = placeholderString
  372. textField.lineBreakMode = lineBreakMode
  373. popViewController?.updateUI()
  374. }
  375. func removeAllItems() {
  376. items = []
  377. }
  378. func addItems(withObjectValues objects: [String]) {
  379. items = objects
  380. }
  381. func selectItem(at index: Int) {
  382. if items.count > 0 && index < items.count {
  383. stringValue = items[index]
  384. self.indexOfSelectedItem = index
  385. }
  386. }
  387. // MARK: Action
  388. func mainBoxAction(_ sender: Any) -> Void {
  389. self.delete?.km_SelectPopoverWillShow?(self)
  390. if createFilePopover.isShown {
  391. createFilePopover.close()
  392. } else {
  393. var vc: KMHomePopViewController?
  394. if (self.isScrollPop) {
  395. vc = KMScrollPopViewController()
  396. } else {
  397. vc = KMHomePopViewController()
  398. }
  399. if (self.popViewControllerBackground != nil) {
  400. vc?.background = self.popViewControllerBackground!
  401. }
  402. if self.popViewControllerBackground != nil {
  403. vc?.textColor = self.popViewControllerTextColor!
  404. }
  405. if self.popViewControllerBackground != nil {
  406. vc?.enterFillColor = self.popViewControllerEnterFillColor!
  407. }
  408. let _ = vc?.initWithPopViewDataArr(items)
  409. self.popViewController = vc
  410. createFilePopover.contentViewController = vc
  411. if (self.stringValue.isEmpty == false) {
  412. vc?.selectedItems = [self.stringValue]
  413. }
  414. createFilePopover.animates = true
  415. createFilePopover.behavior = self.popoverBehavior
  416. createFilePopover.setValue(true, forKey: "shouldHideAnchor")
  417. 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)
  418. var width = mainBox.frame.width
  419. for i in items {
  420. let w = i.getTextRectSize(font: .systemFont(ofSize: 14.0), size: CGSize(width: CGFloat(MAXFLOAT), height: 32.0)).width+12*2
  421. if width < w {
  422. width = w
  423. }
  424. }
  425. vc?.customBoxWidthLayoutConstraint.constant = width
  426. vc?.downCallback = { [unowned self] (downEntered: Bool, count: String) -> Void in
  427. if downEntered {
  428. self.stringValue = count
  429. let current = self.items.firstIndex(of: count) ?? 0
  430. self.indexOfSelectedItem = current
  431. self.delete?.km_comboBoxSelectionDidChange(self)
  432. self.updateUI()
  433. self.createFilePopover.close()
  434. }
  435. }
  436. }
  437. }
  438. }
  439. extension KMDesignSelect: NSTextFieldDelegate {
  440. func controlTextDidChange(_ obj: Notification) {
  441. let textfield = obj.object as! NSTextField
  442. self.stringValue = textfield.stringValue
  443. self.delete?.km_controlTextDidChange?(self)
  444. }
  445. func controlTextDidEndEditing(_ obj: Notification) {
  446. let textfield = obj.object as! NSTextField
  447. self.stringValue = textfield.stringValue
  448. self.delete?.km_controlTextDidEndEditing?(self)
  449. }
  450. }
  451. extension KMDesignSelect: NSPopoverDelegate {
  452. func popoverWillShow(_ notification: Notification) {
  453. let popover = notification.object as! NSPopover
  454. if (createFilePopover == popover) {
  455. self.state = .Focus
  456. self.canHover = false
  457. let vc = createFilePopover.contentViewController! as! KMHomePopViewController
  458. vc.disItems = disItems
  459. }
  460. }
  461. func popoverWillClose(_ notification: Notification) {
  462. let popover = notification.object as! NSPopover
  463. if (createFilePopover == popover) {
  464. self.state = .Norm
  465. self.canHover = true
  466. }
  467. }
  468. }