Closed TravisSpomer closed 3 years ago
Here's how you'd define a gradient brush using a fixed-pixel-size stop in XAML:
<LinearGradientBrush MappingMode="Absolute" StartPoint="0,0" EndPoint="0,4">
<LinearGradientBrush.RelativeTransform>
<RotateTransform Angle="180" CenterX="0.5" CenterY="0.5" />
<!-- OR -->
<ScaleTransform ScaleY="-1" CenterX="0.5" CenterY="0.5" />
</LinearGradientBrush.RelativeTransform>
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Black" />
<GradientStop Offset="1.0" Color="#c0c0c0" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
And here's how you'd do a fixed gradient border in CSS:
.gradient-border
{
position: relative;
border-radius: 4px;
background: linear-gradient(to top, black 0%, #c0c0c0 4px);
}
.gradient-inner
{
position: absolute;
left: 1px;
top: 1px;
right: 1px;
bottom: 1px;
border-radius: 3px;
background-color: white;
}
In CSS, you need to use a set of nested elements: border-image
can support a gradient, but not rounded corners, so the corners of the element are missing if you try to apply a rounded corner.
.gradient-border-image
{
position: relative;
border: 1px solid #c0c0c0;
border-image: linear-gradient(to top, black 0%, #c0c0c0 4px);
border-image-slice: 1;
/* border-radius: 4px; does not help here */
clip-path: inset(0 round 4px);
}
An alternative is to use a pseudo-element, which has the drawbacks of putting the border outside of the element, and also introduces potential z-index and layout problems.
.gradient-border-pseudo
{
position: relative;
border-radius: 3px;
background-color: white;
}
.gradient-border-pseudo::before
{
content: "";
z-index: -1;
position: absolute;
left: -1px;
top: -1px;
right: -1px;
bottom: -1px;
border-radius: 4px;
background: linear-gradient(to top, black 0%, #c0c0c0 4px);
}
Examples of potential JSON syntax:
"RegularGradient": { "value": {
"start": [0, 1],
"end": [0, 0],
"stops": [
{ "position": 0, "color": "black" },
{ "position": 1, "color": "#c0c0c0" }
]
}},
"FixedGradient": { "value": {
"start": [0, 1],
"end": [0, 0],
"stops": [
{ "position": 0, "color": "black" },
{ "position": 4, "color": "#c0c0c0" }
],
"stopsUnits": "pixels"
}}
That syntax would translate to CSS trivially, with stopsUnits: "pixels"
indicating that the stops.position
values are in pixels rather than 0-1.
To translate the pixel stops to WinUI, when stopsUnits === "pixels"
: let scale
be the maximum position
of any gradient stop, and then divide every gradient stop by scale
and add the appropriate transform to the brush.
At first, we could limit gradient support to 90-degree angles, and then arbitrary angles that would require some trig to export to CSS could come later.
Direction | CSS | start |
end |
---|---|---|---|
Left to right | to right / 270deg |
0,y |
1,y |
Right to left | to left / 90deg |
1,y |
0,y |
Top to bottom | to bottom / 0deg |
x,0 |
x,1 |
Bottom to top | to top / 180deg |
x,1 |
x,0 |
(x
and y
can be any number 0-1 but must be the same in both start
and end
)
It might be easier to decide how to represent token references once implementation starts—we'll want to be able to reuse as much of the regular alias token infrastructure as possible. color
might turn into value
or even an object that contains a value
if that allows for more code reuse.
It's also possible that it might work better with the Figma data model if start
and end
are just expressed as a single angle
; I'm not sure yet. The Figma API for gradients uses a matrix transform which isn't super useful on its own, but QR matrix decomposition can extract the rotation angle.
If we imagine the start and end points are on a circle, you could calculate the start and end points from an angle like this:
FromX = (sin(angle) + 1) / 2
FromY = -(cos(angle - 1) / 2
ToX = 1 - FromX
ToY = 1 - FromY
But, after some more thinking, I don't think that's what they'd expect. For a 45-degree angle, the start point would be roughly (0.85, 0.15), but it seems more likely that a person would expect (1, 0).
In general, storing the From and To points rather than just the angle seems like the most expressive way of handling things, since that captures more nuance when the angle isn't a right angle. SVG gradients do it this way as well; CSS seems to be the outlier. To get the angle from two points:
Angle = atan2(ToY - FromY, FromX - ToX) mod 360
// Or, if atan2 is not available:
Angle =
(FromX === ToX) ?
((ToY > FromY) ? 0 : 180) :
(atan((ToY - FromY) / (ToX - FromX)) + (ToX > FromX ? 270 + 90))
As always, I made a few subtle changes from my notes here in the final implementation—check the docs for the final design.
linear-gradient()
natively supports4px
as a stop definitionLinearGradientBrush
needsMappingMode="Absolute"
and an appropriateRotateTransform