Skip to content

[FEAT] LinearGradient, RadialGradient & AngularGradient#450

Open
MiaKoring wants to merge 14 commits intomoreSwift:mainfrom
MiaKoring:feat/gradient
Open

[FEAT] LinearGradient, RadialGradient & AngularGradient#450
MiaKoring wants to merge 14 commits intomoreSwift:mainfrom
MiaKoring:feat/gradient

Conversation

@MiaKoring
Copy link
Contributor

@MiaKoring MiaKoring commented Feb 17, 2026

Summary

Added support for 3 types of gradients to be rendered as Views:

  • LinearGradient
  • RadialGradient
  • AngularGradient

Changes

SwiftCrossUI

  • added functions for creation and update of each gradient type to the AppBackend protocol.
  • added an ElementaryViewstruct for each type of gradient.
  • added a struct Angle intended for use by AngularGradient later
    • gets also used by multiple backends for calculations
    • added an initializer to create an Angle from a UnitPoint
  • added a struct UnitPoint, meant to represent a normalized point in a view’s coordinate space
    • added static values for various points on a rectangle
    • AppKitBackend views using it may need to flip the coordinates vertically, due to AppKit using a different coordinate origin point than the other major platforms.
    • UnitPoints are not clamped, so you can use them with arbitrary values to benefit for example from Angle(origin:destination:)
  • added a struct Gradient, used by all implementations of a Gradient, its a gradient type agnostic representation of a color gradient
    • added nested struct Gradient.Stop representing a color and its normalized position in the gradient
    • added initializers taking [Color] or [Gradient.Stop]

AppKitBackend, GtkBackend, UIKitBackend

  • Added implementations of the new AppBackend methods
  • Added separate Widget classes when needed (AppKit & UIKit)

WinUI

  • Added implementation of the new AppBackend methods related to LinearGradient

SwiftCrossUITests

  • Added Tests validating the initializers of Gradient
    • even distribution of stops when supplied [Color]
    • transparent stops when passed an empty array of Color (to make future support of addition possible and remove edgecases to check outside of Gradient)
    • start and end stop of the same color when passed [Color] with one entry
    • preservation of the order the colors are passed

Examples

  • Added GradientsExample
    • added tab with examples for LinearGradient
    • added tab with examples for RadialGradient
    • added tab with examples for AngularGradient

Notes

  • WinUI 2 and 3 don’t support conic (angular) gradients yet. I currently don’t know what an alternative could be
  • This PR only adds support for rendering them as views directly. A future ShapeStyle could easily re-use the added structs tho.

TODO / Status

  • Added RadialGradient support to WinUIBackend (pending RadialGradientBrush generation)
  • “unavailable on WinUIBackend” documentation comment is removed from RadialGradient

@MiaKoring
Copy link
Contributor Author

MiaKoring commented Feb 17, 2026

Screenshot 2026-02-17 at 20 29 46 Screenshot 2026-02-17 at 20 29 57 Screenshot 2026-02-18 at 12 38 59 Screenshot 2026-02-17 at 20 30 46 Screenshot 2026-02-17 at 20 30 54 Screenshot 2026-02-18 at 12 39 07

@MiaKoring
Copy link
Contributor Author

Related issue: #427

@MiaKoring
Copy link
Contributor Author

The new commit now offers full API parity to SwiftUI on AngularGradient and correctly renders all 6 test gradients. “Correct” meaning looking like the SwiftUI rendered result.

All 3 supported frameworks recieved this change and the shared screenshots were updated.

@MiaKoring MiaKoring marked this pull request as ready for review February 18, 2026 15:53
@MiaKoring
Copy link
Contributor Author

Outstanding Tasks have been completed, this can now be reviewed and merged

Copy link
Collaborator

@stackotter stackotter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling this feature! I've reviewed all of the code now, but I haven't tested anything locally yet. I plan to construct a SwiftUI app with a bunch of edge cases that I have thought of, and then compile that with your PR and ensure that all of the edge cases act the same across platforms. It should be a useful tool to help you address some of my PR comments too. I likely won't get around to doing that today though.

}

private func cssStops(gradient: Gradient, environment: EnvironmentValues) -> [String] {
return gradient.stops.map {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give the closure parameter a name to make things a bit more readable

return
"""
rgba(\(red), \(green), \(blue), \
\(resolved.opacity)) \($0.location * 100)%
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you do the location percentage conversion outside of the interpolation then you might be able to fit the string on a single line.

let nsGradient = NSGradient(
colors: colors,
atLocations: gradient.gradient.stops.map { CGFloat($0.location) },
colorSpace: .extendedSRGB
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use deviceRGB for consistency with UIKitBackend until we know what the real correct choice is

override func didMoveToWindow() {
super.didMoveToWindow()
// UIView is always layer-backed, no need for wantsLayer
setupGradient()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just put the implementation of setupGradient in here because it's not used anywhere else

var cgPoint: CGPoint {
CGPoint(x: x, y: y)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put a blank line after this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants