123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- //
- // KMSlider.swift
- // PDF Reader Pro
- //
- // Created by kdanmobile on 2023/10/31.
- //
- import Cocoa
- @objc(SJTSliderDelegate)
- protocol SJTSliderDelegate: AnyObject {
- func tipForValue(inSlider slider: KMSlider, value: Double) -> String
- func valueDidSelect(inSlider slider: KMSlider)
- }
- let SJTSliderTipPopoverMinWidthX = 60.0;
- let SJTSliderTipPopoverMinWidthY = 40.0;
- let SJTSliderTipPopoverMinHeightX = 38.0;
- let SJTSliderTipPopoverMinHeightY = 24.0;
- let SJTSliderMinHeight = 21.0;
- let SJTSliderMinHeightWithTickMark = 26.0;
- let SJTSliderMinWidth = 100.0;
- let SJTSliderTickMarkMinWidth = 5.0;
- let SJTSliderPositioningRectkey = "positioningRect"
- class KMSlider: NSSlider, NSPopoverDelegate{
- var tipEnabled: Bool = false
- var tipAutoAlignment: Bool = false
- var tipAlignment: NSTextAlignment = .left
- var tipAppearance: NSAppearance?
- var delegate: SJTSliderDelegate?
- private var tipPopover: NSPopover?
-
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- self.initView()
- }
-
- func initView() {
- self.isContinuous = true
- self.tipEnabled = true
- self.tipAutoAlignment = true
- self.tipAlignment = .center
- self.tipAppearance = NSAppearance(named: NSAppearance.Name.vibrantLight)
- let trackingArea = NSTrackingArea(rect: NSZeroRect, options: [.inVisibleRect, .mouseEnteredAndExited, .mouseMoved, .activeInActiveApp], owner: self, userInfo: nil)
- self.addTrackingArea(trackingArea)
-
- let tipView = NSTextField()
- tipView.isBordered = false
- tipView.backgroundColor = NSColor.clear
- tipView.isEditable = false
- tipView.isSelectable = false
- tipView.alignment = .center
- let contentView = NSView()
- contentView.addSubview(tipView)
-
- self.tipPopover = NSPopover()
- self.tipPopover?.contentViewController = NSViewController()
- self.tipPopover?.contentViewController?.view = contentView
- self.tipPopover?.delegate = self
- }
-
- override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
- if object as? NSObject == self.tipPopover && keyPath == SJTSliderPositioningRectkey {
- self.adjustTipAlignment()
- }
- }
-
- override func sendAction(_ action: Selector?, to target: Any?) -> Bool {
- if tipEnabled {
- showTipPopover(animated: false)
- }
-
- let event = NSApplication.shared.currentEvent
- if event?.type == NSEvent.EventType.leftMouseUp {
- delegate?.valueDidSelect(inSlider: self)
- }
-
- return super.sendAction(action, to: target)
- }
-
- func adjustTipAlignment() {
- guard let tipContentView = self.tipPopover?.contentViewController?.view else {
- return
- }
- guard let tipView = tipContentView.subviews.first as? NSTextField else {
- return
- }
-
- var tipAlignment: NSTextAlignment = .center
- if self.tipAutoAlignment {
- var tipContentFrame = tipContentView.convert(tipContentView.bounds, to: nil)
- tipContentFrame = tipContentView.window?.convertToScreen(tipContentFrame) ?? NSRect.zero
- tipContentFrame = self.window?.convertFromScreen(tipContentFrame) ?? NSRect.zero
- if let tipTargetFrame = self.tipPopover?.positioningRect {
- if NSMaxX(tipContentFrame) < NSMinX(tipTargetFrame) {
- tipAlignment = .right
- } else if NSMinX(tipContentFrame) > NSMaxX(tipTargetFrame) {
- tipAlignment = .left
- }
- }
- } else {
- tipAlignment = self.tipAlignment
- }
- tipView.alignment = tipAlignment
- }
-
- func showTipPopover(animated: Bool) {
- let tip = self.tipForValue(self.doubleValue)
- if !tip.isEmpty {
- let contentView = self.tipPopover?.contentViewController?.view
- if let tipView = contentView?.subviews[0] as? NSTextField {
- let knobRect = (self.cell as? NSSliderCell)?.knobRect(flipped: self.isFlipped)
- var preferredEdge: NSRectEdge?
- var newcell: NSSliderCell = self.cell as! NSSliderCell
- if newcell.sliderType == .circular {
- let tickMartCenter = NSMakePoint(NSMidX(knobRect!), NSMidY(knobRect!))
- let viewCenter = NSMakePoint(NSMidX(self.bounds), NSMidY(self.bounds))
- let cutoffValue = sqrt(((viewCenter.x - tickMartCenter.x) * (viewCenter.x - tickMartCenter.x) + (viewCenter.y - tickMartCenter.y) * (viewCenter.y - tickMartCenter.y)) / 2) as CGFloat
-
- if viewCenter.x-tickMartCenter.x > cutoffValue {
- preferredEdge = NSRectEdge.minX
- } else if tickMartCenter.y-viewCenter.y > cutoffValue {
- preferredEdge = NSRectEdge.maxY
- } else if tickMartCenter.x-viewCenter.x > cutoffValue {
- preferredEdge = NSRectEdge.maxX
- } else {
- preferredEdge = NSRectEdge.minY
- }
- } else if self.isVertical {
- if self.tickMarkPosition == NSSlider.TickMarkPosition.leading {
- preferredEdge = NSRectEdge.minX
- } else {
- preferredEdge = NSRectEdge.maxX
- }
- } else {
- if self.tickMarkPosition == NSSlider.TickMarkPosition.below {
- preferredEdge = self.isFlipped ? NSRectEdge.maxY : NSRectEdge.minY
- } else {
- preferredEdge = self.isFlipped ? NSRectEdge.minY : NSRectEdge.maxY
- }
- }
-
- self.tipPopover?.appearance = self.tipAppearance
- tipView.stringValue = tip
- tipView.sizeToFit()
- var tipViewFrame = tipView.bounds
- var contentViewSize = tipView.bounds.size
- var minWidth, minHeight: Int
-
- if preferredEdge == NSRectEdge.minX || preferredEdge == NSRectEdge.maxX {
- minWidth = Int(SJTSliderTipPopoverMinWidthX)
- minHeight = Int(SJTSliderTipPopoverMinHeightX)
- } else {
- minWidth = Int(SJTSliderTipPopoverMinWidthY)
- minHeight = Int(SJTSliderTipPopoverMinHeightY)
- }
-
- if Int(tipViewFrame.size.width) < minWidth {
- tipViewFrame.size.width = CGFloat(minWidth)
- tipViewFrame.origin.x = (CGFloat(minWidth)-tipViewFrame.size.width)/2
- contentViewSize.width = CGFloat(minWidth)
- }
-
- if Int(tipViewFrame.size.height) < minHeight {
- tipViewFrame.origin.y = (CGFloat(minHeight)-tipViewFrame.size.height)/2
- contentViewSize.height = CGFloat(minHeight)
- }
-
- contentView?.setFrameSize(contentViewSize)
- tipView.frame = tipViewFrame
- self.tipPopover?.contentSize = contentViewSize
-
- self.tipPopover?.animates = animated
- self.tipPopover?.show(relativeTo: knobRect!, of: self, preferredEdge: preferredEdge!)
- }
- }
- }
-
- func tipForValue(_ value: Double) -> String {
- if let tip = self.delegate?.tipForValue(inSlider: self, value: self.doubleValue) {
- return tip
- }
- return String(format: "%0.f", self.doubleValue)
- }
-
- func closeTipPopover(animated: Bool) {
- if ((self.tipPopover?.isShown) != nil) {
- self.tipPopover?.animates = animated
- self.tipPopover?.close()
- }
- }
- }
- extension KMSlider {
- override func mouseDown(with event: NSEvent) {
- if self.tipEnabled {
- self.showTipPopover(animated: true)
- }
- super.mouseDown(with: event)
- }
-
- override func mouseEntered(with theEvent: NSEvent) {
- if self.tipEnabled {
- self.showTipPopover(animated: true)
- }
- }
-
- override func mouseExited(with theEvent: NSEvent) {
- self.closeTipPopover(animated: true)
- }
- }
|