1
0

AngularGradient.swift 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /// An angular gradient, often referred to as a conic gradient.
  2. ///
  3. /// Currently unsupported on WinUIBackend and GtkBackend.
  4. public struct AngularGradient: ElementaryView {
  5. /// The gradient represented as an array of color stops, each having a parametric location value.
  6. public let gradient: Gradient
  7. /// The normalized center point of the gradient in its coordinate space.
  8. public let center: UnitPoint
  9. /// The angle at which the gradient starts drawing. 0° is trailing center.
  10. public let startAngle: Angle
  11. /// The angle at which the gradient stops drawing. Everything after is filled with the last used color.
  12. ///
  13. /// Ends 360° from ``AngularGradient/startAngle`` when `nil`.
  14. public let endAngle: Angle?
  15. private static let idealSize = ViewSize(10, 10)
  16. /// Creates an angular gradient that completes a full turn.
  17. public init(
  18. gradient: Gradient,
  19. center: UnitPoint,
  20. angle: Angle = .zero
  21. ) {
  22. self.gradient = gradient
  23. self.center = center
  24. self.startAngle = angle
  25. self.endAngle = nil
  26. }
  27. /// Creates an angular gradient that completes a partial rotation.
  28. ///
  29. /// For each ``Gradient.Stop``, a location of 0 corresponds to 0°, and a location of 1 corresponds to 360°.
  30. public init(
  31. gradient: Gradient,
  32. center: UnitPoint,
  33. startAngle: Angle,
  34. endAngle: Angle
  35. ) {
  36. self.gradient = gradient
  37. self.center = center
  38. self.startAngle = startAngle
  39. self.endAngle = endAngle
  40. }
  41. func asWidget<Backend: BaseAppBackend>(
  42. backend: Backend
  43. ) -> Backend.Widget {
  44. func castBackend_inner<NewBackend: BaseAppBackend & BackendFeatures.AngularGradients>(_ backend: NewBackend) -> NewBackend.Widget {
  45. return backend.createAngularGradientWidget()
  46. }
  47. guard let castedBackend = backend as? any (BaseAppBackend & BackendFeatures.AngularGradients) else {
  48. fatalError("Backend does not implement BackendFeatures.AngularGradients")
  49. }
  50. return castBackend_inner(castedBackend) as! Backend.Widget
  51. }
  52. public func computeLayout<Backend: BaseAppBackend>(
  53. _ widget: Backend.Widget,
  54. proposedSize: ProposedViewSize,
  55. environment: EnvironmentValues,
  56. backend: Backend
  57. ) -> ViewLayoutResult {
  58. ViewLayoutResult.leafView(
  59. size: proposedSize.replacingUnspecifiedDimensions(by: Self.idealSize)
  60. )
  61. }
  62. func commit<Backend: BaseAppBackend>(
  63. _ widget: Backend.Widget,
  64. layout: ViewLayoutResult,
  65. environment: EnvironmentValues,
  66. backend: Backend
  67. ) {
  68. func castBackend_inner<NewBackend: BaseAppBackend & BackendFeatures.AngularGradients>(_ backend: NewBackend) {
  69. let widget = widget as! NewBackend.Widget
  70. backend.setSize(of: widget, to: layout.size.vector)
  71. backend.updateAngularGradientWidget(
  72. widget,
  73. gradient: self,
  74. withSize: layout.size.vector,
  75. in: environment
  76. )
  77. }
  78. guard let castedBackend = backend as? any (BaseAppBackend & BackendFeatures.AngularGradients) else {
  79. fatalError("Backend does not implement BackendFeatures.AngularGradients")
  80. }
  81. castBackend_inner(castedBackend)
  82. }
  83. }
  84. extension AngularGradient {
  85. /// Creates an angular gradient from a collection of colors that completes a full turn.
  86. public init(
  87. colors: [Color],
  88. center: UnitPoint,
  89. angle: Angle = .zero
  90. ) {
  91. self.init(
  92. gradient: Gradient(colors: colors),
  93. center: center,
  94. angle: angle
  95. )
  96. }
  97. /// Creates an angular gradient from a collection of color stops that completes a full turn.
  98. public init(
  99. stops: [Gradient.Stop],
  100. center: UnitPoint,
  101. angle: Angle = .zero
  102. ) {
  103. self.init(
  104. gradient: Gradient(stops: stops),
  105. center: center,
  106. angle: angle
  107. )
  108. }
  109. /// Creates an angular gradient from a collection of colors that completes a partial rotation.
  110. public init(
  111. colors: [Color],
  112. center: UnitPoint,
  113. startAngle: Angle,
  114. endAngle: Angle
  115. ) {
  116. self.init(
  117. gradient: Gradient(colors: colors),
  118. center: center,
  119. startAngle: startAngle,
  120. endAngle: endAngle
  121. )
  122. }
  123. /// Creates an angular gradient from a collection of color stops that completes a partial rotation.
  124. ///
  125. /// Stops are expected to be in 360° unit space.
  126. public init(
  127. stops: [Gradient.Stop],
  128. center: UnitPoint,
  129. startAngle: Angle,
  130. endAngle: Angle
  131. ) {
  132. self.init(
  133. gradient: Gradient(stops: stops),
  134. center: center,
  135. startAngle: startAngle,
  136. endAngle: endAngle
  137. )
  138. }
  139. /// Stops adjusted to accomodate endAngle on backends without native support.
  140. @_spi(Backends) public var adjustedStops: [Gradient.Stop] {
  141. guard let endAngle else { return gradient.stops }
  142. var stops = gradient.stops
  143. let range = (endAngle - startAngle).degrees
  144. if range < 0 {
  145. stops = stops.map {
  146. Gradient.Stop(color: $0.color, location: 1 - $0.location)
  147. }
  148. }
  149. let absoluteRange = abs(range)
  150. let dividableRange = absoluteRange / 360
  151. stops = stops.map {
  152. Gradient.Stop(color: $0.color, location: $0.location * dividableRange)
  153. }
  154. return stops.sorted(by: { $0.location < $1.location })
  155. }
  156. }