aura-gui 0.2.1

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.
docs.rs failed to build aura-gui-0.2.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

Aura UI

Interfaces nativas aceleradas por GPU para macOS, Windows, Linux, Android e iOS con un solo código en Rust. Estilo encadenado con los nombres de Tailwind, componentes tipo shadcn, sombras por capas y animaciones, pensado para gastar poco: poca RAM, casi nada de CPU en reposo y binarios pequeños.

cargo add aura-gui

Un solo crate con un módulo por capa: aura_ui::core (entidades, tareas, geometría, color, animación y capa de sistema), ::gpu (renderizador), ::view (elementos, estilo, layout), ::widgets (componentes), ::app (ventana y bucle) y ::ai (agentes de IA; feature ai, activa por defecto: default-features = false la quita).

use aura_ui::prelude::*;

struct Contador { clics: u32 }

impl Render for Contador {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement {
        div()
            .size_full().flex_col().items_center().justify_center().gap_4()
            .bg(Token::Canvas).text_color(Token::Text)
            .child(div().p_6().rounded_lg().bg(Token::ElevatedSurface).shadow_md().child(format!("Clics: {}", self.clics)))
            .child(Button::new("Sumar").variant(ButtonVariant::Primary).on_click(cx.click(|this, _| this.clics += 1)))
            .into_element()
    }
}

// Genera `main` en escritorio e iOS y `android_main` en Android.
aura_ui::main!(AppOptions::new("Contador"), |window| window.set_root(Contador { clics: 0 }));

Tailwind

Los nombres son los de Tailwind en snake_case y todos son métodos de Styled: con use aura_ui::prelude::*; basta. Cada uno solo fija un campo; no hay trabajo en tiempo de ejecución ni memoria extra hasta que se usan.

Familia Utilidades
Espaciado y tamaño (escala completa, 1 = 4 px) p_4 px_2 pt_0_5 m_8 mx_auto gap_x_2 w_64 h_1_2 size_10 min_w_0 max_w_full inset_0 top_px
Flex flex_row flex_col_reverse flex_wrap grow shrink_0 flex_1 flex_auto items_baseline justify_evenly self_end content_between
Cuadrícula grid_cols_3 grid_rows_2 col_span_2 col_start_2 row_span_3 grid_flow_col + gap_*
Color blue(500) slate(900) rose(50) del módulo palette, con .with_alpha(0.5); degradados bg_gradient_to_r(a, b)
Texto text_xs … text_9xl font_thin … font_black text_center leading_tight tracking_wide truncate line_clamp_3 uppercase line_through font_mono
Bordes border border_t_2 border_x_0 rounded_lg rounded_t_xl rounded_bl_full ring_2 ring_color(..)
Efectos shadow_md glow opacity_50
Transformaciones rotate_45 neg_rotate_12 scale_105 scale_x_50 translate_x_4 neg_translate_y_1_2 skew_x_6 (giran y escalan desde el centro; no cambian el layout)
Filtros blur_sm blur_md backdrop_blur_xl backdrop_blur_px(6.0)
Apilado z_10 z_50 neg_z_10 z(7) entre hermanos, también para el ratón
Overflow overflow_hidden overflow_x_scroll overflow_auto
Estados hover active focus group_hover drag_over
Variantes sm md lg xl xxl (según el ancho de la ventana, primero móvil), dark light
div()
    .flex_col().gap_4().p_6().rounded_xl().border().border_color(palette::slate(200))
    .md(|s| s.flex_row().items_center())
    .dark(|s| s.bg(palette::slate(900)).border_color(palette::slate(700)))
    .child(text("Título").text_2xl().font_semibold().tracking_tight().truncate())
    .child(div().w_1_3().h_10().rounded_lg().bg_gradient_to_r(palette::blue(500), palette::indigo(600)))

Matices

  • backdrop_blur_* difumina lo que hay detrás del elemento (cristal esmerilado). blur_* difumina el elemento y sus hijos, pero se aplica sobre toda su caja: también suaviza el fondo que tenga debajo y no se extiende más allá del borde.
  • Un desenfoque dentro de un elemento girado usa el rectángulo que envuelve al giro (no hay desenfoque girado).
  • Sin GPU (el rasterizador por CPU) las transformaciones sin giro funcionan; un giro o una inclinación se pinta sin girar. Las hitboxes de un elemento transformado son el rectángulo que envuelve a la figura.
  • Un desenfoque pinta el frame en una textura propia y suelta su memoria al rato de no usarse.

Todavía no existe: divide-* y space-*, cursor-* más allá de los de CursorStyle, select-none, pointer-events-none, object-fit, mix-blend, sticky, otros filtros (brightness, grayscale…), valores arbitrarios (w-[137px]; usa .w(px(137.0))) y las variantes disabled: y focus-within:.

Sombras y animaciones

div().shadow_lg()                                              // presets xs, sm, md, lg, xl, 2xl
div().shadow(Shadows::one(BoxShadow::new(0., 4., 6.).spread(-1.)).with(BoxShadow::new(0., 12., 24.)))  // capas
div().glow(Token::Accent, 16.0)                                // resplandor de color

let x = cx.transition("panel", if abierto { 180.0 } else { 0.0 }, Animation::new(ms(600)).easing(Easing::SPRING));
let t = cx.animate("pulso", Animation::new(ms(900)).ping_pong());
let c = cx.animate_between("fondo", Animation::new(ms(500)).easing(Easing::STANDARD), rojo, azul);

Curvas: Linear, EaseIn, EaseOut, EaseInOut, CubicBezier(..) (como CSS) y Spring { bounce }. Con retraso, repetición y ida y vuelta. Una animación solo pide frames mientras corre.

Eficiencia

Medido en macOS arm64 con el ejemplo contador, build release:

Memoria (huella) 25 MB
CPU en reposo 0,00 s en 20 s
Binario 7,0 MB (macOS) · 6,1 MB (iOS) · 4,2 MB (iOS con opt-level = "s" y LTO completo)

Cómo lo logra: el bucle duerme hasta que hay entrada, un temporizador o una animación; no pinta si no se ve; usa la GPU de bajo consumo con un solo frame en cola; suelta cachés al pasar a segundo plano o ante un aviso de poca memoria; las sombras son una sola primitiva analítica en el shader, sin pasadas de desenfoque.

Perfil recomendado para tus apps móviles:

[profile.release]
opt-level = "s"
lto = "fat"
codegen-units = 1
panic = "abort"
strip = "symbols"

Menos código repetido

Todo viene de use aura_ui::prelude::*;, sin importar nada más.

// Clics: sin `|this: &mut V, _: &ClickEvent, cx|` ni `cx.notify()` (se avisa solo)
.on_click(cx.click(|this, cx| this.pick(name.clone(), cx)))
.on_click(cx.toggle(|t| &mut t.menu_open))          // invierte un bool
.on_click(cx.set(|t| &mut t.tab, Tab::Audio))       // asigna un valor
.on_click(cx.fire(Closed))                          // emite un evento a quien se suscriba

// Segundo plano: sin hilo propio, canal ni `sender.update`; vuelve a la vista y redibuja
cx.job(|cancel| fetch(cancel), |this, result, cx| this.on_fetched(result, cx));
self.search.run(cx, |_| slow_search(q), |this, hits, _| this.hits = hits);   // `Latest`: solo vale la última
cx.job_blocking("login", |cancel| wait_for_browser(cancel), |this, ok, cx| this.on_login(ok, cx));

// Configuración: JSON en ~/.config/<app> (APPDATA en Windows), escritura atómica
let store = Store::new("miapp", "prefs.json").unwrap();
let theme = store.load().str_or("theme", "Aura Dark").to_owned();
store.update(|json| json["theme"] = "Nord".into())?;

// Controles y animación que toda app acaba copiando
div().pressable()                        // mano + hover + active
tool_button(icon, "Deshacer", enabled, cx.click(|t, _| t.undo()))
chip("Alta", selected)                   // etiqueta pulsable
switch(on, "notifs", cx.toggle(|t| &mut t.notifs))
let t = cx.phase("carga", motion);       // segundos; `wave(t, 2.0, 0.0)` da una onda de 0 a 1
let a = cx.fade_in("panel", 200, motion);

Por qué cuesta poco: cx.job reutiliza el pool de la app (en vez de un hilo del sistema por tarea) y un trabajo cancelado no llega a tocar la UI; guardar es un temporal más un renombrado; nada de esto despierta el bucle cuando no hay trabajo.

Móvil

Ver docs/movil.md del repositorio. Resumen: iOS compila para aarch64-apple-ios y el simulador; Android para aarch64-linux-android con NativeActivity. Toque, deslizar con inercia, pulsación larga, teclado del sistema y área segura están implementados. En iOS, añade el manifiesto UIScene al Info.plist (plantilla en docs/ios/Info.plist; lo exige el SDK de iOS 27). Probado en el simulador de iPhone; aún no en un iPhone ni un Android físicos.

Licencia

Aura UI tiene doble licencia, a tu elección (crates.io muestra la primera; la segunda va incluida en el paquete):

  • AGPL-3.0-only (LICENSE-AGPL, https://www.gnu.org/licenses/agpl-3.0.html): gratis. Si la usas, tu app completa debe publicarse bajo AGPL-3.0 con su código fuente.
  • Licencia comercial (LICENSE-COMMERCIAL.md): para apps de código cerrado. Regalías del 10 % de los ingresos brutos de cada producto que use Aura UI. Para obtenerla escribe a camiloghs1@gmail.com.

Si no tienes la licencia comercial, aplica la AGPL-3.0. Los iconos de src/widgets/svg son de Lucide (ISC), con su aviso en src/widgets/svg/LICENSE-lucide.

Copyright (C) 2026 Camilo Grajales Holguín.