Preamble
I'm trying to create a rounded Brightness Saturation pad using SwiftUI both for indication (from a given color, show the cursor position) and use (by moving the cursor, obtain the resulting color).
I apologize for my bad English .
Presentation code
Firstly I draw the Circle using two Circle elements, filled with a LinearGradient element and the luminosity bland mode.
public var body: some View {
ZStack(alignment: .top) {
//MARK: - Inner Color circle
Group {
Circle()
.fill(
LinearGradient(
colors: [self.minSaturatedColor, self.maxSaturatedColor],
startPoint: .leading,
endPoint: .trailing
)
)
Circle()
.fill(
LinearGradient(
colors: [.white, .black],
startPoint: .top,
endPoint: .bottom
)
)
.blendMode(.luminosity)
}
.frame(width: self.diameter, height: self.diameter, alignment: .center)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged(self.updateCursorPosition)
.onEnded({ _ in
self.startIndication = nil
self.changed(self.finalColor)
})
)
//MARK: - Indicator
Circle()
.stroke(Color.black, lineWidth: 2)
.foregroundColor(Color.clear)
.frame(width: self.cursorDiameter, height: self.cursorDiameter, alignment: .center)
.position(cursorPosition)
}
.frame(width: self.diameter, height: self.diameter, alignment: .center)
}
Theory
Here's the theory above the mixtures of the color saturation with the brightness.
I'm not sure that this is the correct approach, but it seems so because it produces the following circle having (probably) all the shades:
Problem 1
The simplified code that I used to determine the Cursor position (from a given color) is:
public var cursorPosition: CGPoint {
let hsb = self.finalColor.hsba
let x: CGFloat = hsb.saturation * self.diameter
let y: CGFloat = hsb.brightness * self.diameter
// Adjust the Apple reversed ordinate
return CGPoint(x: x, y: self.diameter - y)
}
Unfortunately, this it seems not correct because by giving a certain color (I.e. #bd4d22
) it makes the pointer placed in a wrong position.
Problem 2
There's also another problem: the component should allow to move the cursor and so update the finalColor
State property with the correct amount of brightness and saturation.
private func updateCursorPosition(_ value: DragGesture.Value) -> Void {
let hsb = self.baseColor.hsba
let saturation = value.location.x / self.diameter
let brightness = (0 - value.location.y + self.diameter) / self.diameter
self.finalColor = Color(
hue: hsb.hue,
saturation: saturation,
brightness: brightness
)
}
yet the result is not what expected because, event tough the cursor follows the point (meaning that the computed value is correct), the resulting color is not what rendered behind the pointer!
Example pt. 1
Here, as you can see, the full saturated color (that should theoretically be 2π of the circumference) is not correct.
Example pt. 2
Here, as you can see, there's an issue by going outside of the circle.
Question
- Can anyone explain where am I failing?
- Can anyone help me blocking the
updateCursorPosition
resulting color value, if the cursor is dragged out of the circle? - Am I doing something else in the wrong way?
Thank you for the patience !
Ps. To accomplish this, I take clue form what done by Procreate and what asked here Circular saturation-brightness gradient for color wheel
Okay, this took a while as it wasn't one thing. I ended up pulling your code apart and rebuilding it a little more simply, as well as refactoring the indicator from the color view. To fix the indicator, I took inspiration from this answer. Give it a bump.
As to the color wheel itself, I simplified it into a
Circle()
with an overlay of the secondCircle()
. Getting the colors straight took a lot more time as I am not a designer, so I had to work through dealing with the colors less intuitively. I had a two realizations while dealing with this.To handle the maximum and minimum saturations, the only variable that could be used was the hue. The brightness and saturation were fixed, either 0 or 1 for saturation and 1 for brightness, otherwise the base color gradient was off.
That caused me to have to deal with the brightness gradient. The color was already at maximum brightness, so putting a gradient from white to black rendered incorrectly, yet again. Another realization I had was that the brightness gradient should go from clear to black. That resolved the color issues. The code is below and commented.
The Color Wheel:
The Indicator:
Also, as a side note, most of the variables you had were constants and should be
lets
and notvars
. Anything internal to the struct should be private.@State
variables are always private.