///|
pub struct Image {
expression : Expression
} derive(Eq, Debug)
///|
pub struct GradientStop[T] {
expression : Expression
hint : Bool
marker : T?
} derive(Eq, Debug)
///|
pub struct ConicPosition {
expression : Expression
} derive(Eq, Debug)
///|
pub struct GradientPosition {
x : Expression
y : Expression
} derive(Eq, Debug)
///|
pub struct GradientDirection {
expression : Expression
} derive(Eq, Debug)
///|
pub(all) enum GradientSide {
ToTop
ToBottom
ToLeft
ToRight
ToTopLeft
ToTopRight
ToBottomLeft
ToBottomRight
} derive(Eq, Debug)
///|
pub(all) enum RadialShape {
Circle
Ellipse
} derive(Eq, Debug)
///|
pub(all) enum RadialExtent {
ClosestSide
FarthestSide
ClosestCorner
FarthestCorner
} derive(Eq, Debug)
///|
pub struct RadialSize {
expression : Expression
shape : RadialShape?
} derive(Eq, Debug)
///|
pub trait GradientColorInput {
fn gradient_color(Self) -> Expression
}
///|
pub impl GradientColorInput for Color with fn gradient_color(self) {
literal_expression(self.to_css(), "color")
}
///|
pub impl GradientColorInput for Token[Color] with fn gradient_color(self) {
reference_expression(self.variable_use())
}
///|
pub impl GradientColorInput for DynamicSlot[Color] with fn gradient_color(self) {
reference_expression(self.variable_use())
}
///|
pub trait ImageInput {
fn image_expression(Self) -> Expression
}
///|
pub impl ImageInput for Image with fn image_expression(self) {
self.expression
}
///|
pub impl ImageInput for Url with fn image_expression(self) {
literal_expression(self.to_css(), "image")
}
///|
pub impl ImageInput for Token[Url] with fn image_expression(self) {
reference_expression(self.variable_use())
}
///|
pub impl ImageInput for DynamicSlot[Url] with fn image_expression(self) {
reference_expression(self.variable_use())
}
///|
pub fn Image::expression(self : Image) -> Expression {
self.expression
}
///|
pub fn Image::to_css(self : Image) -> String {
self.expression.to_css()
}
///|
pub fn[I : ImageInput] image(value : I) -> Image {
{ expression: value.image_expression(), }
}
///|
pub fn image_none() -> Image {
{ expression: literal_expression("none", "image"), }
}
///|
pub fn background_images(values : Array[Image]) -> Image {
if values.is_empty() {
return {
expression: invalid_expression(
"STYLE_IMAGE_EMPTY", "An image list must not be empty",
),
}
}
{
expression: group_expression(
"image-list",
"",
values.map(value => value.expression),
", ",
"",
),
}
}
///|
pub fn[C : GradientColorInput] color_stop(
color : C,
position~ : MathExpr[LengthPercentage]? = None,
end~ : MathExpr[LengthPercentage]? = None,
) -> GradientStop[LengthPercentage] {
let values = [color.gradient_color()]
if position is Some(value) {
values.push(value.expression)
}
if end is Some(value) {
if position is None {
values.push(
invalid_expression(
"STYLE_GRADIENT_STOP", "A double stop requires its first position",
),
)
}
values.push(value.expression)
}
{
expression: group_expression("gradient-stop", "", values, " ", ""),
hint: false,
marker: None,
}
}
///|
pub fn[P : LengthPercentageMathInput] gradient_hint(
position : P,
) -> GradientStop[LengthPercentage] {
{
expression: position.length_percentage_math().expression,
hint: true,
marker: None,
}
}
///|
pub fn[A : AngleMathInput] conic_angle(value : A) -> ConicPosition {
{ expression: value.angle_math().expression, }
}
///|
pub fn[P : PercentageMathInput] conic_percentage(value : P) -> ConicPosition {
{ expression: value.percentage_math().expression, }
}
///|
pub fn[C : GradientColorInput] conic_stop(
color : C,
position~ : ConicPosition? = None,
end~ : ConicPosition? = None,
) -> GradientStop[ConicPosition] {
let values = [color.gradient_color()]
if position is Some(value) {
values.push(value.expression)
}
if end is Some(value) {
if position is None {
values.push(
invalid_expression(
"STYLE_GRADIENT_STOP", "A double stop requires its first position",
),
)
}
values.push(value.expression)
}
{
expression: group_expression("conic-stop", "", values, " ", ""),
hint: false,
marker: None,
}
}
///|
pub fn conic_hint(value : ConicPosition) -> GradientStop[ConicPosition] {
{ expression: value.expression, hint: true, marker: None, }
}
///|
pub fn[X : LengthPercentageMathInput, Y : LengthPercentageMathInput] gradient_position(
x : X,
y : Y,
) -> GradientPosition {
{
x: x.length_percentage_math().expression,
y: y.length_percentage_math().expression,
}
}
///|
pub fn gradient_center() -> GradientPosition {
{
x: literal_expression("50%", "length-percentage"),
y: literal_expression("50%", "length-percentage"),
}
}
///|
fn GradientPosition::expression(self : GradientPosition) -> Expression {
group_expression("position", "", [self.x, self.y], " ", "")
}
///|
pub fn gradient_to(side : GradientSide) -> GradientDirection {
let css = match side {
ToTop => "top"
ToBottom => "bottom"
ToLeft => "left"
ToRight => "right"
ToTopLeft => "top left"
ToTopRight => "top right"
ToBottomLeft => "bottom left"
ToBottomRight => "bottom right"
}
{ expression: literal_expression("to " + css, "gradient-direction"), }
}
///|
pub fn[A : AngleMathInput] gradient_angle(value : A) -> GradientDirection {
{ expression: value.angle_math().expression, }
}
///|
fn[T] gradient(
name : String,
header : Expression,
stops : Array[GradientStop[T]],
repeating : Bool,
) -> Image {
let values = [header]
let mut colors = 0
let mut last_hint = true
for stop in stops {
if stop.hint && last_hint {
values.push(
invalid_expression(
"STYLE_GRADIENT_HINT", "Hints must occur singly between color stops",
),
)
}
if !stop.hint {
colors += 1
}
last_hint = stop.hint
values.push(stop.expression)
}
if colors < 2 || last_hint {
values.push(
invalid_expression(
"STYLE_GRADIENT_STOPS", "A gradient requires at least two colors and must end with a color stop",
),
)
}
let prefix = if repeating { "repeating-" } else { "" }
{
expression: group_expression(
"image",
prefix + name + "-gradient(",
values,
", ",
")",
),
}
}
///|
pub fn linear_gradient(
stops : Array[GradientStop[LengthPercentage]],
direction~ : GradientDirection = gradient_to(ToBottom),
repeating~ : Bool = false,
interpolation? : ColorInterpolation,
) -> Image {
let header = match interpolation {
Some(value) =>
group_expression(
"gradient-header",
"",
[direction.expression, literal_expression(value.css, "interpolation")],
" ",
"",
)
None => direction.expression
}
gradient("linear", header, stops, repeating)
}
///|
pub fn radial_extent(extent : RadialExtent) -> RadialSize {
let css = match extent {
ClosestSide => "closest-side"
FarthestSide => "farthest-side"
ClosestCorner => "closest-corner"
FarthestCorner => "farthest-corner"
}
{ expression: literal_expression(css, "radial-size"), shape: None, }
}
///|
pub fn[L : LengthMathInput] radial_radius(value : L) -> RadialSize {
{
expression: value.length_math().expression.constrain("nonnegative"),
shape: Some(Circle),
}
}
///|
pub fn[X : LengthPercentageMathInput, Y : LengthPercentageMathInput] radial_radii(
x : X,
y : Y,
) -> RadialSize {
{
expression: group_expression(
"radial-size",
"",
[
x.length_percentage_math().expression.constrain("nonnegative"),
y.length_percentage_math().expression.constrain("nonnegative"),
],
" ",
"",
),
shape: Some(Ellipse),
}
}
///|
pub fn radial_gradient(
stops : Array[GradientStop[LengthPercentage]],
shape~ : RadialShape = Ellipse,
size~ : RadialSize = radial_extent(FarthestCorner),
center~ : GradientPosition = gradient_center(),
repeating~ : Bool = false,
interpolation? : ColorInterpolation,
) -> Image {
let shape_css = match shape {
Circle => "circle"
Ellipse => "ellipse"
}
let values = [
literal_expression(shape_css, "radial-shape"),
size.expression,
literal_expression("at", "keyword"),
center.expression(),
]
if size.shape is Some(required) && required != shape {
values.push(
invalid_expression(
"STYLE_GRADIENT_RADIAL_SIZE", "Explicit radial size must agree with its shape",
),
)
}
if interpolation is Some(value) {
values.push(literal_expression(value.css, "interpolation"))
}
gradient(
"radial",
group_expression("radial-header", "", values, " ", ""),
stops,
repeating,
)
}
///|
pub fn conic_gradient(
stops : Array[GradientStop[ConicPosition]],
angle~ : MathExpr[Angle]? = None,
center~ : GradientPosition = gradient_center(),
repeating~ : Bool = false,
interpolation? : ColorInterpolation,
) -> Image {
let values = []
if angle is Some(value) {
values.push(literal_expression("from", "keyword"))
values.push(value.expression)
}
values.push(literal_expression("at", "keyword"))
values.push(center.expression())
if interpolation is Some(value) {
values.push(literal_expression(value.css, "interpolation"))
}
gradient(
"conic",
group_expression("conic-header", "", values, " ", ""),
stops,
repeating,
)
}