cotis-defaults 0.1.0-alpha.1

Modular Rust UI framework core
Documentation
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
// Todo Color Alpha chanel warning for 255 instead of 0 to 1
/// RGBA color used by default Cotis configs and commands.
///
/// Channel values use the `0..=255` range stored as `f32`. Renderers usually
/// normalize by dividing by `255.0`.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct Color {
    /// Red channel in `0..=255`.
    pub r: f32,
    /// Green channel in `0..=255`.
    pub g: f32,
    /// Blue channel in `0..=255`.
    pub b: f32,
    /// Alpha channel in `0..=255`.
    pub a: f32,
}

impl Color {
    /// Creates an opaque color from RGB channels.
    ///
    /// Sets alpha to `255.0`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use cotis_defaults::colors::Color;
    ///
    /// let color = Color::rgb(128.0, 64.0, 32.0);
    /// assert_eq!(color.a, 255.0);
    /// ```
    pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
        Self { r, g, b, a: 255.0 }
    }
    /// Creates a color from RGBA channels.
    pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
        Self { r, g, b, a }
    }

    /// Creates an opaque color from `u8` RGB channels.
    pub const fn u_rgb(r: u8, g: u8, b: u8) -> Self {
        Self::rgb(r as _, g as _, b as _)
    }
    /// Creates a color from `u8` RGBA channels.
    pub const fn u_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
        Self::rgba(r as _, g as _, b as _, a as _)
    }
}

impl From<(f32, f32, f32)> for Color {
    fn from(value: (f32, f32, f32)) -> Self {
        Self::rgb(value.0, value.1, value.2)
    }
}
impl From<(f32, f32, f32, f32)> for Color {
    fn from(value: (f32, f32, f32, f32)) -> Self {
        Self::rgba(value.0, value.1, value.2, value.3)
    }
}

impl From<(u8, u8, u8)> for Color {
    fn from(value: (u8, u8, u8)) -> Self {
        Self::u_rgb(value.0, value.1, value.2)
    }
}
impl From<(u8, u8, u8, u8)> for Color {
    fn from(value: (u8, u8, u8, u8)) -> Self {
        Self::u_rgba(value.0, value.1, value.2, value.3)
    }
}

/// Attachment point used by gradient positioning.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ColorAttachmentPoint {
    TopLeft,
    TopCenter,
    TopRight,
    CenterLeft,
    CenterCenter,
    CenterRight,
    BottomLeft,
    BottomCenter,
    BottomRight,
}

/// Position for gradient control points.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ColorPos {
    /// X coordinate (usually normalized).
    pub x: f32,
    /// Y coordinate (usually normalized).
    pub y: f32,
    /// Anchor used to interpret this position.
    pub attachment_point: ColorAttachmentPoint,
}

/// A color and its position along a gradient axis (typically 0.0–1.0).
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GradientStop {
    /// Position in the gradient domain.
    pub offset: f32,
    /// Color at this stop.
    pub color: Color,
}

/// Linear gradient along the line from `start` to `end` in normalized coordinates (0–1).
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LinearGradient {
    /// Gradient line start point.
    pub start: ColorPos,
    /// Gradient line end point.
    pub end: ColorPos,
    /// Stops sampled along the line.
    pub stops: Vec<GradientStop>,
}

/// Radial gradient centered at `center` with the given `radius` in normalized coordinates.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RadialGradient {
    /// Gradient center.
    pub center: ColorPos,
    /// Radius in normalized units.
    pub radius: f32,
    /// Gradient color stops.
    pub stops: Vec<GradientStop>,
}

/// Rounded rectangle gradient in normalized coordinates (0-1).
///
/// `position` is the top-left corner of the rectangle. `width` and `height` define its size.
/// `corner_radius` is the radius for all corners.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RoundedRectGradient {
    /// Top-left position of rounded rectangle.
    pub position: ColorPos,
    /// Rectangle width in normalized units.
    pub width: f32,
    /// Rectangle height in normalized units.
    pub height: f32,
    /// Shared corner radius.
    pub corner_radius: f32,
    /// Gradient color stops.
    pub stops: Vec<GradientStop>,
}

/// One layer in a [`LayeredColor`]: a solid color or a gradient.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ColorLayer {
    /// Solid color fill.
    Solid(Color),
    /// Linear gradient fill.
    Linear(LinearGradient),
    /// Radial gradient fill.
    Radial(RadialGradient),
    /// Rounded-rectangle gradient fill.
    RoundedRect(RoundedRectGradient),
    /// Nested multi-layer fill.
    Layered(LayeredColor),
}

/// Stack of paints drawn in order.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LayeredColor {
    /// Layers rendered in declaration order.
    pub layers: Vec<ColorLayer>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn rgb_sets_opaque_alpha() {
        let color = Color::rgb(10.0, 20.0, 30.0);
        assert_eq!(color.a, 255.0);
        assert_eq!(color.r, 10.0);
    }

    #[test]
    fn from_tuple_conversions_set_channels() {
        let from_float: Color = (1.0, 2.0, 3.0).into();
        assert_eq!(from_float, Color::rgb(1.0, 2.0, 3.0));

        let from_bytes: Color = (4, 5, 6).into();
        assert_eq!(from_bytes, Color::u_rgb(4, 5, 6));
    }
}