///|
pub(all) struct Color {
r : Double
g : Double
b : Double
a : Double
} derive(Eq, Debug, ToJson)
///|
pub(all) enum FontFamily {
Name(String)
SystemUi
UiSansSerif
UiSerif
UiMonospace
SansSerif
Serif
Monospace
Emoji
Math
FangSong
} derive(Eq, Debug, ToJson)
///|
pub(all) struct FontFamilyStack {
families : Array[FontFamily]
} derive(Eq, Debug, ToJson)
///|
pub(all) enum FontStyle {
Normal
Italic
} derive(Eq, Debug, ToJson)
///|
/// A single variation axis of a variable font. `axis` is the 4-character
/// OpenType tag (e.g. "wght", "wdth", "ital", "opsz", "slnt").
pub struct FontVariation {
axis : String
value : Double
} derive(Eq, Debug, ToJson)
///|
pub(all) struct FontSpec {
families : FontFamilyStack
size : Double
weight : Int
style : FontStyle
variations : Array[FontVariation]
} derive(Eq, Debug, ToJson)
///|
pub(all) enum TextAlign {
TextStart
TextCenter
TextEnd
} derive(Eq, Debug, ToJson)
///|
pub(all) struct TextRun {
text : String
frame : Rect
font : FontSpec
color : Color
align : TextAlign
} derive(Eq, Debug, ToJson)
///|
pub(all) struct RoundedRect {
rect : Rect
radius : Double
} derive(Eq, Debug, ToJson)
///|
pub(all) struct LinearGradientSpec {
start : Point
end : Point
start_color : Color
end_color : Color
} derive(Eq, Debug, ToJson)
///|
pub(all) struct RadialGradientSpec {
center : Point
radius : Double
center_color : Color
edge_color : Color
} derive(Eq, Debug, ToJson)
///|
pub(all) enum Brush {
Solid(Color)
LinearGradient(LinearGradientSpec)
RadialGradient(RadialGradientSpec)
} derive(Eq, Debug, ToJson)
///|
pub(all) struct ShadowSpec {
rect : RoundedRect
color : Color
offset : Point
blur_radius : Double
spread : Double
} derive(Eq, Debug, ToJson)
///|
pub(all) struct ImageRun {
source : String
frame : Rect
opacity : Double
fit : ImageFit
} derive(Eq, Debug, ToJson)
///|
pub(all) struct Transform2D {
a : Double
b : Double
c : Double
d : Double
tx : Double
ty : Double
} derive(Eq, Debug, ToJson)
///|
pub(all) enum BlendMode {
SourceOver
Multiply
Screen
Overlay
Darken
Lighten
} derive(Eq, Debug, ToJson)
///|
pub(all) enum LayerMask {
NoMask
RectMask(Rect)
RoundedMask(RoundedRect)
} derive(Eq, Debug, ToJson)
///|
pub(all) struct LayerSpec {
opacity : Double
blend_mode : BlendMode
mask : LayerMask
offscreen : Bool
} derive(Eq, Debug, ToJson)
///|
pub(all) struct RetainedLayerSpec {
key : String
frame : Rect
content_revision : Int
scale_factor : Double
} derive(Eq, Debug, ToJson)
///|
pub(all) enum FilterEffect {
Blur(Double)
Saturate(Double)
Brightness(Double)
Contrast(Double)
ColorMatrix(Array[Double])
} derive(Eq, Debug, ToJson)
///|
pub(all) enum PathVerb {
MoveTo(Point)
LineTo(Point)
QuadTo(Point, Point)
CubicTo(Point, Point, Point)
Close
} derive(Eq, Debug, ToJson)
///|
pub(all) struct PathSpec {
verbs : Array[PathVerb]
fill : Brush?
stroke : Brush?
stroke_width : Double
} derive(Eq, Debug, ToJson)
///|
pub(all) struct ShaderEffectSpec {
name : String
frame : Rect
uniforms : Array[Double]
fallback : Brush
} derive(Eq, Debug, ToJson)
///|
pub(all) enum DrawCommand {
Clear(Color)
FillRect(Rect, Color)
StrokeRect(Rect, Color, Double)
FillRoundedRect(RoundedRect, Color)
StrokeRoundedRect(RoundedRect, Color, Double)
FillRoundedRectBrush(RoundedRect, Brush)
StrokeRoundedRectBrush(RoundedRect, Brush, Double)
DrawShadow(ShadowSpec)
DrawText(TextRun)
DrawImage(ImageRun)
PushClip(Rect)
PopClip
PushRoundedClip(RoundedRect)
PopRoundedClip
PushTransform(Transform2D)
PopTransform
PushOpacity(Double)
PopOpacity
PushLayer(LayerSpec)
PopLayer
BeginRetainedLayer(RetainedLayerSpec)
EndRetainedLayer(String)
PushFilter(FilterEffect)
PopFilter
DrawPath(PathSpec)
DrawShaderEffect(ShaderEffectSpec)
} derive(Eq, Debug, ToJson)
///|
pub fn Color::rgba(
r~ : Double,
g~ : Double,
b~ : Double,
a? : Double = 1.0,
) -> Color {
{ r, g, b, a }
}
///|
pub fn Color::white() -> Color {
Color::rgba(r=1.0, g=1.0, b=1.0)
}
///|
pub fn Color::black() -> Color {
Color::rgba(r=0.0, g=0.0, b=0.0)
}
///|
pub fn Color::blue() -> Color {
Color::rgba(r=0.12, g=0.36, b=0.95)
}
///|
pub fn Color::gray() -> Color {
Color::rgba(r=0.82, g=0.84, b=0.88)
}
///|
pub fn Color::with_alpha(self : Color, alpha : Double) -> Color {
{ ..self, a: clamp_double(alpha, 0.0, 1.0) }
}
///|
pub fn Color::multiply_alpha(self : Color, opacity : Double) -> Color {
self.with_alpha(self.a * clamp_double(opacity, 0.0, 1.0))
}
///|
/// Linearly interpolate between this color and `other` by `t` (clamped to
/// 0..1). `t = 0` returns `self`, `t = 1` returns `other`. Used to derive
/// hover/press shades and drive color transitions without a full animation
/// pipeline.
pub fn Color::lerp(self : Color, other : Color, t : Double) -> Color {
let u = clamp_double(t, 0.0, 1.0)
Color::rgba(
r=self.r + (other.r - self.r) * u,
g=self.g + (other.g - self.g) * u,
b=self.b + (other.b - self.b) * u,
a=self.a + (other.a - self.a) * u,
)
}
///|
/// Alias of `Color::lerp` with the conventional `weight` naming: a `weight`
/// of 0.0 keeps `self`, 1.0 fully adopts `other`.
pub fn Color::mix(self : Color, other : Color, weight : Double) -> Color {
self.lerp(other, weight)
}
///|
/// Blend this color toward white by `amount` (clamped to 0..1). Useful for
/// deriving hover shades from a base palette color.
pub fn Color::lighten(self : Color, amount : Double) -> Color {
self.lerp(Color::white(), amount)
}
///|
/// Blend this color toward black by `amount` (clamped to 0..1). Useful for
/// deriving press/active shades from a base palette color.
pub fn Color::darken(self : Color, amount : Double) -> Color {
self.lerp(Color::black(), amount)
}
///|
pub fn FontFamily::css_name(self : FontFamily) -> String {
match self {
Name(name) => name
SystemUi => "system-ui"
UiSansSerif => "ui-sans-serif"
UiSerif => "ui-serif"
UiMonospace => "ui-monospace"
SansSerif => "sans-serif"
Serif => "serif"
Monospace => "monospace"
Emoji => "emoji"
Math => "math"
FangSong => "fangsong"
}
}
///|
pub fn FontFamily::css_token(self : FontFamily) -> String {
match self {
Name(name) =>
if font_family_name_needs_quotes(name) {
"\"" + name.replace(old="\"", new="\\\"") + "\""
} else {
name
}
_ => self.css_name()
}
}
///|
fn font_family_name_needs_quotes(name : String) -> Bool {
name.contains(" ") || name.contains(",") || name.contains("\"")
}
///|
pub fn FontFamilyStack::new(families : Array[FontFamily]) -> FontFamilyStack {
if families.is_empty() {
FontFamilyStack::system_ui()
} else {
{ families, }
}
}
///|
pub fn FontFamilyStack::system_ui() -> FontFamilyStack {
{ families: [SystemUi] }
}
///|
pub fn FontFamilyStack::sans_serif() -> FontFamilyStack {
{ families: [SansSerif] }
}
///|
pub fn FontFamilyStack::serif() -> FontFamilyStack {
{ families: [Serif] }
}
///|
pub fn FontFamilyStack::monospace() -> FontFamilyStack {
{ families: [UiMonospace, Monospace] }
}
///|
pub fn FontFamilyStack::named(name : String) -> FontFamilyStack {
{ families: [FontFamily::Name(name)] }
}
///|
pub fn FontFamilyStack::with_fallback(
self : FontFamilyStack,
family : FontFamily,
) -> FontFamilyStack {
let families = self.families.copy()
families.push(family)
{ families, }
}
///|
pub fn FontFamilyStack::css(self : FontFamilyStack) -> String {
let parts : Array[String] = []
for family in self.families {
parts.push(family.css_token())
}
parts.join(", ")
}
///|
pub fn FontVariation::new(axis~ : String, value~ : Double) -> FontVariation {
{ axis, value }
}
///|
pub fn FontVariation::axis(self : FontVariation) -> String {
self.axis
}
///|
pub fn FontVariation::value(self : FontVariation) -> Double {
self.value
}
///|
pub fn FontSpec::new(
families? : FontFamilyStack = FontFamilyStack::system_ui(),
size? : Double = 16.0,
weight? : Int = 400,
style? : FontStyle = FontStyle::Normal,
variations? : Array[FontVariation] = [],
) -> FontSpec {
{ families, size, weight, style, variations }
}
///|
/// Returns a copy of this font spec with one more variable-font axis applied.
/// Later axes win when the same tag appears twice.
pub fn FontSpec::with_variation(
self : FontSpec,
axis~ : String,
value~ : Double,
) -> FontSpec {
let variations = self.variations.filter(fn(existing) { existing.axis != axis })
variations.push(FontVariation::new(axis~, value~))
{ ..self, variations, }
}
///|
pub fn FontSpec::variation_value(self : FontSpec, axis : String) -> Double? {
let mut found : Double? = None
for variation in self.variations {
if variation.axis == axis {
found = Some(variation.value)
}
}
found
}
///|
pub fn FontSpec::css_family(self : FontSpec) -> String {
self.families.css()
}
///|
pub fn FontSpec::css_style(self : FontSpec) -> String {
match self.style {
Normal => "normal"
Italic => "italic"
}
}
///|
pub fn TextRun::new(
text~ : String,
frame~ : Rect,
font~ : FontSpec,
color~ : Color,
align? : TextAlign = TextAlign::TextCenter,
) -> TextRun {
{ text, frame, font, color, align }
}
///|
pub fn RoundedRect::new(rect~ : Rect, radius? : Double = 0.0) -> RoundedRect {
{ rect, radius }
}
///|
pub fn LinearGradientSpec::new(
start~ : Point,
end~ : Point,
start_color~ : Color,
end_color~ : Color,
) -> LinearGradientSpec {
{ start, end, start_color, end_color }
}
///|
pub fn RadialGradientSpec::new(
center~ : Point,
radius~ : Double,
center_color~ : Color,
edge_color~ : Color,
) -> RadialGradientSpec {
{ center, radius: radius.max(0.0), center_color, edge_color }
}
///|
pub fn Brush::solid(color : Color) -> Brush {
Solid(color)
}
///|
pub fn Brush::linear_gradient(
start~ : Point,
end~ : Point,
start_color~ : Color,
end_color~ : Color,
) -> Brush {
LinearGradient(
LinearGradientSpec::new(start~, end~, start_color~, end_color~),
)
}
///|
pub fn Brush::radial_gradient(
center~ : Point,
radius~ : Double,
center_color~ : Color,
edge_color~ : Color,
) -> Brush {
RadialGradient(
RadialGradientSpec::new(center~, radius~, center_color~, edge_color~),
)
}
///|
pub fn Brush::fallback_color(self : Brush) -> Color {
match self {
Solid(color) => color
LinearGradient(gradient) => gradient.start_color
RadialGradient(gradient) => gradient.center_color
}
}
///|
pub fn Brush::multiply_alpha(self : Brush, opacity : Double) -> Brush {
match self {
Solid(color) => Solid(color.multiply_alpha(opacity))
LinearGradient(gradient) =>
LinearGradient({
..gradient,
start_color: gradient.start_color.multiply_alpha(opacity),
end_color: gradient.end_color.multiply_alpha(opacity),
})
RadialGradient(gradient) =>
RadialGradient({
..gradient,
center_color: gradient.center_color.multiply_alpha(opacity),
edge_color: gradient.edge_color.multiply_alpha(opacity),
})
}
}
///|
pub fn ShadowSpec::new(
rect~ : RoundedRect,
color~ : Color,
offset? : Point = Point::new(x=0.0, y=2.0),
blur_radius? : Double = 8.0,
spread? : Double = 0.0,
) -> ShadowSpec {
{ rect, color, offset, blur_radius, spread }
}
///|
pub fn Transform2D::identity() -> Transform2D {
{ a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0 }
}
///|
pub fn Transform2D::translation(dx~ : Double, dy~ : Double) -> Transform2D {
{ a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: dx, ty: dy }
}
///|
pub fn Transform2D::scale(sx~ : Double, sy~ : Double) -> Transform2D {
{ a: sx, b: 0.0, c: 0.0, d: sy, tx: 0.0, ty: 0.0 }
}
///|
pub fn LayerSpec::new(
opacity? : Double = 1.0,
blend_mode? : BlendMode = BlendMode::SourceOver,
mask? : LayerMask = LayerMask::NoMask,
offscreen? : Bool = false,
) -> LayerSpec {
{ opacity: clamp_double(opacity, 0.0, 1.0), blend_mode, mask, offscreen }
}
///|
pub fn RetainedLayerSpec::new(
key~ : String,
frame~ : Rect,
content_revision~ : Int,
scale_factor? : Double = 1.0,
) -> RetainedLayerSpec {
{ key, frame, content_revision, scale_factor }
}
///|
pub fn FilterEffect::blur(radius : Double) -> FilterEffect {
Blur(radius)
}
///|
pub fn PathSpec::new(
verbs~ : Array[PathVerb],
fill? : Brush? = None,
stroke? : Brush? = None,
stroke_width? : Double = 1.0,
) -> PathSpec {
{ verbs, fill, stroke, stroke_width }
}
///|
pub fn ShaderEffectSpec::new(
name~ : String,
frame~ : Rect,
uniforms? : Array[Double] = [],
fallback? : Brush = Brush::solid(Color::black()),
) -> ShaderEffectSpec {
{ name, frame, uniforms, fallback }
}