Runtime GUI (forja-gui-rt)

Crate: crates/forja-gui-rt/ — Basado en Xilem v0.4 (Masonry + Vello)

¿Qué es?

El runtime GUI de Forja permite crear aplicaciones de ventana con interfaz gráfica usando el mismo lenguaje Forja. Está implementado sobre xilem, un framework UI reactivo con renderizado GPU acelerado por Vello (WebGPU).

Cuando un programa Forja importa "gui", el transpilador genera código Rust que llama a forja_gui_rt::build_and_run(), el cual se encarga de:

  1. Inicializar el estado evaluando las variables y funciones del programa
  2. Extraer el layout de la función main()
  3. Crear una ventana nativa con los widgets Xilem correspondientes
  4. Manejar el event loop: renders, interacciones, callbacks

Arquitectura

  Código Forja (.fa) ──→ AST (forja::ast::Programa)
                                 │
                 ┌───────────────┼───────────────┐
                 ▼               ▼               ▼
         inicializar_estado  extraer_layout  build_and_run
          (evalúa vars y     (convierte AST    (crea ventana
           main() nativo)     → Layout enum)    Xilem + Vello)
                                 │
                                 ▼
                           layout_a_view
                        (Layout → AnyWidgetView)

Estructura del crate

ArchivoDescripción
gui_nativa.rsMotor principal: enum Layout (87+ variantes), expr_a_layout() (200+ funciones Forja), layout_a_view() (conversión a Xilem), AppStateNativo, build_and_run()
evaluador.rsIntérprete tree-walking: ValorGUI, ejecutar_funcion(), evaluar_expresion(), funciones nativas (SQLite, archivos, JSON)
signals.rsSistema reactivo: Signal<T>, VariableStore, generación atómica, Stream<T>
accessibility.rsAccesibilidad: roles Material, descripciones en español, TalkBack/VoiceOver
icons.rsCatálogo de 70+ iconos Material Design con fallback a emoji
svg_icon_widget.rsRenderizador SVG de iconos via Vello
shadow_widget.rsConversión de sombras a BoxShadow
animated_widgets.rsWidgets animados: AnimatedOpacity, AnimatedScale, AnimatedSlide, RippleWidget
animation_root.rsWidget raíz que tickea el motor de animación en cada frame
gesture_widgets.rsGestos: Swipe, Pan, PullToRefresh, PinchZoom, Rotate
pickers.rsSelectores: DatePicker, TimePicker, ColorPicker
qr_widget.rsWidget QR Code con render Vello
chart_widgets.rsGráficos: Bar, Line, Pie, Gauge, Sparkline con Vello
canvas_widget.rsLienzo de dibujo libre con 20+ comandos vía JSON
theme/Sistema de tema Material You: ColorScheme, TypeScale, ShapeSystem, ElevationSystem, MotionSystem, animaciones, HCT color space

Quick Start

Creá un archivo app.fa:

importar "gui"

funcion al_click() {
    escribir("¡Hiciste clic!")
}

funcion main() {
    columna(
        texto_grande("Mi App"),
        boton("Saludar", &al_click)
    )
}

Ejecutar:

forja ejecutar --native app.fa

Widgets de Layout

Columnas y Filas

columna(etiqueta("Uno"), etiqueta("Dos"))
fila(etiqueta("A"), etiqueta("B"))
columna_con_gap(12, [etiqueta("Uno"), etiqueta("Dos")])
columna_centrada(etiqueta("Centrado"))

Apilamiento (ZStack)

pila(
    etiqueta("Fondo"),
    boton("Frente", &fn)
)

Scroll y Contenedores

desplazable(columna(etiqueta("Largo contenido...")))
contenedor(columna(...), 600)
relleno(16, etiqueta("Con padding"))
expansor(etiqueta("Empuja al final"))
centrado(etiqueta("Centrado"))
espacio(24)

Relación de Aspecto

caja_relativa(16.0/9.0, video)

Flex Layout

flex_layout("horizontal", 8, verdadero, [etiqueta("A"), etiqueta("B")])
flujo(4, [etiqueta("Tag1"), etiqueta("Tag2")])

Layout Adaptable (Responsive)

Tres variantes según el ancho de ventana: Compact (<600px), Medium (600–840px), Expanded (>840px).

adaptable(
    columna(etiqueta("Compact")),
    fila(etiqueta("Medium")),
    fila(etiqueta("Expanded"))
)

Widgets de Texto

etiqueta("Hola Mundo")
etiqueta_dinamica("mi_variable")
titulo("Mi App")
etiqueta_color("Rojo", "rojo")
texto_enriquecido("<b>Negrita</b> y <i>itálica</i>")
visor_markdown("# Título\n\nPárrafo")

Estilos Tipográficos (15 estilos MD3)

FunciónEstiloTamañoPeso
texto_grandedisplay_large57spRegular
texto_medianodisplay_medium45spRegular
texto_pequeñodisplay_small36spRegular
titular_grandeheadline_large32spRegular
titular_medianoheadline_medium28spRegular
titular_pequeñoheadline_small24spRegular
encabezado_grandetitle_large22spRegular
encabezado_medianotitle_medium16spMedium
encabezado_pequeñotitle_small14spMedium
cuerpo_grandebody_large16spRegular
cuerpo_medianobody_medium14spRegular
cuerpo_pequeñobody_small12spRegular
etiqueta_grandelabel_large14spMedium
etiqueta_medianalabel_medium12spMedium
etiqueta_pequeñalabel_small11spMedium

Botones Material Design 3

5 Variantes

boton("Relleno", &fn)                    // Filled (default)
boton_relleno("Relleno", &fn)            // Filled
boton_tonal("Tonal", &fn)                 // Tonal
boton_perfilado("Perfilado", &fn)      // Outlined
boton_texto("Texto", &fn)                 // Text
boton_elevado("Elevado", &fn)           // Elevated

Con Icono

boton("Con icono", &fn, "favorite")
boton("Inactivo", &fn, "home", falso, verdadero)

FAB

fab("+", &agregar)                     // Medium
fab_pequeño("+", &agregar)            // Small
fab_grande("+", &agregar)             // Large
fab_extendido("add", "Nuevo", &fn)  // Medium + texto

Icon Button

boton_icono("search", &buscar)            // Standard
boton_icono_relleno("search", &buscar)   // Filled
boton_icono_tonal("favorite", &like)    // Tonal
boton_icono_perfilado("close", &cerrar) // Outlined

Botón Segmentado

segmentado(["Día", "Semana", "Mes"], &cambiar, seleccion)
segmentado_multiple(["A", "B"], &fn, selecciones)

Chips

subconjunto_asistente("Ayuda", &fn)
subconjunto_filtro("Filtro", &fn, activo)
subconjunto_entrada("Tag", &click, &eliminar)
subconjunto_sugerencia("Sugerencia", &sel)

Campos de Entrada

// Texto
campo_texto("nombre", "Nombre", "Tu nombre")
campo_perfilado("email", "Email")
campo_email("email")
campo_telefono("tel")
campo_url("sitio")
campo_numero("edad", "Edad", 0, 150, 0)
campo_contraseña("pass", "Contraseña")
campo_busqueda("query", "Buscar...")
area_texto("desc", "Descripción")           // multilínea

// Con error y contador
campo_texto("nombre", "Nombre", "Tu nombre", "filled", falso, "Error", verdadero)

// Dropdown / Select / Autocomplete
contraer_desplegable(["Op1", "Op2"], sel, "Elige...")
menu_seleccion(["Op1", "Op2"], sel, "Label")
autocompletar(["Manzana", "Banana"], "fruta")

// Switch / Checkbox / Sliders
interruptor("Wifi", "wifi_activado")
casilla("Acepto", "acepto")
deslizante("volumen", 0, 100)
deslizante_discreto("brillo", 0, 100, 10)
deslizante_rango("min", "max", 0, 100)

Selectores y Grupos

grupo_radio("color", ["Rojo", "Verde"], sel, &fn, "horizontal")
grupo_subconjuntos(["Tag1", "Tag2"], selecciones, &fn, verdadero)
selector_fecha("fecha_nac")
selector_hora("hora")
selector_color("color_fondo")

Tarjetas, Listas y Tablas

// Cards
tarjeta(columna(etiqueta("Título")))
tarjeta_elevada(columna(...))
tarjeta_perfilada(columna(...))
tarjeta_seleccionable(columna(...), &on_click, seleccionado)

// List Items
elemento_lista("Título", "Subtítulo", icono, &fn)
elemento_lista_doble("Título", "Subtítulo", icono, &fn)
lista([item1, item2])
lista_con_dividores([item1, item2])
lista_control([item1, item2], "checkbox", ["var1", "var2"])
lista_seleccion([item1, item2], selecciones, &fn, verdadero)

// Data Table
tabla_datos(
    ["Nombre", "Edad"],
    [["Ana", "30"], ["Luis", "25"]]
)
tabla_ordenable(...)
tabla_seleccion(...)

// Surface / Scaffold
superficie(columna(...))
andamio(top, body, bottom, fab)

Superposiciones y Feedback

// Diálogos
dialogo_alerta("Error", "Ocurrió un error", "OK", "", &ok, &ignorar)
dialogo_confirmacion("¿Salir?", "¿Seguro?", "Salir", "Cancelar", &fn, &fn)
dialogo_personalizado("Título", columna(...), &cerrar)

// Bottom Sheets
hoja_inferior(columna(...), "visible")
hoja_inferior_modal(columna(...), "visible", &fn)
hoja_inferior_grande(columna(...), "visible")

// Snackbar / Tooltip / Menú
notificación("Mensaje")
notificación_accion("Mensaje", "Deshacer", &fn, 4000, "snack_visible")
información(boton(...), "Tooltip")
menú_desplegable(["Op1", "Op2"], &fn, "menu_visible")
menú_contexto(["Copiar", "Pegar"], &fn, "ctx_visible")

Navegación

Navigator (gestor de pantallas)

variable pantalla_actual = "inicio"

funcion cambiar(id) {
    pantalla_actual = id
}

funcion main() {
    navegador(
        navegador_pantallas(
            pantalla("Inicio", columna(etiqueta("Home"))),
            pantalla("Perfil", columna(etiqueta("Profile")))
        ),
        "pantalla_actual",
        "barra",
        &cambiar,
        "fade"      // animación opcional
    )
}

Tipos de navegación:

ValorDescripción
"ninguno"Solo contenido
"barra" / "bottom"Bottom navigation bar
"riel" / "rail"Navigation rail lateral
"pestañas" / "tabs"Tabs superiores
"cajón" / "drawer"Drawer lateral

Animaciones (5to argumento): "fade", "slide", "ninguno" (default)

Componentes de Navegación

// Barras
barra_superior("Título", [icono_buscar()])
barra_superior_media("Título", [icono_buscar()])
barra_superior_grande("Título", [icono_buscar()])
barra_inferior([icono_casa(), icono_perfil()])

// Tabs / Navegación lateral
pestañas(["Tab1", "Tab2"], sel, &fn)
riel_navegacion([item_nav("Inicio", "home")], sel, &fn)
cajon_navegacion([item_nav(...)])

Indicadores y Avatares

// Progress
barra_progreso("var")
barra_progreso_indeterminada()
circulo_progreso("var", 48)
circulo_progreso_indeterminado(48)

// Badge / Skeleton
distintivo(boton(...), "5")
distintivo_punto(boton(...))
esqueleto(200, 20)
esqueleto_tarjeta(300, 100)
esqueleto_linea(200, 16)

// Estados
estado_vacio("search", "Sin resultados", "Recargar", &fn)
estado_error("Error", &reintentar)

// Avatares
avatar("JD")
avatar_icono("person")
grupo_avatar(["A", "B", "C"], 3)

Gráficos (Charts)

// Line Chart
gráfico_linea([10, 25, 15, 30], "#6366f1", ["Ene", "Feb", "Mar", "Abr"])

// Bar Chart
gráfico_barras([30, 50, 20], ["#f59e0b", "#6366f1", "#10b981"], ["A", "B", "C"], falso)

// Pie / Donut
gráfico_pastel([30, 50, 20], ["A", "B", "C"])
gráfico_donut([30, 50, 20], ["A", "B", "C"])

// Gauge / Sparkline
gráfico_indicador(75, 0, 100, "#10b981")
minigráfico([5, 10, 8, 15, 12], "#6366f1")

Widgets Avanzados

calificación(3, 5, &fn)           // Star Rating
asistente_pasos(["Paso1", "Paso2"], actual, &fn)
migaja_de_pan(["Inicio", "Perfil"], "→")
calendario(7, 2026, "fecha", &fn)
visor_qr("https://forja-lang.github.io", 200)
selector_archivo(["txt", "json"], falso, &fn)

Widgets Expressive

tarjeta_vidrio(columna(...), 10, 0.3)
gradiente_lineal(["#f59e0b", "#ef4444"], "vertical", columna(...))
boton_morphing("add", "Nuevo", &fn)
fondo_expresivo(["#1a1a2e", "#16213e"], verdadero)
efecto_brillo(columna(...), "#f59e0b", 2.0)

Canvas de Dibujo

Canvas interactivo que renderiza comandos Vello desde una variable JSON.

variable comandos = '[]'

funcion dibujar() {
    comandos = '[{"FillCircle": {"x":100,"y":100,"radius":50,"color":"#f59e0b"}}]'
}

funcion main() {
    columna(
        lienzo("comandos", 400, 300),
        boton("Dibujar", &dibujar)
    )
}

Comandos de canvas: FillCircle, StrokeCircle, FillRect, StrokeRect, FillLine, StrokeLine, FillPath, StrokePath, FillText, ClearCanvas, FillPolygon, StrokePolygon, FillRoundedRect, StrokeRoundedRect, DrawEllipse, DrawArc, DrawBezier, DrawImage, DrawLinearGradient


Iconos Material Design

icono_material("home", 24, "#f59e0b")
icono_relleno("star", 24, "#f59e0b")
icono_perfilado("home", 24, "#6366f1")
icono_redondo("search", 24, "#10b981")
icono_agudo("settings", 24, "#ec4899")
icono_dos_tonos("email", 24, "#6366f1")

70+ iconos: home, search, settings, menu, arrow_back, arrow_forward, add, delete, edit, save, copy, done, close, refresh, filter, sort, email, phone, chat, notifications, person, group, file, folder, download, upload, image, code, wifi, location, place, info, warning, error, help, favorite, star, thumb_up, share, lock, calendar_today, shopping_cart y más.


Sistema de Temas Material You

Uso básico

// Color semilla (se genera toda la paleta automáticamente)
tema_material(columna(...), "#f59e0b")

// Tema claro u oscuro
tema_material(columna(...), "#6366f1", falso)  // is_dark=false

Colores del Scheme (29 roles)

Funciones para aplicar colores: color_primario(child), color_secundario(child), color_terciario(child), color_error(child), color_superficie(child), color_fondo(child), color_perfil(child), color_primario_contenedor(child), color_secundario_contenedor(child), etc.

Tipografía

15 estilos tipográficos MD3 (ver tabla en Widgets de Texto).

Formas (Shapes)

esquinas_pequeñas(child)    // 8px
esquinas_medianas(child)    // 12px
esquinas_grandes(child)     // 16px
esquinas_completas(child)   // 28px o 50%

Elevaciones / Sombras

sombra(child, 1)    // nivel 1 (1dp)
sombra(child, 3)    // nivel 3 (6dp)
// Niveles: 0–5 (0dp, 1dp, 3dp, 6dp, 8dp, 12dp)

Estado Reactivo

Las variables de Forja se sincronizan automáticamente con los widgets. Cuando una variable cambia (por input del usuario o callback), todos los widgets que la lean se re-renderizan automáticamente.

variable nombre = "Invitado"

funcion saludar() {
    escribir("Hola, " + nombre)
}

funcion main() {
    columna(
        campo_texto("nombre", "Nombre"),
        boton("Saludar", &saludar)
    )
}

Callbacks y Funciones Nativas

Callbacks

funcion mi_callback() {
    // ...
}

// Conectar con &nombre_funcion:
boton("Click", &mi_callback)

// Callback que recibe argumentos del widget:
campo_texto("variable", "Label", "Placeholder", &on_change)
deslizante("volumen", 0, 100, &on_change)

Funciones Built-in

FunciónDescripción
timestamp()Milisegundos desde Unix epoch
fecha_desde_timestamp(ts)Formatea timestamp
dividir(texto, sep)Divide string → JSON array
a_numero(valor)Convierte a entero
longitud(valor)Longitud de array/texto
tipo(valor)Nombre del tipo
a_texto(valor)Convierte a string
empujar(array, elem)Agrega a array JSON
_json_stringificar(valor)Serializa a JSON
_archivo_leer(ruta)Lee archivo → Texto
_archivo_escribir(ruta, contenido)Escribe archivo → Entero
_sqlite_abrir(ruta)Abre BD → índice
_sqlite_cerrar(idx)Cierra conexión
_sqlite_ejecutar(idx, sql)Ejecuta SQL
_sqlite_consultar(idx, sql)Consulta → JSON
_sqlite_ejecutar_params(idx, sql, valores)Con bind params
_sqlite_consultar_params(idx, sql, valores)Con bind params
_sqlite_ultimo_id(idx)Último rowid
_sqlite_tablas(idx)Lista de tablas
_sqlite_columnas(idx, tabla)Columnas de tabla

Animación y Motion

FadeTransition

transición(columna(...), "visible", 300)
// (child, variable_bool, duración_ms)

Easing Curves (API Rust)

EASE_STANDARD     // (0.2, 0.0, 0.0, 1.0)
EASE_EMPHASIZED   // (0.3, 0.0, 0.0, 1.0)
EASE_DECELERATE   // (0.0, 0.0, 0.0, 1.0)
EASE_ACCELERATE   // (0.3, 0.0, 1.0, 1.0)
EASE_EXPRESSIVE   // (0.34, 1.56, 0.64, 1.0)

AnimationPresets (API Rust)

AnimationPresets::button_ripple()    // 150ms
AnimationPresets::card_elevate()     // 200ms
AnimationPresets::page_transition()  // 300ms
AnimationPresets::spinner_loop()     // 800ms

Gestos y Touch

pull_to_refresh(columna(...), &recargar, "refrescando")
swipe_to_dismiss(tarjeta(...), &descartar, "Descartar", "descartado")
zoom_pellizco(imagen, 0.5, 3.0)
rotar(imagen)

Ejemplo Completo

importar "gui"

variable nombre = ""
variable email = ""
variable acepta = falso

funcion enviar() {
    si (nombre != "" && email != "" && acepta) {
        notificación("Formulario enviado")
    } sino {
        notificación("Completa todos los campos")
    }
}

funcion main() {
    tema_material(columna(
        titulo("Registro"),
        campo_texto("nombre", "Nombre", "Tu nombre"),
        campo_email("email"),
        casilla("Acepto términos", "acepta"),
        boton("Enviar", &enviar, "send")
    ), "#6366f1")
}