diff --git a/README.md b/README.md
index 7c95504..7cbf446 100644
--- a/README.md
+++ b/README.md
@@ -181,6 +181,24 @@ configuration.zoomValues = [.xxl, .s, .l, .xs]
SCColorSampler.sample(configuration: configuration) { ... }
```
+#### Zoom with Mouse Wheel Inverted
+```swift
+// For example:
+configuration.zoomWheelInverse = true
+```
+
+#### Change Loupe Location to avoid sight blocking
+```swift
+// For example:
+configuration.loupeShape = .rect
+configuration.loupeFollowDistance = 10
+configuration.loupeSize = .recOnly(256, 64)
+
+//configuration.loupeShape = .circle
+//configuration.loupeFollowDistance = 1
+//configuration.loupeSize = .large
+```
+
#### Show Color Description
diff --git a/Sources/ColorSampler.swift b/Sources/ColorSampler.swift
index 9551685..647f28a 100644
--- a/Sources/ColorSampler.swift
+++ b/Sources/ColorSampler.swift
@@ -10,6 +10,7 @@ import Combine
import Foundation
import ScreenCaptureKit
import struct SwiftUI.Binding
+import Carbon.HIToolbox
internal class ColorSampler: NSObject {
// Properties
@@ -20,6 +21,8 @@ internal class ColorSampler: NSObject {
var onMouseMovedHandlerBlock: ((NSColor) -> Void)?
var selectionHandlerBlock: ((NSColor?) -> Void)?
+ var monitors: [Any?] = []
+ var isRunning: Bool = false;
// Functions
func sample(
@@ -40,6 +43,11 @@ internal class ColorSampler: NSObject {
return
}
+ // Make window a little bigger than use specified
+ let loupeSize = configuration.loupeSize.getSize()
+ let samplerWindowWidth = loupeSize.width + configuration.padding * 2
+ let samplerWindowHeight = loupeSize.height + configuration.padding * 2
+
var windowInit: (
contentRect: NSRect,
styleMask: NSWindow.StyleMask,
@@ -47,7 +55,7 @@ internal class ColorSampler: NSObject {
defer: Bool
) {
return (
- NSRect.init(origin: .zero, size: configuration.loupeSize.getSize()),
+ NSRect.init(origin: .zero, size: CGSize(width: samplerWindowWidth, height: samplerWindowHeight)),
NSWindow.StyleMask.borderless,
NSWindow.BackingStoreType.buffered,
true
@@ -75,15 +83,18 @@ internal class ColorSampler: NSObject {
name: NSWindow.didResignKeyNotification,
object: self.colorSamplerWindow
)
-
- NSApplication.shared.activate(ignoringOtherApps: true)
- self.colorSamplerWindow?.makeKeyAndOrderFront(self)
+ addMouseMonitor()
+ // 这里有问题,激活放大镜后,其它程序都变灰色了,取色就不对了(已修复)
+ // NSApplication.shared.activate(ignoringOtherApps: false)
+ self.colorSamplerWindow?.orderFront(self) // 不能变成 key
self.colorSamplerWindow?.orderedIndex = 0
-
// prepare image for window's contentView in advance
self.colorSamplerWindow?.mouseMoved(with: NSEvent())
- NSCursor.hide()
+ self.isRunning = true
+ if self.configuration?.loupeFollowMode == .center {
+ NSCursor.hide()
+ }
}
func reset() {
@@ -99,4 +110,94 @@ internal class ColorSampler: NSObject {
self.onMouseMovedHandlerBlock = nil
self.selectionHandlerBlock = nil
}
+
+ func colorSelected() {
+ self.isRunning = false
+ self.colorSamplerWindow?.finalizeColor()
+ self.removeMonitors()
+ self.reset()
+ }
+
+ func cancel() {
+ self.isRunning = false
+ self.colorSamplerWindow?.cancel()
+ self.removeMonitors()
+ self.reset()
+ }
+
+ func addMouseMonitor() {
+
+ // 假如鼠标移动过快导致窗口跟不上,需要此函数来找回监听。和键盘相关的全局事件需要辅助功能权限
+ // 目前已经将隐形窗口放大(SCColorSamplerConfiguration.padding),这种情况应该很少出现了,如果出现,就需要这里发挥作用
+ let global_mouseMoved = NSEvent.addGlobalMonitorForEvents(matching: .mouseMoved) { e in
+ self.colorSamplerWindow?.mouseMoved(with: e)
+ }
+ monitors.append(global_mouseMoved)
+
+ let local_mouseExited = NSEvent.addLocalMonitorForEvents(matching: .mouseExited) { e in
+ self.colorSamplerWindow?.mouseMoved(with: e)
+ return e
+ }
+ monitors.append(local_mouseExited)
+
+ // 该事件只监听除了自身以外的程序,用于在鼠标按下捕获颜色,和键盘相关的全局事件需要辅助功能权限
+ let global_mouse_down = NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDown) { e in
+ guard self.isRunning else {
+ return
+ }
+ self.colorSelected()
+ }
+ monitors.append(global_mouse_down)
+
+ // 用于在按下ESC关闭取色窗口,回车取色,和键盘相关的全局事件需要辅助功能权限
+ let global_key = NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { e in
+ guard self.isRunning else {
+ return
+ }
+ if e.keyCode == kVK_Escape {
+ self.cancel()
+ }
+ if e.keyCode == kVK_Return {
+ self.colorSelected()
+ }
+ }
+ monitors.append(global_key)
+ // 鼠标左键取色
+ let local_mouse = NSEvent.addLocalMonitorForEvents(matching: .leftMouseDown) { e in
+ guard self.isRunning else {
+ return e
+ }
+ self.colorSelected()
+ return e
+ }
+ monitors.append(local_mouse)
+
+ // 用于在按下ESC关闭取色窗口,回车取色
+ let local_key = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { e in
+ guard self.isRunning else {
+ return e
+ }
+ if e.keyCode == kVK_Escape {
+ self.cancel()
+ }
+ if e.keyCode == kVK_Return {
+ self.colorSelected()
+ }
+ return e
+ }
+ monitors.append(local_key)
+ }
+
+ func removeMonitors() {
+ for i in 0 ..< self.monitors.count {
+ if let m = self.monitors[i] {
+ do {
+ NSEvent.removeMonitor(m)
+ } catch {
+ }
+ }
+ }
+ // 防止出现 Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
+ self.monitors = []
+ }
}
diff --git a/Sources/ColorSamplerView.swift b/Sources/ColorSamplerView.swift
index b9da2b6..353803d 100644
--- a/Sources/ColorSamplerView.swift
+++ b/Sources/ColorSamplerView.swift
@@ -14,27 +14,34 @@ internal class ColorSamplerView: NSView {
var image: Binding!
var loupeColor: Binding!
- var quality: SCColorSamplerConfiguration.Quality!
- var shape: SCColorSamplerConfiguration.LoupeShape!
+ var config: SCColorSamplerConfiguration!
+ var frameRect: NSRect!
init(
frame frameRect: NSRect,
zoom: Binding,
image: Binding,
loupeColor: Binding,
- shape: SCColorSamplerConfiguration.LoupeShape,
- quality: SCColorSamplerConfiguration.Quality
+ config: SCColorSamplerConfiguration
) {
self.zoom = zoom
self.image = image
- self.quality = quality
self.loupeColor = loupeColor
- self.shape = shape
+ self.config = config
+ self.frameRect = frameRect
super.init(
frame: frameRect
)
}
+ private func getUserViewFrame() -> NSRect {
+ let windowFrame = self.window!.frame
+ var size: CGSize = config.loupeSize.getSize()
+ let originOffset = config.loupeFollowMode == .noBlock ? config.padding : 0
+ var origin: CGPoint = .init(x: self.frameRect.origin.x + config.padding, y: self.frameRect.origin.y + originOffset)
+ return .init(origin: origin, size: size)
+ }
+
required init?(coder: NSCoder) {
super.init(coder: coder)
}
@@ -48,14 +55,40 @@ internal class ColorSamplerView: NSView {
// Weird ??
fatalError()
}
-
- // Clear the drawing rect.
- context.clear(self.bounds)
-
- let rect = self.bounds
-
- let width: CGFloat = rect.width
- let height: CGFloat = rect.height
+ let quality = config.quality
+ let shape = config.loupeShape
+ let windowRect: NSRect = window!.frame
+
+ // User specified region
+ // 这个 rect 是放大镜的绘画区域,它的坐标系是相对于这个view本身,因此它的原点不是零点,而是(P, P), P=config.padding
+ let rect: NSRect = .init(origin: .init(x: config.padding, y: config.padding), size: config.loupeSize.getSize())
+
+ // 以下debug信息非常重要,保留
+// print("window frame \(self.window!.frame.debugDescription)")
+// print("view frame \(self.frame.debugDescription)")
+// print("draw zone: \(rect.debugDescription)")
+//
+// // Invisible window for debug
+// context.setLineWidth(4.0)
+// context.setStrokeColor(CGColor(red: 255, green: 0, blue: 0, alpha: 1))
+// var shape1: SCColorSamplerConfiguration.LoupeShape = .rect
+// context.addPath(shape1.path(in: rect))
+// context.strokePath()
+//
+// // Inviisible bounds window for debug
+// context.setLineWidth(4.0)
+// context.setStrokeColor(CGColor(red: 0, green: 255, blue: 0, alpha: 1))
+// var shape2: SCColorSamplerConfiguration.LoupeShape = .rect
+// context.addPath(shape2.path(in: self.bounds))
+// context.strokePath()
+//
+// // Inviisible Out window for debug
+// context.setLineWidth(4.0)
+// context.setStrokeColor(CGColor(red: 0, green: 0, blue: 255, alpha: 1))
+// var shape3: SCColorSamplerConfiguration.LoupeShape = .rect
+// context.addPath(shape3.path(in: windowRect))
+// context.strokePath()
+ // 以上debug信息非常重要,保留
// mask
let path = shape.path(in: rect)
@@ -68,6 +101,9 @@ internal class ColorSamplerView: NSView {
}
// draw image
+ let width: CGFloat = rect.width
+ let height: CGFloat = rect.height
+
context.setRenderingIntent(.relativeColorimetric)
context.interpolationQuality = .none
context.draw(image, in: rect)
@@ -75,8 +111,9 @@ internal class ColorSamplerView: NSView {
// Get dimensions
let apertureSize: CGFloat = zoom.getApertureSize()
- let x: CGFloat = (width / 2.0) - (apertureSize / 2.0)
- let y: CGFloat = (height / 2.0) - (apertureSize / 2.0)
+ // 孔径位置
+ let x: CGFloat = (self.frameRect.width / 2.0) - (apertureSize / 2.0)
+ let y: CGFloat = (self.frameRect.height / 2.0) - (apertureSize / 2.0)
// Square pattern
let replicatorLayer = CAReplicatorLayer()
@@ -85,15 +122,15 @@ internal class ColorSamplerView: NSView {
let squareSize = zoom.getSquarePatternSize()
let squareDisplacement = zoom.getSquarePatternDisplacement()
square.borderWidth = 0.5
- square.borderColor = .black.copy(alpha: 0.15)
+ square.borderColor = .black.copy(alpha: 0.05)
square.frame = CGRect(x: x - (squareSize * 25),
y: y - (squareSize * 25),
width: squareSize,
height: squareSize)
- let instanceCount = 50
-
- replicatorLayer.instanceCount = instanceCount
+ let instanceCount: Double = 50
+
+ replicatorLayer.instanceCount = Int(instanceCount)
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(squareSize, squareDisplacement, 0)
replicatorLayer.addSublayer(square)
@@ -102,7 +139,7 @@ internal class ColorSamplerView: NSView {
outerReplicatorLayer.addSublayer(replicatorLayer)
- outerReplicatorLayer.instanceCount = instanceCount
+ outerReplicatorLayer.instanceCount = Int(instanceCount)
outerReplicatorLayer.instanceTransform = CATransform3DMakeTranslation(squareDisplacement, squareSize, 0)
outerReplicatorLayer.render(in: context)
@@ -111,6 +148,7 @@ internal class ColorSamplerView: NSView {
let apertureRect = CGRect(x: x, y: y, width: apertureSize, height: apertureSize)
context.setLineWidth(zoom.getApertureLineWidth())
context.setStrokeColor(loupeColor.wrappedValue.cgColor)
+// context.setStrokeColor(CGColor(red: 255, green: 0, blue: 0, alpha: 1))
context.setShouldAntialias(false)
context.stroke(apertureRect.insetBy(dx: zoom.getInsetAmount(), dy: zoom.getInsetAmount()))
@@ -118,6 +156,7 @@ internal class ColorSamplerView: NSView {
context.setShouldAntialias(true)
context.setLineWidth(4.0)
context.setStrokeColor(loupeColor.wrappedValue.cgColor)
+ //context.setStrokeColor(CGColor(red: 0, green: 255, blue: 0, alpha: 1))
context.addPath(path)
context.strokePath()
}
diff --git a/Sources/ColorSamplerWindow.swift b/Sources/ColorSamplerWindow.swift
index 6a13f2d..4163fbe 100644
--- a/Sources/ColorSamplerWindow.swift
+++ b/Sources/ColorSamplerWindow.swift
@@ -62,10 +62,11 @@ internal class ColorSamplerWindow: NSWindow {
// NSWindow properties
self.delegate = delegate
self.isOpaque = false
- self.backgroundColor = .clear
+ self.backgroundColor = .init(red: 1, green: 1, blue: 1, alpha: 0.001) // 让隐形窗口不可见,但是不能透传点击事件到底部
self.level = .screenSaver
self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
self.ignoresMouseEvents = false
+ self.acceptsMouseMovedEvents = true
// Start stream
Task {
await startStream()
@@ -118,7 +119,7 @@ internal class ColorSamplerWindow: NSWindow {
contentRect: .init(
origin: .init(
x: self.frame.midX - 50,
- y: self.frame.minY - 35
+ y: self.frame.minY - 35 + delegate.config.padding // 实时颜色的位置根据用户可见区域计算
),
size: .init(
width: 100,
@@ -133,10 +134,66 @@ internal class ColorSamplerWindow: NSWindow {
}
}
+ /// This function get user view frame from calculated window frame.
+ ///
+ /// Be cautious this function should only be used after window frame is set by `getWindowOriginPoint`
+ private func getUserViewSize() -> CGSize {
+ return unwrappedDelegate.config.loupeSize.getSize()
+ }
+
+ // Get origin point(zero point) of the rectangle area(loupe)
+ private func getWindowOriginPoint(_ position: NSPoint, _ display: NSScreen) -> NSPoint {
+ let displayOrigin = display.frame.origin
+ // should minus display origin point for multiple displays
+ let position = NSPoint(x: position.x - displayOrigin.x, y: position.y - displayOrigin.y)
+ let config = unwrappedDelegate.config
+ let safeAreaDistance: CGFloat = 10
+
+ var origin: NSPoint = .zero
+ // 在隐形窗口之内的用户可见区域
+ let size: CGSize = getUserViewSize()
+ // Need dodge when mouse reach edge of screen, especially bottom and right edge
+ switch config.loupeFollowMode {
+ case .center:
+ origin = .init(x: position.x - self.frame.size.width / 2, y: position.y - (self.frame.size.height / 2))
+ case .noBlock:
+ if position.x + size.width >= display.frame.width - safeAreaDistance && position.y - size.height <= safeAreaDistance {
+ // right and bottom
+ origin = .init(
+ x: position.x - self.frame.size.width + config.padding,
+ y: position.y - config.padding
+ )
+ } else if position.x + size.width >= display.frame.width - safeAreaDistance { // 使用用户可见区域判断
+ // right
+ origin = .init(
+ x: position.x - self.frame.size.width + config.padding,
+ y: position.y - self.frame.size.height + config.padding - config.loupeFollowDistance // 但是使用窗口大小计算,因为计算的不是可见区域的原点,而是外部窗口的原点
+ )
+ } else if position.y - size.height <= safeAreaDistance {
+ // bottom
+ origin = .init(
+ x: position.x - config.padding,
+ y: position.y - config.padding
+ )
+ } else {
+ // top and left
+ origin = .init(
+ x: position.x - config.padding + config.loupeFollowDistance,
+ y: position.y - self.frame.size.height + config.padding - config.loupeFollowDistance
+ )
+ }
+ }
+
+ // should add origin back cause we want an absolute value caculated base on (0,0)
+ return .init(
+ x: origin.x + displayOrigin.x,
+ y: origin.y + displayOrigin.y
+ )
+ }
// Override NSWindow methods
+ // 这个方法需要采样窗口一直是key,但是这样其它窗口就会失去焦点,颜色会变,因此不能再用了
override open func mouseMoved(with event: NSEvent) {
let position = NSEvent.mouseLocation
-
guard let screenWithMouse = NSScreen.screens.first(
where: { NSMouseInRect(position, $0.frame, false) }
)
@@ -147,11 +204,8 @@ internal class ColorSamplerWindow: NSWindow {
if self.activeDisplay != screenWithMouse {
self.activeDisplay = screenWithMouse
}
-
- let origin: NSPoint = .init(
- x: position.x - (self.frame.size.width / 2),
- y: position.y - (self.frame.size.height / 2)
- )
+
+ let origin: NSPoint = getWindowOriginPoint(position, screenWithMouse)
self.setFrameOrigin(origin)
if let image = croppedImageBinding.wrappedValue,
@@ -174,7 +228,16 @@ internal class ColorSamplerWindow: NSWindow {
super.mouseMoved(with: event)
}
- override open func mouseDown(with event: NSEvent) {
+// override open func mouseDown(with event: NSEvent) {
+// if let color = self.croppedImageBinding.wrappedValue?.colorAtCenter(),
+// let delegate = self.delegate as? ColorSamplerDelegate {
+// delegate.callSelectionHandler(color: color)
+// }
+// self.orderOut(self)
+// }
+//
+ func finalizeColor() {
+// print("finalize color down")
if let color = self.croppedImageBinding.wrappedValue?.colorAtCenter(),
let delegate = self.delegate as? ColorSamplerDelegate {
delegate.callSelectionHandler(color: color)
@@ -212,12 +275,14 @@ internal class ColorSamplerWindow: NSWindow {
return
}
- if event.scrollingDeltaY < -1 {
+ let deltaY = delegate.config.zoomWheelInverse ? -event.scrollingDeltaY : event.scrollingDeltaY
+
+ if deltaY < -1 {
guard let nextZoom = zoom?.getNextZoom(available: delegate.config.zoomValues) else {
return
}
zoom = nextZoom
- } else if event.scrollingDeltaY > 1 {
+ } else if deltaY > 1 {
guard let previousZoom = zoom?.getPreviousZoom(available: delegate.config.zoomValues) else {
return
}
@@ -228,14 +293,22 @@ internal class ColorSamplerWindow: NSWindow {
super.scrollWheel(with: event)
}
- override func keyDown(with event: NSEvent) {
- if event.keyCode == kVK_Escape {
- if let delegate = self.delegate as? ColorSamplerDelegate {
- delegate.callSelectionHandler(color: nil)
- }
- self.orderOut(self)
+ func cancel() {
+ if let delegate = self.delegate as? ColorSamplerDelegate {
+ delegate.callSelectionHandler(color: nil)
}
+ self.orderOut(self)
}
+
+ // 取消置顶后,这里的keydonw就不能用了
+// override func keyDown(with event: NSEvent) {
+// if event.keyCode == kVK_Escape {
+// if let delegate = self.delegate as? ColorSamplerDelegate {
+// delegate.callSelectionHandler(color: nil)
+// }
+// self.orderOut(self)
+// }
+// }
}
extension ColorSamplerWindow {
@@ -269,8 +342,7 @@ extension ColorSamplerWindow {
zoom: zoomBinding,
image: croppedImageBinding,
loupeColor: loupeColorBinding,
- shape: delegate.config.loupeShape,
- quality: delegate.config.quality
+ config: delegate.config
)
self.contentView = contentView
if unwrappedDelegate.config.showColorDescription {
@@ -282,7 +354,7 @@ extension ColorSamplerWindow {
NSRect.init(
origin: .init(
x: self.frame.midX - newWidth / 2,
- y: self.frame.minY - 35
+ y: self.frame.minY - 35 + delegate.config.padding
),
size: .init(
width: newWidth,
@@ -386,20 +458,23 @@ internal extension ColorSamplerWindow {
var captureSize: CGFloat = round(
round(
- self.frame.size.width / self.zoom!.getPixelZoom(quality: delegate.config.quality)
+ delegate.config.loupeSize.getSize().width / self.zoom!.getPixelZoom(quality: delegate.config.quality)
) * delegate.config.quality.getMultiplier()
)
if captureSize.truncatingRemainder(dividingBy: 2) != 0 { captureSize += 1 }
+ let loupeSize = delegate.config.loupeSize.getSize()
+ let captureSizeY = captureSize * loupeSize.height / loupeSize.width
+
let x = (position.x - display.frame.origin.x) * delegate.config.quality.getMultiplier()
let y = (display.frame.height - (position.y - display.frame.origin.y)) * delegate.config.quality.getMultiplier()
let captureRect = NSRect(
x: x - (captureSize / 2),
- y: y - (captureSize / 2),
+ y: y - (captureSizeY / 2),
width: captureSize,
- height: captureSize
+ height: captureSizeY
)
guard let croppedImage = image.cropping(to: captureRect) else {
diff --git a/Sources/SCColorSamplerConfiguration.swift b/Sources/SCColorSamplerConfiguration.swift
index b663d9f..1381496 100644
--- a/Sources/SCColorSamplerConfiguration.swift
+++ b/Sources/SCColorSamplerConfiguration.swift
@@ -21,6 +21,7 @@ open class SCColorSamplerConfiguration: NSObject {
private var _defaultZoom: ZoomValue = .m
private var _loupeShape: LoupeShape = .roundedRect
private var _showColorDescription: Bool = true
+ private var _zoomWheelInverse: Bool = false
private var _colorDescriptionMethod: (NSColor) -> String = { color in
let red = Int((color.redComponent * 255).rounded())
let green = Int((color.greenComponent * 255).rounded())
@@ -33,6 +34,8 @@ open class SCColorSamplerConfiguration: NSObject {
return String(format: "%02x%02x%02x%02x", red, green, blue, alpha).uppercased()
}
}
+ private var _loupeFollowMode: LoupeFollowMode = .center
+ private var _loupeFollowDistance: Double = 10
// MARK: - Loupe shape
public enum LoupeShape {
@@ -56,6 +59,26 @@ open class SCColorSamplerConfiguration: NSObject {
}
}
+ // MARK: - Loupe follow
+ public enum LoupeFollowMode {
+ case center
+ case noBlock
+ }
+
+ /// SCColorSamplerConfiguration property that specifies the distance from the loupe to the mouse.
+ ///
+ open var loupeFollowMode: LoupeFollowMode { get { _loupeFollowMode } set { _loupeFollowMode = newValue } }
+
+ /// SCColorSamplerConfiguration property that specifies the color sampler loupe shape.
+ ///
+ /// It should be set to the approximate mouse size.
+ open var loupeFollowDistance: Double { get { _loupeFollowDistance } set { _loupeFollowDistance = newValue } }
+
+ /// SCColorSamplerConfiguration property that specifies the invisible padding, used to initialize an invisible window to listen on mouse event
+ ///
+ /// It should be a little greater than `loupeFollowDistance`
+ var padding: Double { get { _loupeFollowDistance + 300 } }
+
/// SCColorSamplerConfiguration property that specifies the color sampler loupe shape.
///
/// - Possible values are:
@@ -70,6 +93,7 @@ open class SCColorSamplerConfiguration: NSObject {
case medium
case large
case custom(CGFloat)
+ case recOnly(CGFloat, CGFloat)
internal func getSize() -> CGSize {
switch self {
@@ -81,6 +105,8 @@ open class SCColorSamplerConfiguration: NSObject {
return .init(width: 160, height: 160)
case .custom(let value):
return .init(width: value, height: value)
+ case .recOnly(let width, let height):
+ return .init(width: width, height: height)
}
}
}
@@ -239,6 +265,17 @@ open class SCColorSamplerConfiguration: NSObject {
}
}
+ // MARK: - ZOOM
+ /// SCColorSamplerConfiguration property that specifies if the mouse wheel should be inverted when zooming. It has nothing to do with `event.isDirectionInvertedFromDevice`. It just provides a way to invert the mouse wheel without forcing users to change their system config.
+ ///
+ /// - Possible values are:
+ /// * false (default)
+ /// * true
+ open var zoomWheelInverse: Bool {
+ get { _zoomWheelInverse }
+ set { _zoomWheelInverse = newValue }
+ }
+
/// SCColorSamplerConfiguration property that specifies the possible zoom values. Set to empty array to disable zoom functionality. Set the `defaultZoomValue` property to set the starting zoom value.
///
/// Define all the possible zoom values in an array like so: [.s, .m, .l, .xl]