// =============================================================================
// Color Types
// =============================================================================
//
// RGBA color representation for computed CSS colors.
// All color values are resolved to this format by css/computed.
///|
/// RGBA color with 8-bit channels and alpha
pub(all) struct Color {
r : Int // 0-255
g : Int // 0-255
b : Int // 0-255
a : Double // 0.0-1.0
} derive(Debug)
///|
pub fn Color::rgb(r : Int, g : Int, b : Int) -> Color {
{ r, g, b, a: 1.0 }
}
///|
pub fn Color::rgba(r : Int, g : Int, b : Int, a : Double) -> Color {
{ r, g, b, a }
}
///|
/// Shared immutable singletons for the most common color sentinels.
/// `Color` has no mutable fields, so returning a shared instance is safe and
/// avoids re-allocating these immutable values on every access (hot path in
/// `Style::default` / `StyleBuilder`, where each default style references
/// `transparent` five times).
let color_transparent : Color = { r: 0, g: 0, b: 0, a: 0.0 }
///|
let color_black : Color = { r: 0, g: 0, b: 0, a: 1.0 }
///|
let color_white : Color = { r: 255, g: 255, b: 255, a: 1.0 }
///|
/// Transparent color (alpha = 0)
pub fn Color::transparent() -> Color {
color_transparent
}
///|
/// Black color
pub fn Color::black() -> Color {
color_black
}
///|
/// White color
pub fn Color::white() -> Color {
color_white
}
///|
pub fn Color::is_transparent(self : Color) -> Bool {
self.a == 0.0
}
///|
pub fn Color::is_opaque(self : Color) -> Bool {
self.a == 1.0
}
///|
/// Convert to CSS hex string (#rrggbb or #rrggbbaa)
pub fn Color::to_hex(self : Color) -> String {
fn hex_char(n : Int) -> Char {
if n < 10 {
('0'.to_int() + n).unsafe_to_char()
} else {
('a'.to_int() + n - 10).unsafe_to_char()
}
}
fn hex2(n : Int) -> String {
let hi = n / 16 % 16
let lo = n % 16
let sb = StringBuilder::new()
sb.write_char(hex_char(hi))
sb.write_char(hex_char(lo))
sb.to_string()
}
let rgb = "#" + hex2(self.r) + hex2(self.g) + hex2(self.b)
if self.a < 1.0 {
let alpha_byte = (self.a * 255.0).to_int()
rgb + hex2(alpha_byte)
} else {
rgb
}
}
///|
/// Convert to CSS rgba() string
pub fn Color::to_rgba_string(self : Color) -> String {
if self.a == 1.0 {
"rgb(\{self.r}, \{self.g}, \{self.b})"
} else {
"rgba(\{self.r}, \{self.g}, \{self.b}, \{self.a})"
}
}
///|
pub impl Show for Color with fn output(self, logger) {
logger.write_string(self.to_rgba_string())
}
///|
pub impl Eq for Color with fn equal(self, other) {
self.r == other.r &&
self.g == other.g &&
self.b == other.b &&
self.a == other.a
}
// =============================================================================
// Gradient Types
// =============================================================================
///|
/// A color stop in a gradient
pub(all) struct GradientStop {
color : Color
position : Double // 0.0-1.0
} derive(Debug, Eq)
///|
/// Linear gradient with angle and color stops
pub(all) struct LinearGradient {
angle_deg : Double // Angle in degrees (0 = to top, 90 = to right, 180 = to bottom)
stops : Array[GradientStop]
} derive(Debug, Eq)
///|
/// Background image (currently only linear-gradient)
pub(all) enum BackgroundImage {
None
Gradient(LinearGradient)
} derive(Debug, Eq)
///|
pub impl Show for GradientStop with fn output(self, logger) {
logger.write_string("{color: ")
logger.write_string(self.color.to_string())
logger.write_string(", position: ")
logger.write_string(self.position.to_string())
logger.write_string("}")
}
///|
pub impl Show for LinearGradient with fn output(self, logger) {
logger.write_string("{angle_deg: ")
logger.write_string(self.angle_deg.to_string())
logger.write_string(", stops: ")
write_gradient_stops(self.stops, logger)
logger.write_string("}")
}
///|
fn write_gradient_stops(stops : Array[GradientStop], logger : &Logger) -> Unit {
logger.write_string("[")
for i = 0; i < stops.length(); i = i + 1 {
if i > 0 {
logger.write_string(", ")
}
stops[i].output(logger)
}
logger.write_string("]")
}
///|
pub impl Show for BackgroundImage with fn output(self, logger) {
match self {
None => logger.write_string("None")
Gradient(gradient) => {
logger.write_string("Gradient(")
logger.write_string(gradient.to_string())
logger.write_string(")")
}
}
}