aura-gui 0.1.0

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! Escena de un frame: listas de primitivas ya listas para subir a la GPU.
//!
//! Cada struct es `Pod` con tamaño múltiplo de 16 bytes para coincidir con el
//! layout `std430` de los storage buffers del shader.

use crate::core::{Bounds, Corners, Edges, Rgba};
use bytemuck::{Pod, Zeroable};

fn bounds_array(bounds: Bounds) -> [f32; 4] {
    [bounds.origin.x.0, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0]
}

/// Rectángulo redondeado con fondo (sólido o degradado lineal) y borde (128 bytes).
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Quad {
    pub bounds: [f32; 4],
    pub clip: [f32; 4],
    pub background: Rgba,
    pub border_color: Rgba,
    pub corner_radii: [f32; 4],
    pub border_widths: [f32; 4],
    /// Color final del degradado; el inicial es `background`.
    pub gradient_end: Rgba,
    /// `[dirección x, dirección y, activo (0 o 1), sin uso]`.
    pub gradient: [f32; 4],
}

impl Quad {
    pub fn new(bounds: Bounds, clip: Bounds, background: Rgba) -> Self {
        Self {
            bounds: bounds_array(bounds),
            clip: bounds_array(clip),
            background,
            border_color: Rgba::TRANSPARENT,
            corner_radii: [0.0; 4],
            border_widths: [0.0; 4],
            gradient_end: Rgba::TRANSPARENT,
            gradient: [0.0; 4],
        }
    }

    /// Degradado lineal de `background` a `end`. El ángulo va en grados como en CSS:
    /// 0° sube, 90° va hacia la derecha y 180° baja.
    pub fn gradient(mut self, angle_degrees: f32, end: Rgba) -> Self {
        let radians = angle_degrees.to_radians();
        self.gradient_end = end;
        self.gradient = [radians.sin(), -radians.cos(), 1.0, 0.0];
        self
    }

    pub fn corners(mut self, corners: Corners) -> Self {
        self.corner_radii = [corners.top_left.0, corners.top_right.0, corners.bottom_right.0, corners.bottom_left.0];
        self
    }

    pub fn border(mut self, widths: Edges, color: Rgba) -> Self {
        self.border_widths = [widths.top.0, widths.right.0, widths.bottom.0, widths.left.0];
        self.border_color = color;
        self
    }
}

/// Sombra gaussiana analítica (80 bytes).
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Shadow {
    pub bounds: [f32; 4],
    pub clip: [f32; 4],
    pub color: Rgba,
    pub corner_radii: [f32; 4],
    pub blur: f32,
    pub padding: [f32; 3],
}

impl Shadow {
    pub fn new(bounds: Bounds, clip: Bounds, color: Rgba, corners: Corners, blur: f32) -> Self {
        Self {
            bounds: bounds_array(bounds),
            clip: bounds_array(clip),
            color,
            corner_radii: [corners.top_left.0, corners.top_right.0, corners.bottom_right.0, corners.bottom_left.0],
            blur,
            padding: [0.0; 3],
        }
    }
}

/// Subrayado recto u ondulado (64 bytes).
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Underline {
    pub bounds: [f32; 4],
    pub clip: [f32; 4],
    pub color: Rgba,
    pub thickness: f32,
    pub wavy: f32,
    pub padding: [f32; 2],
}

impl Underline {
    pub fn new(bounds: Bounds, clip: Bounds, color: Rgba, thickness: f32, wavy: bool) -> Self {
        Self {
            bounds: bounds_array(bounds),
            clip: bounds_array(clip),
            color,
            thickness,
            wavy: if wavy { 1.0 } else { 0.0 },
            padding: [0.0; 2],
        }
    }
}

/// Glifo monocromático: la máscara R8 del atlas se tiñe con `color` (64 bytes).
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct MonochromeSprite {
    pub bounds: [f32; 4],
    pub clip: [f32; 4],
    pub uv: [f32; 4],
    pub color: Rgba,
}

/// Imagen a color (emoji, icono): RGBA premultiplicado del atlas secundario.
pub type PolychromeSprite = MonochromeSprite;

impl MonochromeSprite {
    pub fn new(bounds: Bounds, clip: Bounds, uv: [f32; 4], color: Rgba) -> Self {
        Self { bounds: bounds_array(bounds), clip: bounds_array(clip), uv, color }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PrimitiveKind {
    Shadow,
    Quad,
    Underline,
    Monochrome,
    Polychrome,
}

/// Rango consecutivo de instancias del mismo tipo; conserva el orden de pintado.
#[derive(Clone, Debug)]
pub struct Batch {
    pub kind: PrimitiveKind,
    pub range: std::ops::Range<u32>,
}

#[derive(Default)]
pub struct Scene {
    pub shadows: Vec<Shadow>,
    pub quads: Vec<Quad>,
    pub underlines: Vec<Underline>,
    pub monochrome_sprites: Vec<MonochromeSprite>,
    pub polychrome_sprites: Vec<PolychromeSprite>,
    pub batches: Vec<Batch>,
}

impl Scene {
    pub fn clear(&mut self) {
        self.shadows.clear();
        self.quads.clear();
        self.underlines.clear();
        self.monochrome_sprites.clear();
        self.polychrome_sprites.clear();
        self.batches.clear();
    }

    pub fn insert_shadow(&mut self, shadow: Shadow) {
        self.shadows.push(shadow);
        self.extend_batch(PrimitiveKind::Shadow, self.shadows.len() as u32);
    }

    pub fn insert_quad(&mut self, quad: Quad) {
        self.quads.push(quad);
        self.extend_batch(PrimitiveKind::Quad, self.quads.len() as u32);
    }

    pub fn insert_underline(&mut self, underline: Underline) {
        self.underlines.push(underline);
        self.extend_batch(PrimitiveKind::Underline, self.underlines.len() as u32);
    }

    pub fn insert_monochrome(&mut self, sprite: MonochromeSprite) {
        self.monochrome_sprites.push(sprite);
        self.extend_batch(PrimitiveKind::Monochrome, self.monochrome_sprites.len() as u32);
    }

    pub fn insert_polychrome(&mut self, sprite: PolychromeSprite) {
        self.polychrome_sprites.push(sprite);
        self.extend_batch(PrimitiveKind::Polychrome, self.polychrome_sprites.len() as u32);
    }

    /// `end` es el total de instancias de ese tipo tras la inserción.
    fn extend_batch(&mut self, kind: PrimitiveKind, end: u32) {
        match self.batches.last_mut() {
            Some(batch) if batch.kind == kind => batch.range.end = end,
            _ => self.batches.push(Batch { kind, range: end - 1..end }),
        }
    }
}