aura-gui 0.3.5

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 crate::gpu::transform::Affine;
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 }
    }
}

/// Desenfoque de lo que ya se pintó dentro de un rectángulo redondeado (`backdrop-blur`, `blur`): 64 bytes.
/// Está en coordenadas de pantalla: al insertarlo se lleva a su caja envolvente si había una transformación.
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Blur {
    pub bounds: [f32; 4],
    pub clip: [f32; 4],
    pub corner_radii: [f32; 4],
    /// Desviación estándar en px lógicos (como el `blur(8px)` de CSS).
    pub sigma: f32,
    pub padding: [f32; 3],
}

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

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PrimitiveKind {
    Blur,
    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>,
    /// Índice en `Scene::transforms`; 0 es la identidad.
    pub transform: u32,
}

#[derive(Default)]
pub struct Scene {
    pub shadows: Vec<Shadow>,
    pub blurs: Vec<Blur>,
    pub quads: Vec<Quad>,
    pub underlines: Vec<Underline>,
    pub monochrome_sprites: Vec<MonochromeSprite>,
    pub polychrome_sprites: Vec<PolychromeSprite>,
    pub batches: Vec<Batch>,
    /// Transformaciones de los lotes; la 0 es siempre la identidad.
    pub transforms: Vec<Affine>,
    /// Transformaciones activas (índices en `transforms`), la última es la vigente.
    active: Vec<u32>,
}

impl Scene {
    /// Transformación aplicada a lo que se inserte ahora.
    pub fn transform(&self) -> Affine {
        self.transforms.get(self.current() as usize).copied().unwrap_or_default()
    }

    fn current(&self) -> u32 {
        self.active.last().copied().unwrap_or(0)
    }

    /// Lo que se inserte hasta `pop_transform` se dibuja con `transform` aplicada (dentro de la vigente).
    pub fn push_transform(&mut self, transform: Affine) {
        if self.transforms.is_empty() {
            self.transforms.push(Affine::IDENTITY);
        }
        let combined = transform.then(self.transform());
        self.transforms.push(combined);
        self.active.push(self.transforms.len() as u32 - 1);
    }

    pub fn pop_transform(&mut self) {
        self.active.pop();
    }

    pub fn clear(&mut self) {
        self.transforms.clear();
        self.active.clear();
        self.shadows.clear();
        self.blurs.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);
    }

    /// Siempre en coordenadas de pantalla: el lote no lleva transformación.
    pub fn insert_blur(&mut self, blur: Blur) {
        self.blurs.push(blur);
        let end = self.blurs.len() as u32;
        match self.batches.last_mut() {
            Some(batch) if batch.kind == PrimitiveKind::Blur => batch.range.end = end,
            _ => self.batches.push(Batch { kind: PrimitiveKind::Blur, range: end - 1..end, transform: 0 }),
        }
    }

    pub fn has_blur(&self) -> bool {
        !self.blurs.is_empty()
    }

    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) {
        let transform = self.current();
        match self.batches.last_mut() {
            Some(batch) if batch.kind == kind && batch.transform == transform => batch.range.end = end,
            _ => self.batches.push(Batch { kind, range: end - 1..end, transform }),
        }
    }
}