chore: refresco desde el monorepo (pin tawasuyu.git v0.2.0)

Regenerado con scripts/actualizar-standalone.py: código del dominio al día,
git-deps repineadas a v0.2.0 y doble-fuente resuelta con [patch] al source git.
cargo check --workspace verde.
This commit is contained in:
Sergio
2026-07-25 21:10:38 +00:00
parent 18a001b6ad
commit df7773b4f2
75 changed files with 3555 additions and 582 deletions
+4 -2
View File
@@ -2,6 +2,8 @@
> Visualización agnóstica del backend. El "tercer ojo" del monorepo.
![la galería de painters de pineal renderizada headless: una grilla de 11 tiles — cartesiano con senoide, pie/donut, radar, treemap squarified, heatmap Viridis, hexbin, isolíneas de contour, un sankey de presupuesto, un mesh force-directed, barras con un valor negativo y un histograma](https://tawasuyu.net/00_unanchay/pineal/pantallazo.png)
Pineal es un catálogo de painters especializados — cartesiano, polar,
mesh, treemap, phosphor, flow, heatmap, stream, financial, hexbin,
contour y bars — sobre una **única abstracción de pintura**, el trait
@@ -116,8 +118,8 @@ pasada vello hermana) cuando hagan falta etiquetas.
## Consideraciones
- pineal **no calcula** — sólo dibuja. Para correr una simulación hablás
con `dominium`, `tinkuy`, `cosmos`, etc., y le pasás el resultado.
- pineal **no calcula** — sólo dibuja. Para correr una simulación hablas
con `dominium`, `tinkuy`, `cosmos`, etc., y le pasas el resultado.
- El export a SVG es vector real (no captura de píxeles). El PNG es un
rasterizador software diminuto — sin stack gráfico nativo. El texto se
omite en PNG y en el camino GPU (usar SVG o una pasada vello hermana).
+2
View File
@@ -2,6 +2,8 @@
> Backend-agnostic visualization. The "third eye" of the monorepo.
![the pineal painter gallery rendered headless: an 11-tile grid — sine cartesian, pie/donut, radar, squarified treemap, Viridis heatmap, hexbin, contour isolines, a budget sankey, a force-directed mesh, bars with a negative value and a histogram](https://tawasuyu.net/00_unanchay/pineal/pantallazo.png)
Pineal is a catalog of specialized painters — cartesian, polar, mesh,
treemap, phosphor, flow, heatmap, stream, financial, hexbin, contour and
bars — over a **single painter abstraction**, the `Canvas` trait. Any
+1 -1
View File
@@ -22,7 +22,7 @@ cargo run --release -p pineal-stream-demo
## Crateskuna
Tukuy crates yachachiyninwan iskay versionkunapi: [`pineal-core`](pineal-core/README.md) escena modelo, [`pineal-render`](pineal-render/README.md) Llimphi rikuchikuy, [`pineal-{cartesian,polar,mesh,treemap,phosphor,flow,heatmap,stream,financial,umbrella}`](pineal-cartesian/README.md) sapanka layakuna, [`pineal-export`](pineal-export/README.md) PNG/SVG/GIF qatinapaq, [`pineal-{demo,financial-demo,phosphor-demo,stream-demo}`](pineal-demo/README.md) muestrakuna.
Tukuy crates yachachiyninwan iskay versionkunapi: [`pineal-core`](pineal-core/README.md) escena modelo, [`pineal-render`](pineal-render/README.md) Llimphi rikuchikuy, [`pineal-{cartesian,polar,mesh,treemap,phosphor,flow,heatmap,stream,financial,umbrella}`](pineal-cartesian/README.md) sapanka layakuna, [`pineal-export`](pineal-export/README.md) PNG/SVG/GIF qatinapaq, [`pineal-{demo,financial-demo,phosphor-demo,stream-demo}`](demos/pineal-demo/README.md) muestrakuna.
## Yuyaykunaq
@@ -0,0 +1,27 @@
# pineal-bars-demo
*Read this in English: [README.md](README.md).*
El painter de barras del catálogo, en sus cinco modos, sobre una grilla estática:
- **Columnas** (vertical simple, con un valor negativo para mostrar el
baseline).
- **Barras** (horizontal simple).
- **Agrupadas** (dos series clustered por categoría).
- **Apiladas** (segmentos sobre un baseline común).
- **Histograma** (muestra gaussiana sintética bineada).
Datos 100 % deterministas (sin `Date::now`/random): la muestra del
histograma sale de un LCG sembrado con constante. Es un showcase, sin
animación. Las etiquetas de eje no las pinta el painter (regla del
SDD: el texto va aparte) — aquí los títulos viven en Views hermanas.
## Uso
```sh
cargo run --release -p pineal-bars-demo
```
---
Parte de **pineal** — ver [pineal](../../LEEME.md).
@@ -0,0 +1,12 @@
# pineal-bars-demo
The catalog's bar painter in its five modes, over a static grid:
- **Columns** (simple vertical, with a negative value to show the baseline).
- **Bars** (simple horizontal).
- **Grouped** (two series clustered per category).
- **Stacked** and the remaining variants.
---
Part of **pineal** — see [pineal](../../README.md).
@@ -11,7 +11,7 @@
//! Datos 100 % deterministas (sin `Date::now`/random): la muestra del
//! histograma sale de un LCG sembrado con constante. Es un showcase, sin
//! animación. Las etiquetas de eje no las pinta el painter (regla del
//! SDD: el texto va aparte) — a los títulos viven en Views hermanas.
//! SDD: el texto va aparte) — aquí los títulos viven en Views hermanas.
use std::sync::Arc;
@@ -0,0 +1,21 @@
# pineal-contour-demo
*Read this in English: [README.md](README.md).*
Campo escalar con 8 isolíneas + heatmap base.
Renderiza primero el heatmap Viridis del campo (para contexto), y
encima 8 isolíneas extraídas por marching squares con gradiente
azul→rojo. Matriz 64×48; el campo es
`sin(x · 0.4 - t · 0.1) + cos(y · 0.4 + t · 0.07)` con un tick lento
para que se vea la deformación.
## Uso
```sh
cargo run --release -p pineal-contour-demo
```
---
Parte de **pineal** — ver [pineal](../../LEEME.md).
@@ -0,0 +1,11 @@
# pineal-contour-demo
A scalar field with 8 isolines plus a base heatmap.
It first renders the field's Viridis heatmap (for context), and over it 8 isolines
extracted by marching squares with a blue→red gradient. A 64×48 matrix; the field
is `sin(x · 0.4 - t · 0.1) + cos(y · 0.4 + t · 0.07)` with a slow tick.
---
Part of **pineal** — see [pineal](../../README.md).
@@ -1,8 +1,8 @@
# pineal-demo
> Demo gallery del catálogo de [pineal](../README.md).
> Demo gallery del catálogo de [pineal](../../README.md).
Binario que muestra todos los backends (`cartesian`, `polar`, `mesh`, `treemap`, `flow`, `heatmap`, `umbrella`) en una grid Llimphi. Click en una demo abre la vista completa. Sirve como **regression visual**: si rompés un backend, la demo lo grita.
Binario que muestra todos los backends (`cartesian`, `polar`, `mesh`, `treemap`, `flow`, `heatmap`, `umbrella`) en una grid Llimphi. Click en una demo abre la vista completa. Sirve como **regression visual**: si rompes un backend, la demo lo grita.
## Uso
@@ -13,4 +13,4 @@ cargo run --release -p pineal-demo
## Deps
- Todos los backends de pineal
- [`llimphi-ui`](../../../02_ruway/llimphi/)
- [`llimphi-ui`](../../../../02_ruway/llimphi/)
@@ -1,6 +1,6 @@
# pineal-demo
> Catalog demo gallery for [pineal](../README.md).
> Catalog demo gallery for [pineal](../../README.md).
Binary that shows all backends (`cartesian`, `polar`, `mesh`, `treemap`, `flow`, `heatmap`, `umbrella`) in a Llimphi grid. Click on a demo opens the full view. Works as **visual regression**: if you break a backend, the demo screams.
@@ -13,4 +13,4 @@ cargo run --release -p pineal-demo
## Deps
- All pineal backends
- [`llimphi-ui`](../../../02_ruway/llimphi/)
- [`llimphi-ui`](../../../../02_ruway/llimphi/)
@@ -1,8 +1,8 @@
# pineal-financial-demo
> Demo del backend financiero de [pineal](../README.md).
> Demo del backend financiero de [pineal](../../README.md).
Carga bars OHLCV sintéticos (o de un CSV pasado por CLI), aplica overlays (SMA, EMA, Bollinger) y los muestra. Si pasás un símbolo y tenés conexión, intenta cargar bars desde un endpoint público — pero por defecto opera offline con datos generados.
Carga bars OHLCV sintéticos (o de un CSV pasado por CLI), aplica overlays (SMA, EMA, Bollinger) y los muestra. Si pasas un símbolo y tienes conexión, intenta cargar bars desde un endpoint público — pero por defecto opera offline con datos generados.
## Uso
@@ -13,4 +13,4 @@ cargo run --release -p pineal-financial-demo -- --csv bars.csv
## Deps
- [`pineal-financial`](../pineal-financial/README.md), [`pineal-render`](../pineal-render/README.md)
- [`pineal-financial`](../../pineal-financial/README.md), [`pineal-render`](../../pineal-render/README.md)
@@ -1,6 +1,6 @@
# pineal-financial-demo
> Financial backend demo for [pineal](../README.md).
> Financial backend demo for [pineal](../../README.md).
Loads synthetic OHLCV bars (or from a CSV passed via CLI), applies overlays (SMA, EMA, Bollinger) and renders them. If you pass a symbol with connectivity, it tries to load bars from a public endpoint — but by default it works offline with generated data.
@@ -13,4 +13,4 @@ cargo run --release -p pineal-financial-demo -- --csv bars.csv
## Deps
- [`pineal-financial`](../pineal-financial/README.md), [`pineal-render`](../pineal-render/README.md)
- [`pineal-financial`](../../pineal-financial/README.md), [`pineal-render`](../../pineal-render/README.md)
@@ -0,0 +1,20 @@
# pineal-flow-demo
*Read this in English: [README.md](README.md).*
Sankey de presupuesto familiar.
4 fuentes de ingreso → 5 categorías de gasto → 1 nodo de ahorro.
El algoritmo de layout (longest-path + barycenter) ubica los
nodos en columnas y minimiza cruces; las bandas se tesselan con
curva S (smoothstep) y se rendean como triangle strips.
## Uso
```sh
cargo run --release -p pineal-flow-demo
```
---
Parte de **pineal** — ver [pineal](../../LEEME.md).
@@ -0,0 +1,12 @@
# pineal-flow-demo
A Sankey of a household budget.
Four income sources → five spending categories → one savings node. The layout
algorithm (longest-path + barycenter) places the nodes in columns and minimizes
crossings; the bands are tessellated with an S curve (smoothstep) and rendered as
triangle strips.
---
Part of **pineal** — see [pineal](../../README.md).
@@ -21,3 +21,11 @@ pineal-contour = { path = "../../pineal-contour" }
pineal-flow = { path = "../../pineal-flow" }
pineal-mesh = { path = "../../pineal-mesh" }
pineal-bars = { path = "../../pineal-bars" }
[dev-dependencies]
# Sólo para los examples headless (`pantallazo_pineal`, `pineal_showreel`).
pollster = { workspace = true }
png = { workspace = true }
# Beats animados extra del showreel (no los usa la galería estática).
pineal-financial = { path = "../../pineal-financial" }
pineal-cartesian = { path = "../../pineal-cartesian" }
@@ -0,0 +1,33 @@
# pineal-galeria-demo
*Read this in English: [README.md](README.md).*
Galería estática de TODO el catálogo de painters.
Una sola ventana con una grilla 3×3 de tiles; cada tile pinta un
painter distinto de pineal con datos sintéticos deterministas (sin
timers, sin RNG de sistema): cartesian (sinusoide a mano), polar
(pie/donut y radar), treemap, heatmap, hexbin, contour, flow (Sankey)
y mesh (grafo force-directed pre-relajado).
Los painters animados (phosphor, stream) y el financial tienen su
propio demo en vivo y quedan FUERA — la galería es un showcase
estático. El tile #9 es un cartesiano extra (segunda señal) para
completar la grilla.
Cada tile reusa la construcción de datos de su demo suelto en
`pineal-<painter>-demo`, así que las firmas son las reales. Cada
closure de `paint_with` captura SUS datos por `move`.
Cableado de UI: barra de menú principal mínima (Archivo / Ver). Los
tiles son canvas estáticos sin edición ni clipboard.
## Uso
```sh
cargo run --release -p pineal-galeria-demo
```
---
Parte de **pineal** — ver [pineal](../../LEEME.md).
@@ -0,0 +1,12 @@
# pineal-galeria-demo
A static gallery of the WHOLE painter catalog.
One window with a 3×3 grid of tiles; each tile paints a different pineal painter
with deterministic synthetic data (no timers, no system RNG): cartesian (a
hand-made sinusoid), polar (pie/donut and radar), treemap, heatmap, hexbin,
contour, flow (Sankey) and the rest.
---
Part of **pineal** — see [pineal](../../README.md).
@@ -0,0 +1,159 @@
//! Pantallazo headless de `pineal` — la galería completa de painters.
//!
//! Monta la **view real** de `pineal-galeria-demo` (grilla de 11 tiles:
//! cartesian, pie/donut, radar, treemap, heatmap, hexbin, contour, sankey,
//! mesh force-directed, bars e histograma — todos con datos sintéticos
//! deterministas, sin reloj ni RNG de sistema) reutilizando el `init` y el
//! `view` del demo vía `#[path]`, con un `Handle::for_test()` (no hay
//! event loop).
//!
//! Pinta a una textura wgpu sin ventana y vuelca PNG (mismo patrón que
//! `tullpu-app-llimphi/examples/pantallazo_tullpu.rs`).
//!
//! `cargo run -p pineal-galeria-demo --example pantallazo_pineal --release -- [out.png]`
#![allow(dead_code)]
// El demo es un crate binario sin lib: incluimos su `main.rs` real por
// `#[path]` para montar exactamente la misma view que pinta la ventana.
#[path = "../src/main.rs"]
mod galeria;
use std::fs::File;
use std::io::BufWriter;
use llimphi_theme::Theme;
use llimphi_ui::llimphi_hal::{wgpu, Hal};
use llimphi_ui::llimphi_layout::taffy;
use llimphi_ui::llimphi_layout::LayoutTree;
use llimphi_ui::llimphi_raster::peniko::Color;
use llimphi_ui::llimphi_raster::{vello, Renderer};
use llimphi_ui::llimphi_text::Typesetter;
use llimphi_ui::{measure_text_node, mount, paint, App, Handle};
use crate::galeria::GaleriaDemo;
const W: u32 = 1280;
const H: u32 = 860;
const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
fn main() {
let out = std::env::args()
.nth(1)
.unwrap_or_else(|| "/tmp/shots/pineal.png".to_string());
if let Some(dir) = std::path::Path::new(&out).parent() {
std::fs::create_dir_all(dir).ok();
}
// El init real del demo: relaja el grafo del tile mesh, bina el hexbin
// y computa el campo escalar. El handle de test descarta dispatches.
let handle: Handle<galeria::Msg> = Handle::for_test();
let model = GaleriaDemo::init(&handle);
let root = GaleriaDemo::view(&model);
// view → layout → scene (misma secuencia que el eventloop real).
let mut layout = LayoutTree::new();
let mounted = mount(&mut layout, root);
let mut ts = Typesetter::new();
let computed = {
let tmap = &mounted.text_measures;
layout
.compute_with_measure(mounted.root, (W as f32, H as f32), |nid, known, avail| {
match tmap.get(&nid) {
Some(tm) => measure_text_node(&mut ts, tm, known, avail),
None => taffy::Size::ZERO,
}
})
.expect("layout")
};
let mut scene = vello::Scene::new();
paint(&mut scene, &mounted, &computed, &mut ts, None, None);
let hal = pollster::block_on(Hal::new(None)).expect("hal");
let mut renderer = Renderer::new(&hal).expect("renderer");
let target = hal.device.create_texture(&wgpu::TextureDescriptor {
label: Some("pantallazo-pineal"),
size: wgpu::Extent3d {
width: W,
height: H,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: FMT,
usage: wgpu::TextureUsages::STORAGE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let view = target.create_view(&wgpu::TextureViewDescriptor::default());
// Mismo fondo que el init del demo (Theme::dark) — el campo `theme`
// del Model es privado al módulo incluido.
let [r, g, b, _] = Theme::dark().bg_app.components;
let bg = Color::from_rgba8((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8, 255);
renderer
.render_to_view(&hal, &scene, &view, W, H, bg)
.expect("render_to_view");
write_png(&hal, &target, &out);
eprintln!("pantallazo_pineal: escrito {out} ({W}x{H})");
}
/// Lee la textura a CPU y la vuelca como PNG RGBA8.
fn write_png(hal: &Hal, target: &wgpu::Texture, path: &str) {
let unpadded = (W * 4) as usize;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
let padded = unpadded.div_ceil(align) * align;
let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("readback"),
size: (padded * H as usize) as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let mut enc = hal
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
enc.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: target,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &buf,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded as u32),
rows_per_image: Some(H),
},
},
wgpu::Extent3d {
width: W,
height: H,
depth_or_array_layers: 1,
},
);
hal.queue.submit(std::iter::once(enc.finish()));
let slice = buf.slice(..);
let (tx, rx) = std::sync::mpsc::channel();
slice.map_async(wgpu::MapMode::Read, move |r| {
let _ = tx.send(r);
});
let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
rx.recv().unwrap().unwrap();
let data = slice.get_mapped_range();
let mut pixels = Vec::with_capacity((W * H * 4) as usize);
for row in 0..H as usize {
let s = row * padded;
pixels.extend_from_slice(&data[s..s + unpadded]);
}
drop(data);
buf.unmap();
let file = File::create(path).expect("png");
let mut enc = png::Encoder::new(BufWriter::new(file), W, H);
enc.set_color(png::ColorType::Rgba);
enc.set_depth(png::BitDepth::Eight);
let mut w = enc.write_header().unwrap();
w.write_image_data(&pixels).unwrap();
}
@@ -0,0 +1,819 @@
//! **Showreel** de `pineal` — montaje de tipos de gráfico para el README
//! del standalone (audiencia r/rust). NO es eye-candy abstracto: cada beat
//! invoca un **painter REAL** del catálogo de pineal (`paint_bars`,
//! `paint_heatmap`, `paint_contours`, `paint_pie`, `paint_radar`,
//! `paint_treemap`, `paint_hexbin`, `paint_sankey`, el grafo force-directed
//! de `pineal-mesh` y los candlesticks de `pineal-financial`) sobre el
//! `Canvas` de `pineal-render` (backend vello/wgpu de Llimphi).
//!
//! El render es **headless y determinista** (sin reloj, sin runtime, sin
//! winit): frame `i` de `N` → `t = i/(N-1)` → `vello::Scene` → wgpu → PNG.
//! El stage central cruza de un gráfico al siguiente con **cross-fade**
//! (capas vello con alpha), y los datos de cada gráfico se derivan de su
//! progreso local para que *animen* mientras están en pantalla. Cold-open
//! sobrio (trazo bezier draw-on) + wordmark final **"pineal"** con
//! subtítulo "data visualization, in Rust".
//!
//! ```text
//! cargo run -p pineal-galeria-demo --example pineal_showreel --release -- \
//! [out_dir] [n_frames] [W] [H]
//! ```
//! Defaults: `out_dir=showreel_frames_pineal`, `n_frames=300`, `W=1600`, `H=900`.
#![allow(clippy::too_many_arguments)]
use std::fs::{create_dir_all, File};
use std::io::BufWriter;
use llimphi_ui::llimphi_hal::{wgpu, Hal};
use llimphi_ui::llimphi_raster::kurbo::{Affine, BezPath, Circle, Point as KPoint, Stroke};
use llimphi_ui::llimphi_raster::peniko::{self, Color as PColor, Fill, Gradient};
use llimphi_ui::llimphi_raster::{vello, Renderer};
use llimphi_ui::llimphi_text::{
draw_layout_brush_xf, measurement, Alignment, Typesetter,
};
use pineal_bars::{paint_bars, Bar, BarStyle, Histogram};
use pineal_cartesian::{ChartViewport, CoordinateSystem};
use pineal_contour::paint_contours;
use pineal_financial::{paint_candlesticks, CandlestickStyle, OhlcBuffer};
use pineal_flow::{compute_layout, paint_sankey, SankeyLink, SankeyNode};
use pineal_heatmap::{paint as paint_heatmap, HeatmapMatrix, Ramp};
use pineal_mesh::{EdgeBuffer, ForceLayout, ForceParams, NodeBuffer};
use pineal_polar::{paint_pie, paint_radar, Slice};
use pineal_render::{Canvas as _, Color, Point, Rect, SceneCanvas, StrokeStyle};
use pineal_treemap::{paint_treemap, Tile};
const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
// ───────────────────────── utilidades de tiempo / color ─────────────────────────
/// Reescala `t` desde el subintervalo `[lo,hi]` a `[0,1]`, clampado.
fn seg(t: f32, lo: f32, hi: f32) -> f32 {
((t - lo) / (hi - lo)).clamp(0.0, 1.0)
}
fn lerp(a: f64, b: f64, t: f64) -> f64 {
a + (b - a) * t
}
fn ease_in_out_cubic(t: f32) -> f32 {
if t < 0.5 {
4.0 * t * t * t
} else {
let f = 2.0 * t - 2.0;
0.5 * f * f * f + 1.0
}
}
fn ease_out_cubic(t: f32) -> f32 {
let f = t - 1.0;
f * f * f + 1.0
}
fn ease_out_back(t: f32) -> f32 {
let c1 = 1.70158_f32;
let c3 = c1 + 1.0;
let f = t - 1.0;
1.0 + c3 * f * f * f + c1 * f * f
}
/// `PColor` (peniko) con alpha sobreescrito.
fn palpha(c: PColor, a: f32) -> PColor {
let [r, g, b, _] = c.components;
PColor::new([r, g, b, a.clamp(0.0, 1.0)])
}
// ───────────────────────── paleta del showreel ─────────────────────────
struct Skin {
bg: PColor,
panel_hi: PColor,
panel_lo: PColor,
border: PColor,
accent: PColor,
fg: PColor,
fg_muted: PColor,
/// Fondo del área de plot dentro del stage (oscuro como en la galería).
plot_bg: Color,
}
fn skin() -> Skin {
Skin {
bg: PColor::from_rgba8(0x0C, 0x0E, 0x12, 0xFF),
panel_hi: PColor::from_rgba8(0x18, 0x1D, 0x26, 0xFF),
panel_lo: PColor::from_rgba8(0x10, 0x14, 0x1B, 0xFF),
border: PColor::from_rgba8(0x2A, 0x31, 0x3E, 0xFF),
accent: PColor::from_rgba8(0x2B, 0xD9, 0xA6, 0xFF), // teal firma
fg: PColor::from_rgba8(0xE8, 0xEC, 0xF2, 0xFF),
fg_muted: PColor::from_rgba8(0x8A, 0x93, 0xA3, 0xFF),
plot_bg: Color::rgba(0.043, 0.055, 0.078, 1.0),
}
}
// ───────────────────────── stage central ─────────────────────────
/// Rect (pineal) interior del stage donde pinta el painter, con padding.
fn plot_rect_of(stage: KurboRectLite, pad: f64) -> Rect {
Rect::new(
(stage.x + pad) as f32,
(stage.y + pad) as f32,
(stage.w - 2.0 * pad) as f32,
(stage.h - 2.0 * pad) as f32,
)
}
#[derive(Clone, Copy)]
struct KurboRectLite {
x: f64,
y: f64,
w: f64,
h: f64,
}
// ───────────────────────── los 8 beats: cada uno un painter REAL ─────────────────────────
//
// `lp ∈ [0,1]` = progreso local del beat (para animar datos). `area` = rect
// de plot (coords pineal). Todos hablan contra el trait `Canvas` vía
// `SceneCanvas`. El fondo del plot ya está pintado por el caller.
const N_BEATS: usize = 8;
fn beat_label(i: usize) -> (&'static str, &'static str) {
match i {
0 => ("bars", "columnas + histograma"),
1 => ("heatmap", "campo escalar · Viridis"),
2 => ("contour", "marching squares · 8 niveles"),
3 => ("polar", "pie / donut + radar"),
4 => ("treemap", "squarified"),
5 => ("financial", "candlesticks OHLC"),
6 => ("flow", "diagrama Sankey"),
_ => ("mesh", "grafo force-directed"),
}
}
/// Despacha el painter del beat `i` con progreso local `lp`.
fn paint_beat(i: usize, lp: f32, canvas: &mut SceneCanvas<'_>, area: Rect, plotbg: Color) {
canvas.fill_rect(area, plotbg);
match i {
0 => beat_bars(lp, canvas, area),
1 => beat_heatmap(lp, canvas, area),
2 => beat_contour(lp, canvas, area),
3 => beat_polar(lp, canvas, area),
4 => beat_treemap(lp, canvas, area),
5 => beat_financial(lp, canvas, area),
6 => beat_sankey(lp, canvas, area),
_ => beat_mesh(lp, canvas, area),
}
}
/// 0 — bars: barras a la izquierda (con un negativo) + histograma a la
/// derecha. Las alturas crecen desde el baseline con `lp`.
fn beat_bars(lp: f32, canvas: &mut SceneCanvas<'_>, area: Rect) {
let g = ease_out_cubic(seg(lp, 0.0, 0.85));
let raw = [4.0, 7.0, 2.0, -3.0, 5.0, 6.5, 3.5, 5.8];
let bars: Vec<Bar> = raw
.iter()
.map(|&v| {
let c = if v < 0.0 { 0xd08770 } else { 0x2bd9a6 };
Bar::new(v * g as f64, Color::from_hex(c))
})
.collect();
let half = area.w * 0.5 - 12.0;
let left = Rect::new(area.x + 8.0, area.y + 8.0, half - 8.0, area.h - 16.0);
paint_bars(&bars, left, &BarStyle::vertical().with_gap(0.18), canvas);
// Histograma (gaussiana de un LCG sembrado), altura escalada por lp.
let mut rng: u32 = 0x0BAD_F00D;
let mut next = || {
rng = rng.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(rng >> 8) as f32 / (1u32 << 24) as f32
};
let mut sample = Vec::with_capacity(4000);
for _ in 0..4000 {
let gg: f32 = (0..6).map(|_| next()).sum::<f32>() / 6.0;
sample.push((gg - 0.5) * 6.0);
}
let mut hbars = Histogram::new(&sample, 28).to_bars(Color::from_hex(0xb48ead));
for b in hbars.iter_mut() {
b.value *= g as f64;
}
let right = Rect::new(area.x + half + 16.0, area.y + 8.0, half - 8.0, area.h - 16.0);
paint_bars(&hbars, right, &BarStyle::vertical().with_gap(0.04), canvas);
}
/// Campo escalar 64×40 animado por `lp` (la fase de las ondas avanza).
fn animated_field(lp: f32) -> HeatmapMatrix {
const W: usize = 64;
const H: usize = 40;
let ph = lp * std::f32::consts::TAU * 0.5;
let mut m = HeatmapMatrix::new(W, H);
let mut data = Vec::with_capacity(W * H);
for y in 0..H {
for x in 0..W {
let v = (x as f32 * 0.22 + ph).sin() + (y as f32 * 0.26 - ph * 0.7).cos()
+ 0.6 * ((x as f32 * 0.1).sin() * (y as f32 * 0.12).cos());
data.push(v);
}
}
m.replace_data(data);
m
}
/// 1 — heatmap Viridis del campo animado.
fn beat_heatmap(lp: f32, canvas: &mut SceneCanvas<'_>, area: Rect) {
let field = animated_field(lp);
paint_heatmap(&field, Ramp::Viridis, area, canvas);
}
/// 2 — contour: el mismo campo + isolíneas (marching squares). El nº de
/// niveles sube de 3 a 9 con `lp` (las curvas "aparecen").
fn beat_contour(lp: f32, canvas: &mut SceneCanvas<'_>, area: Rect) {
let field = animated_field(lp * 0.6);
paint_heatmap(&field, Ramp::Viridis, area, canvas);
let levels = (3.0 + 6.0 * ease_out_cubic(seg(lp, 0.0, 0.9))) as usize;
paint_contours(
&field,
levels.max(2),
area,
Color::rgba(0.35, 0.62, 1.0, 0.9),
Color::rgba(1.0, 0.42, 0.32, 0.95),
1.3,
canvas,
);
}
/// 3 — polar: donut a la izquierda (barrido por lp) + radar a la derecha
/// (vértices que crecen). Dos painters de pineal-polar en un beat.
fn beat_polar(lp: f32, canvas: &mut SceneCanvas<'_>, area: Rect) {
let g = ease_out_cubic(seg(lp, 0.0, 0.85));
// Donut.
let cx = area.x + area.w * 0.27;
let cy = area.y + area.h * 0.5;
let r_out = (area.w.min(area.h) * 0.34).max(20.0);
let r_in = r_out * 0.5;
let base = [28.0_f32, 18.0, 14.0, 12.0, 10.0, 8.0];
let pal = [0x2bd9a6, 0xd08770, 0xa3be8c, 0xebcb8b, 0xb48ead, 0x5e81ac];
let slices: Vec<Slice> = base
.iter()
.zip(pal.iter())
.map(|(&v, &c)| Slice::new(v * g, Color::from_hex(c)))
.collect();
if g > 0.01 {
paint_pie(&slices, Point::new(cx, cy), r_out, r_in, canvas);
}
// Radar.
let rcx = area.x + area.w * 0.73;
let rcy = cy;
let rr = (area.w.min(area.h) * 0.34).max(20.0);
for step in 1..=4 {
let t = step as f32 / 4.0;
let ring: Vec<f32> = (0..=72)
.flat_map(|k| {
let a = (k as f32 / 72.0) * std::f32::consts::TAU - std::f32::consts::FRAC_PI_2;
[rcx + (rr * t) * a.cos(), rcy + (rr * t) * a.sin()]
})
.collect();
canvas.stroke_polyline(&ring, StrokeStyle::new(0.6, Color::rgba(0.5, 0.56, 0.66, 0.35)));
}
let values: Vec<f32> = [8.0_f32, 6.5, 9.0, 4.0, 7.0, 5.5].iter().map(|v| v * g).collect();
paint_radar(
&values,
10.0,
Point::new(rcx, rcy),
rr,
Color::rgba(0.169, 0.851, 0.651, 0.30),
StrokeStyle::new(1.8, Color::from_hex(0x2bd9a6)),
canvas,
);
}
/// 4 — treemap squarified: 14 tiles, los pesos respiran con `lp`.
fn beat_treemap(lp: f32, canvas: &mut SceneCanvas<'_>, area: Rect) {
let pal = [
0x2bd9a6, 0xd08770, 0xa3be8c, 0xebcb8b, 0xb48ead, 0x5e81ac, 0x81a1c1, 0xbf616a,
0x8fbcbb, 0xd8dee9, 0x88c0d0, 0xebcb8b, 0xa3be8c, 0x5e81ac,
];
let base = [40.0, 28.0, 22.0, 18.0, 14.0, 12.0, 10.0, 8.0, 6.0, 5.0, 4.0, 3.5, 3.0, 2.0];
let wob = lp * std::f32::consts::TAU;
let tiles: Vec<Tile> = base
.iter()
.zip(pal.iter())
.enumerate()
.map(|(k, (&w, &c))| {
let m = 1.0 + 0.25 * (wob + k as f32 * 0.6).sin();
Tile::new((w * m) as f64, Color::from_hex(c))
})
.collect();
let inset = Rect::new(area.x + 6.0, area.y + 6.0, area.w - 12.0, area.h - 12.0);
paint_treemap(&tiles, inset, 3.0, canvas);
}
/// 5 — financial: candlesticks OHLC (random-walk determinista). El nº de
/// velas visibles crece con `lp` (se van "imprimiendo" de izq a der).
fn beat_financial(lp: f32, canvas: &mut SceneCanvas<'_>, area: Rect) {
const N: usize = 64;
let mut buf = OhlcBuffer::with_capacity(N);
let mut rng: u32 = 0xCAFE_1234;
let mut next = || {
rng = rng.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
((rng >> 8) as f32 / (1u32 << 24) as f32) - 0.5
};
let mut price = 100.0_f32;
let mut lo_all = f32::MAX;
let mut hi_all = f32::MIN;
let shown = ((N as f32) * ease_out_cubic(seg(lp, 0.0, 0.95))).round() as usize;
let shown = shown.clamp(2, N);
for i in 0..shown {
let o = price;
let drift = next() * 4.0 + 0.25 * (i as f32 * 0.3).sin();
let c = (o + drift).max(5.0);
let hi = o.max(c) + next().abs() * 3.0;
let lo = o.min(c) - next().abs() * 3.0;
buf.push_values(i as f32, o, hi, lo, c, 1.0);
price = c;
lo_all = lo_all.min(lo);
hi_all = hi_all.max(hi);
}
if hi_all <= lo_all {
return;
}
let pad = (hi_all - lo_all) * 0.08;
let vp = ChartViewport::new(
-1.0,
N as f64,
(lo_all - pad) as f64,
(hi_all + pad) as f64,
);
let plot = Rect::new(area.x + 6.0, area.y + 6.0, area.w - 12.0, area.h - 12.0);
let cs = CoordinateSystem::new(vp, plot);
let mut style = CandlestickStyle::default();
style.bull_color = Color::from_hex(0x2bd9a6);
style.bear_color = Color::from_hex(0xe06c75);
style.body_width_ratio = 0.68;
paint_candlesticks(canvas, &cs, &buf, style);
}
/// 6 — flow: diagrama Sankey de un presupuesto. Los ribbons aparecen con
/// `lp` vía el alpha del color de link.
fn beat_sankey(lp: f32, canvas: &mut SceneCanvas<'_>, area: Rect) {
let nodes: Vec<SankeyNode> = [
"Sueldo", "Freelance", "Renta", "Dividendos", "Vivienda", "Comida", "Transporte",
"Ocio", "Salud", "Ahorro",
]
.iter()
.map(|n| SankeyNode::new(*n))
.collect();
let links: Vec<SankeyLink> = vec![
SankeyLink { source: 0, target: 4, value: 1200.0 },
SankeyLink { source: 0, target: 5, value: 600.0 },
SankeyLink { source: 0, target: 6, value: 250.0 },
SankeyLink { source: 0, target: 9, value: 950.0 },
SankeyLink { source: 1, target: 5, value: 200.0 },
SankeyLink { source: 1, target: 7, value: 300.0 },
SankeyLink { source: 1, target: 9, value: 400.0 },
SankeyLink { source: 2, target: 4, value: 400.0 },
SankeyLink { source: 2, target: 8, value: 150.0 },
SankeyLink { source: 3, target: 9, value: 350.0 },
SankeyLink { source: 3, target: 7, value: 80.0 },
];
let inset = Rect::new(area.x + 16.0, area.y + 16.0, area.w - 32.0, area.h - 32.0);
let layout = compute_layout(&nodes, &links, inset, 16.0, 8.0);
let ribbon_a = 0.10 + 0.40 * ease_out_cubic(seg(lp, 0.0, 0.85));
paint_sankey(
&layout,
Color::from_hex(0xe5e9f0),
Color::rgba(0.169, 0.851, 0.651, ribbon_a),
canvas,
);
}
/// 7 — mesh: grafo force-directed pre-relajado, que rota lentamente con
/// `lp` para que se note la profundidad de la maraña.
fn beat_mesh(lp: f32, canvas: &mut SceneCanvas<'_>, area: Rect) {
let g = relaxed_graph();
let cx = area.x + area.w * 0.5;
let cy = area.y + area.h * 0.5;
let scale = (area.w.min(area.h) / 170.0).max(0.5);
let ang = lp * std::f32::consts::TAU * 0.15;
let (sa, ca) = ang.sin_cos();
let rot = |x: f32, y: f32| -> (f32, f32) {
(cx + (x * ca - y * sa) * scale, cy + (x * sa + y * ca) * scale)
};
let edge = StrokeStyle::new(1.0, Color::rgba(0.55, 0.62, 0.7, 0.4));
for (u, v) in g.edges.iter() {
let (xu, yu) = g.nodes.pos(u);
let (xv, yv) = g.nodes.pos(v);
let (ax, ay) = rot(xu, yu);
let (bx, by) = rot(xv, yv);
canvas.stroke_line(Point::new(ax, ay), Point::new(bx, by), edge);
}
let n = g.nodes.len();
for i in 0..n {
let (x, y) = g.nodes.pos(i);
let r = g.nodes.radius(i) * scale;
let (px, py) = rot(x, y);
let color = if i < N_RING { Color::from_hex(0x2bd9a6) } else { Color::from_hex(0xa3be8c) };
canvas.fill_rect(Rect::new(px - r, py - r, r * 2.0, r * 2.0), color);
}
}
// ── grafo del beat mesh (mismo armado que pineal-mesh-demo / la galería) ──
const N_RING: usize = 12;
const N_SAT: usize = 12;
struct Graph {
nodes: NodeBuffer,
edges: EdgeBuffer,
}
fn relaxed_graph() -> Graph {
let mut nodes = NodeBuffer::new();
for i in 0..N_RING {
let a = (i as f32 / N_RING as f32) * std::f32::consts::TAU;
nodes.push(20.0 * a.cos(), 20.0 * a.sin(), 6.0);
}
for i in 0..N_SAT {
let a = (i as f32 / N_SAT as f32) * std::f32::consts::TAU + 0.13;
nodes.push(60.0 * a.cos(), 60.0 * a.sin(), 4.5);
}
let mut edges = EdgeBuffer::new();
for i in 0..N_RING {
edges.push(i, (i + 1) % N_RING);
}
for i in 0..N_RING {
edges.push(i, (i + 3) % N_RING);
}
for i in 0..N_SAT {
edges.push(i, N_RING + i);
}
let mut sim = ForceLayout::new(ForceParams { k: 38.0, temperature: 60.0, cooling: 0.985 });
for _ in 0..400 {
let _ = sim.step(&mut nodes, &edges);
}
Graph { nodes, edges }
}
// ───────────────────────── overlays vector (cold-open + wordmark + chrome) ─────────────────────────
fn signature_path(cw: f64, ch: f64) -> BezPath {
let cx = cw / 2.0;
let cy = ch / 2.0;
let mut p = BezPath::new();
p.move_to((cx - 380.0, cy + 30.0));
p.curve_to(
(cx - 150.0, cy - 200.0),
(cx + 150.0, cy + 200.0),
(cx + 380.0, cy - 30.0),
);
p
}
fn trim_path(full: &BezPath, prog: f64) -> (BezPath, KPoint) {
use vello::kurbo::ParamCurve;
let prog = prog.clamp(0.0, 1.0);
let mut cubic = None;
let mut start = KPoint::ZERO;
for el in full.elements() {
match el {
vello::kurbo::PathEl::MoveTo(p) => start = *p,
vello::kurbo::PathEl::CurveTo(c1, c2, p) => {
cubic = Some(vello::kurbo::CubicBez::new(start, *c1, *c2, *p));
}
_ => {}
}
}
let mut out = BezPath::new();
let mut head = start;
if let Some(cb) = cubic {
out.move_to(cb.p0);
let steps = 96;
for i in 1..=steps {
let u = (i as f64 / steps as f64) * prog;
let pt = cb.eval(u);
out.line_to(pt);
head = pt;
}
}
(out, head)
}
// ───────────────────────── la escena por frame ─────────────────────────
fn render_frame(scene: &mut vello::Scene, ts: &mut Typesetter, t: f32, cw: f64, ch: f64, s: &Skin) {
// Fondo (gradiente sutil).
let bg_rect = vello::kurbo::Rect::new(0.0, 0.0, cw, ch);
let grad = Gradient::new_linear(KPoint::new(0.0, 0.0), KPoint::new(0.0, ch))
.with_stops([s.panel_lo, s.bg].as_slice());
scene.fill(Fill::NonZero, Affine::IDENTITY, &grad, None, &bg_rect);
// Geometría del stage central (gran panel donde viven los gráficos).
let margin_x = 200.0_f64;
let stage_top = 178.0_f64;
let stage_bottom = 120.0_f64;
let stage = KurboRectLite {
x: margin_x,
y: stage_top,
w: cw - 2.0 * margin_x,
h: ch - stage_top - stage_bottom,
};
// Aparición del chrome (panel) en el cold-open; desaparición antes del wordmark.
let chrome_in = ease_out_cubic(seg(t, 0.06, 0.16));
let chrome_out = 1.0 - ease_in_out_cubic(seg(t, 0.86, 0.93));
let chrome_a = (chrome_in * chrome_out).clamp(0.0, 1.0);
// Ventana de los beats en la timeline.
let beats_lo = 0.12_f32;
let beats_hi = 0.86_f32;
if chrome_a > 0.001 {
// Panel del stage (card con borde + gradiente + sombra simulada).
let r = vello::kurbo::RoundedRect::new(
stage.x,
stage.y,
stage.x + stage.w,
stage.y + stage.h,
18.0,
);
// Sombra simple.
let shadow = vello::kurbo::RoundedRect::new(
stage.x,
stage.y + 10.0,
stage.x + stage.w,
stage.y + stage.h + 10.0,
18.0,
);
scene.fill(Fill::NonZero, Affine::IDENTITY, palpha(PColor::BLACK, 0.45 * chrome_a), None, &shadow);
let pgrad = Gradient::new_linear(KPoint::new(0.0, stage.y), KPoint::new(0.0, stage.y + stage.h))
.with_stops([palpha(s.panel_hi, chrome_a), palpha(s.panel_lo, chrome_a)].as_slice());
scene.fill(Fill::NonZero, Affine::IDENTITY, &pgrad, None, &r);
scene.stroke(&Stroke::new(1.2), Affine::IDENTITY, palpha(s.border, chrome_a), None, &r);
// ── beats: cross-fade entre gráfico saliente y entrante ──
let phase = seg(t, beats_lo, beats_hi); // 0..1 sobre todos los beats
let scaled = phase * N_BEATS as f32;
let cur = (scaled.floor() as usize).min(N_BEATS - 1);
let frac = scaled - cur as f32; // 0..1 dentro del beat actual
// Cross-fade en el último 22% de cada beat hacia el siguiente.
let xf = seg(frac, 0.78, 1.0);
let plot = plot_rect_of(stage, 18.0);
let clip = vello::kurbo::RoundedRect::new(
plot.x as f64,
plot.y as f64,
(plot.x + plot.w) as f64,
(plot.y + plot.h) as f64,
10.0,
);
// Progreso local "vivo" del beat actual: 0..1 a lo largo de su tramo.
let lp_cur = frac;
// Gráfico actual (se desvanece durante el cross-fade final).
let a_cur = (1.0 - xf) * chrome_a;
if a_cur > 0.004 {
scene.push_layer(Fill::NonZero, peniko::Mix::Normal, a_cur, Affine::IDENTITY, &clip);
{
let mut canvas = SceneCanvas::new(scene, ts);
paint_beat(cur, lp_cur, &mut canvas, plot, s.plot_bg);
}
scene.pop_layer();
}
// Gráfico entrante (aparece durante el cross-fade).
if xf > 0.004 && cur + 1 < N_BEATS {
let a_next = xf * chrome_a;
scene.push_layer(Fill::NonZero, peniko::Mix::Normal, a_next, Affine::IDENTITY, &clip);
{
let mut canvas = SceneCanvas::new(scene, ts);
// El entrante arranca su animación desde 0.
paint_beat(cur + 1, 0.0, &mut canvas, plot, s.plot_bg);
}
scene.pop_layer();
}
// ── chrome textual: título del beat + subtítulo + barra de progreso ──
let (name, subtitle) = beat_label(cur);
let head_a = chrome_a * (1.0 - seg(frac, 0.86, 1.0)); // pequeño parpadeo al cambiar
let head_a = head_a.max(chrome_a * 0.0);
// Título arriba del stage.
let tl = ts.layout(name, 30.0, None, Alignment::Start, 1.0, false, None, 800.0, false, false, 0.0, 0.0);
let m = measurement(&tl);
let tx = stage.x + 6.0;
let ty = stage.y - 48.0;
// Punto teal de acento a la izquierda del título.
scene.fill(
Fill::NonZero,
Affine::IDENTITY,
palpha(s.accent, head_a),
None,
&Circle::new(KPoint::new(tx + 6.0, ty + m.height as f64 * 0.5), 6.0),
);
let brush = peniko::Brush::Solid(palpha(s.fg, head_a));
draw_layout_brush_xf(scene, &tl, &brush, Affine::translate((tx + 22.0, ty)));
// Subtítulo a la derecha del título.
let sl = ts.layout(subtitle, 15.0, None, Alignment::Start, 1.0, false, None, 400.0, false, false, 0.0, 0.0);
let sbrush = peniko::Brush::Solid(palpha(s.fg_muted, head_a));
draw_layout_brush_xf(
scene,
&sl,
&sbrush,
Affine::translate((tx + 28.0 + m.width as f64, ty + 12.0)),
);
// Barra de progreso de la timeline (debajo del stage).
let bar_y = stage.y + stage.h + 30.0;
let bar_x = stage.x;
let bar_w = stage.w;
let track = vello::kurbo::RoundedRect::new(bar_x, bar_y, bar_x + bar_w, bar_y + 4.0, 2.0);
scene.fill(Fill::NonZero, Affine::IDENTITY, palpha(s.border, chrome_a), None, &track);
let fillw = bar_w * phase as f64;
if fillw > 1.0 {
let fr = vello::kurbo::RoundedRect::new(bar_x, bar_y, bar_x + fillw, bar_y + 4.0, 2.0);
scene.fill(Fill::NonZero, Affine::IDENTITY, palpha(s.accent, chrome_a), None, &fr);
}
// Marcas de los beats (tics).
for k in 0..=N_BEATS {
let mx = bar_x + bar_w * (k as f64 / N_BEATS as f64);
let on = (k as f32 / N_BEATS as f32) <= phase + 0.001;
let col = if on { s.accent } else { s.border };
scene.fill(
Fill::NonZero,
Affine::IDENTITY,
palpha(col, chrome_a),
None,
&Circle::new(KPoint::new(mx, bar_y + 2.0), 3.0),
);
}
// Wordmark pequeño persistente arriba a la izquierda (marca).
let wm = ts.layout("pineal", 22.0, None, Alignment::Start, 1.0, false, None, 800.0, false, false, 0.0, 0.0);
let wmb = peniko::Brush::Solid(palpha(s.fg, chrome_a * 0.9));
draw_layout_brush_xf(scene, &wm, &wmb, Affine::translate((margin_x, 60.0)));
let tagl = ts.layout(
"data visualization, in Rust",
13.0, None, Alignment::Start, 1.0, false, None, 400.0, false, false, 0.0, 0.0,
);
let tagb = peniko::Brush::Solid(palpha(s.fg_muted, chrome_a * 0.9));
draw_layout_brush_xf(scene, &tagl, &tagb, Affine::translate((margin_x + 86.0, 68.0)));
}
// ── COLD OPEN: trazo bezier draw-on (014%) ──
let line_vis = 1.0 - seg(t, 0.13, 0.20);
if line_vis > 0.001 && t < 0.21 {
let path = signature_path(cw, ch);
let draw_on = ease_out_cubic(seg(t, 0.0, 0.12)) as f64;
let (trimmed, head) = trim_path(&path, draw_on);
scene.stroke(
&Stroke::new(2.2),
Affine::IDENTITY,
palpha(s.accent, 0.9 * line_vis),
None,
&trimmed,
);
let pop = ease_out_back(seg(t, 0.0, 0.1));
let r = (4.0 + 7.0 * pop as f64).max(0.0);
let dot_a = (seg(t, 0.0, 0.1) * line_vis).clamp(0.0, 1.0);
scene.fill(Fill::NonZero, Affine::IDENTITY, palpha(s.accent, 0.18 * dot_a), None, &Circle::new(head, r * 3.2));
scene.fill(Fill::NonZero, Affine::IDENTITY, palpha(s.accent, dot_a), None, &Circle::new(head, r));
}
// ── WORDMARK final (88100%) ──
let word_a = ease_out_cubic(seg(t, 0.90, 0.98));
if word_a > 0.001 {
let size = 150.0_f32;
let layout = ts.layout("pineal", size, None, Alignment::Start, 1.0, false, None, 800.0, false, false, 0.0, 0.0);
let m = measurement(&layout);
let rise = lerp(28.0, 0.0, word_a as f64);
let ox = (cw - m.width as f64) / 2.0;
let oy = (ch - m.height as f64) / 2.0 - 24.0 + rise;
let brush = peniko::Brush::Solid(palpha(s.fg, word_a));
draw_layout_brush_xf(scene, &layout, &brush, Affine::translate((ox, oy)));
let sub_a = ease_out_cubic(seg(t, 0.93, 1.0));
if sub_a > 0.001 {
let sy = oy + m.height as f64 + 16.0;
// Subtítulo con punto teal a la izquierda.
let sl = ts.layout(
"data visualization, in Rust",
28.0, None, Alignment::Start, 1.0, false, None, 400.0, false, false, 0.0, 0.0,
);
let sm = measurement(&sl);
let dot_r = 6.0;
let block_w = sm.width as f64 + dot_r * 2.0 + 14.0;
let sx = (cw - block_w) / 2.0;
scene.fill(
Fill::NonZero,
Affine::IDENTITY,
palpha(s.accent, sub_a),
None,
&Circle::new(KPoint::new(sx + dot_r, sy + 28.0 * 0.5), dot_r),
);
let sbrush = peniko::Brush::Solid(palpha(s.fg_muted, sub_a));
draw_layout_brush_xf(scene, &sl, &sbrush, Affine::translate((sx + dot_r * 2.0 + 14.0, sy)));
}
}
}
// ───────────────────────── main: N frames headless → PNG ─────────────────────────
fn main() {
let mut args = std::env::args().skip(1);
let out_dir = args.next().unwrap_or_else(|| "showreel_frames_pineal".to_string());
let n: usize = args.next().and_then(|v| v.parse().ok()).unwrap_or(300);
let w: u32 = args.next().and_then(|v| v.parse().ok()).unwrap_or(1600);
let h: u32 = args.next().and_then(|v| v.parse().ok()).unwrap_or(900);
create_dir_all(&out_dir).expect("mkdir out_dir");
let s = skin();
let [br, bgc, bb, _] = s.bg.components;
let base = PColor::from_rgba8((br * 255.0) as u8, (bgc * 255.0) as u8, (bb * 255.0) as u8, 255);
// GPU una sola vez; reusar device/renderer/target para los N frames.
let hal = pollster::block_on(Hal::new(None)).expect("hal");
let mut renderer = Renderer::new(&hal).expect("renderer");
let target = hal.device.create_texture(&wgpu::TextureDescriptor {
label: Some("showreel-pineal"),
size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: FMT,
usage: wgpu::TextureUsages::STORAGE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let view = target.create_view(&wgpu::TextureViewDescriptor::default());
let mut ts = Typesetter::new();
let cw = w as f64;
let ch = h as f64;
for i in 0..n {
let t = if n <= 1 { 0.0 } else { i as f32 / (n as f32 - 1.0) };
let mut scene = vello::Scene::new();
render_frame(&mut scene, &mut ts, t, cw, ch, &s);
renderer
.render_to_view(&hal, &scene, &view, w, h, base)
.expect("render_to_view");
let path = format!("{out_dir}/frame_{i:04}.png");
write_png(&hal, &target, &path, w, h);
if i % 30 == 0 || i == n - 1 {
eprintln!("pineal_showreel: frame {}/{} (t={:.3})", i + 1, n, t);
}
}
eprintln!("pineal_showreel: {n} frames en {out_dir}/ ({w}x{h})");
}
fn write_png(hal: &Hal, target: &wgpu::Texture, path: &str, w: u32, h: u32) {
let unpadded = (w * 4) as usize;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
let padded = unpadded.div_ceil(align) * align;
let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("readback"),
size: (padded * h as usize) as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let mut enc = hal
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
enc.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: target,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &buf,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded as u32),
rows_per_image: Some(h),
},
},
wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
);
hal.queue.submit(std::iter::once(enc.finish()));
let slice = buf.slice(..);
let (tx, rx) = std::sync::mpsc::channel();
slice.map_async(wgpu::MapMode::Read, move |r| {
let _ = tx.send(r);
});
let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
rx.recv().unwrap().unwrap();
let data = slice.get_mapped_range();
let mut pixels = Vec::with_capacity((w * h * 4) as usize);
for r in 0..h as usize {
let sidx = r * padded;
pixels.extend_from_slice(&data[sidx..sidx + unpadded]);
}
drop(data);
buf.unmap();
let file = File::create(path).expect("png");
let mut enc = png::Encoder::new(BufWriter::new(file), w, h);
enc.set_color(png::ColorType::Rgba);
enc.set_depth(png::BitDepth::Eight);
let mut wr = enc.write_header().unwrap();
wr.write_image_data(&pixels).unwrap();
}
@@ -43,8 +43,11 @@ use pineal_treemap::{paint_treemap, Tile};
// Modelo y mensajes
// =====================================================================
// `pub(crate)` (aquí y en `Model`/`GaleriaDemo`): el example
// `pantallazo_pineal` incluye este archivo por `#[path]` para montar la
// MISMA view de la galería sin ventana. Para el binario no cambia nada.
#[derive(Clone)]
enum Msg {
pub(crate) enum Msg {
/// Barra de menú principal: abrir/cerrar un menú raíz (`None` cierra).
MenuOpen(Option<usize>),
/// Comando elegido en la barra → se traduce al `Msg` real.
@@ -53,7 +56,7 @@ enum Msg {
CycleTheme,
}
struct Model {
pub(crate) struct Model {
theme: Theme,
menu_open: Option<usize>,
/// Grafo del tile mesh, ya relajado en el init (posiciones fijas).
@@ -64,7 +67,7 @@ struct Model {
field: Arc<HeatmapMatrix>,
}
struct GaleriaDemo;
pub(crate) struct GaleriaDemo;
impl App for GaleriaDemo {
type Model = Model;
@@ -0,0 +1,43 @@
# pineal-gpu-demo
*Read this in English: [README.md](README.md).*
Starfield warp 3D sobre el camino GPU directo.
Es el **primer consumidor real de `GpuSceneCanvas`** (Fase 4 del SDD
de pineal). El resto del catálogo sólo tenía el backend GPU
documentado y testeado; aquí se pinta de verdad: el demo emite hasta
1 M de `fill_rect` por frame contra el trait `Canvas`, pero detrás
del trait está `GpuSceneCanvas` empujando a un `GpuBatch` que despacha
todo en **una sola draw call instanciada** (P3 del SDD).
Reparto de responsabilidades (Elm + el split CPU/GPU del SDD):
- `update(Tick)` avanza la simulación en CPU: cada estrella vuela
hacia el observador (`z -= speed`) y se recicla al fondo al cruzar
el plano cercano. Estado en un `Vec<f32>` plano interleaved
`[x,y,z, ...]`, mutado in-place (P1 + P2: zero boxing, zero alloc
en hot path).
- `gpu_paint_with(...)` proyecta perspectiva y dibuja: lee el campo
de estrellas, calcula el pixel de cada una contra el rect del nodo
y llama `canvas.fill_rect(...)`. Ni el demo ni el painter saben de
`wgpu` — sólo hablan el trait `Canvas`.
El campo se comparte entre `update` (escribe) y el painter (lee) por
`Arc<Mutex<StarField>>`. Ambos corren en el hilo de UI, así que el
mutex nunca se disputa; el `Arc` existe sólo porque la closure GPU
debe ser `'static` y no puede tomar prestado el `Model`.
Las `GpuPipelines` (shaders + render pipelines) se compilan una vez y
se cachean en un `OnceLock` que vive en el `Model`, no en `view()`
recompilarlas por frame mataría el framerate.
## Uso
```sh
cargo run --release -p pineal-gpu-demo
```
---
Parte de **pineal** — ver [pineal](../../LEEME.md).
@@ -0,0 +1,12 @@
# pineal-gpu-demo
A 3D starfield warp over the direct GPU path.
It is the **first real consumer of `GpuSceneCanvas`** (Phase 4 of pineal's SDD).
The rest of the catalog only had the GPU backend documented and tested; here it
actually paints: the demo emits up to 1M `fill_rect` calls per frame against the
`Canvas` trait, but behind it the GPU path takes over.
---
Part of **pineal** — see [pineal](../../README.md).
@@ -0,0 +1,24 @@
# pineal-heatmap-demo
*Read this in English: [README.md](README.md).*
Campo 2D con onda viajera.
Matriz 48×32 que se reescribe cada 33 ms (≈ 30 Hz). El valor de
cada celda combina dos sinusoides con desplazamiento de fase
ligado al tick: `sin(x·0.25 - t·0.1) + cos(y·0.30 + t·0.07)`.
El ramp Viridis mapea `[min, max]` de la matriz a color.
Cableado de UI: barra de menú principal (Archivo / Ver / Ayuda) +
menú contextual sobre el plot (right-click). Como es un canvas sin
texto editable no hay menú Editar ni clipboard.
## Uso
```sh
cargo run --release -p pineal-heatmap-demo
```
---
Parte de **pineal** — ver [pineal](../../LEEME.md).
@@ -0,0 +1,12 @@
# pineal-heatmap-demo
A 2D field with a travelling wave.
A 48×32 matrix rewritten every 33 ms (≈ 30 Hz). Each cell's value combines two
sinusoids with a phase shift tied to the tick:
`sin(x·0.25 - t·0.1) + cos(y·0.30 + t·0.07)`. The Viridis ramp maps the matrix's
`[min, max]` to colour.
---
Part of **pineal** — see [pineal](../../README.md).
@@ -0,0 +1,24 @@
# pineal-hexbin-demo
*Read this in English: [README.md](README.md).*
5 000 puntos sintéticos bineados.
Generador determinista (LCG sobre `t`) que produce dos clusters
gaussianos solapados. El hexbin revela la densidad — cada celda se
colorea con Viridis según count. Sin animación: el chart se computa
una vez al iniciar.
Cableado de UI: barra de menú principal (Archivo / Ver / Ayuda) +
menú contextual sobre el plot (right-click). Como es un canvas sin
texto editable no hay menú Editar ni clipboard.
## Uso
```sh
cargo run --release -p pineal-hexbin-demo
```
---
Parte de **pineal** — ver [pineal](../../LEEME.md).
@@ -0,0 +1,11 @@
# pineal-hexbin-demo
5,000 synthetic points, binned.
A deterministic generator (an LCG over `t`) producing two overlapping Gaussian
clusters. The hexbin reveals the density — each cell is coloured with Viridis by
count. No animation: the chart is computed once at startup.
---
Part of **pineal** — see [pineal](../../README.md).
@@ -0,0 +1,26 @@
# pineal-mesh-demo
*Read this in English: [README.md](README.md).*
Grafo de 24 nodos relajándose en vivo.
Topología: ciclo de 12 nodos + cordales aleatorios + 12 nodos
satélites enganchados al exterior. La simulación Fruchterman-Reingold
corre un paso por tick (≈ 60 Hz) hasta enfriarse; el panel muestra
aristas (gris) y nodos (círculos de discos rellenos) sobre fondo
oscuro. Cuando la temperatura cae bajo el umbral, el sistema queda
estacionario hasta que un reset lo recalienta.
Cableado de UI: barra de menú principal (Archivo / Ver / Ayuda) +
menú contextual sobre el plot (right-click). Como es un canvas sin
texto editable no hay menú Editar ni clipboard.
## Uso
```sh
cargo run --release -p pineal-mesh-demo
```
---
Parte de **pineal** — ver [pineal](../../LEEME.md).
@@ -0,0 +1,11 @@
# pineal-mesh-demo
A 24-node graph relaxing live.
Topology: a 12-node cycle + random chords + 12 satellite nodes hooked to the
outside. The Fruchterman-Reingold simulation runs one step per tick (≈ 60 Hz)
until it cools; the panel shows edges (grey) and nodes (filled discs).
---
Part of **pineal** — see [pineal](../../README.md).
@@ -1,8 +1,8 @@
# pineal-phosphor-demo
> Demo del backend fósforo de [pineal](../README.md).
> Demo del backend fósforo de [pineal](../../README.md).
Genera waveforms (seno, lissajous, ruido) y los pasa por [`pineal-phosphor`](../pineal-phosphor/README.md) con distintos `decay`. Slider para ajustar decay en vivo. Útil para entender qué hace el parámetro antes de usarlo en una app real.
Genera waveforms (seno, lissajous, ruido) y los pasa por [`pineal-phosphor`](../../pineal-phosphor/README.md) con distintos `decay`. Slider para ajustar decay en vivo. Útil para entender qué hace el parámetro antes de usarlo en una app real.
## Uso
@@ -12,4 +12,4 @@ cargo run --release -p pineal-phosphor-demo
## Deps
- [`pineal-phosphor`](../pineal-phosphor/README.md), [`pineal-render`](../pineal-render/README.md)
- [`pineal-phosphor`](../../pineal-phosphor/README.md), [`pineal-render`](../../pineal-render/README.md)
@@ -1,8 +1,8 @@
# pineal-phosphor-demo
> Phosphor backend demo for [pineal](../README.md).
> Phosphor backend demo for [pineal](../../README.md).
Generates waveforms (sine, lissajous, noise) and pipes them through [`pineal-phosphor`](../pineal-phosphor/README.md) with various `decay` values. Slider to tune decay live. Useful to understand the parameter before using it in a real app.
Generates waveforms (sine, lissajous, noise) and pipes them through [`pineal-phosphor`](../../pineal-phosphor/README.md) with various `decay` values. Slider to tune decay live. Useful to understand the parameter before using it in a real app.
## Usage
@@ -12,4 +12,4 @@ cargo run --release -p pineal-phosphor-demo
## Deps
- [`pineal-phosphor`](../pineal-phosphor/README.md), [`pineal-render`](../pineal-render/README.md)
- [`pineal-phosphor`](../../pineal-phosphor/README.md), [`pineal-render`](../../pineal-render/README.md)
@@ -0,0 +1,26 @@
# pineal-polar-demo
*Read this in English: [README.md](README.md).*
Pie/donut + radar sobre Llimphi.
Dos paneles uno al lado del otro:
- **Pie chart (donut)** — 6 porciones de un presupuesto sintético.
- **Radar (spider)** — perfil de 6 atributos contra un máximo común.
Ambos se ajustan al rect del panel: el centro y el radio se calculan
en el closure de `paint_with` a partir del `PaintRect` recibido.
Lleva barra de menú principal (Archivo/Ver/Ayuda) + un menú
contextual sobre los plots. Son canvas estáticos sin edición ni
clipboard — el contextual sólo ofrece cambiar tema.
## Uso
```sh
cargo run --release -p pineal-polar-demo
```
---
Parte de **pineal** — ver [pineal](../../LEEME.md).
@@ -0,0 +1,12 @@
# pineal-polar-demo
Pie/donut + radar over Llimphi.
Two panels side by side:
- **Pie chart (donut)** — six slices of a synthetic budget.
- **Radar (spider)** — a profile of six attributes against a common maximum.
---
Part of **pineal** — see [pineal](../../README.md).
@@ -1,8 +1,8 @@
# pineal-stream-demo
> Demo del backend streaming de [pineal](../README.md).
> Demo del backend streaming de [pineal](../../README.md).
Genera muestras a 60 Hz (varias series simultáneas) y las pushea a [`pineal-stream`](../pineal-stream/README.md). Demuestra el scroll suave y la composición de múltiples series sobre el mismo eje de tiempo.
Genera muestras a 60 Hz (varias series simultáneas) y las pushea a [`pineal-stream`](../../pineal-stream/README.md). Demuestra el scroll suave y la composición de múltiples series sobre el mismo eje de tiempo.
## Uso
@@ -12,4 +12,4 @@ cargo run --release -p pineal-stream-demo
## Deps
- [`pineal-stream`](../pineal-stream/README.md), [`pineal-render`](../pineal-render/README.md)
- [`pineal-stream`](../../pineal-stream/README.md), [`pineal-render`](../../pineal-render/README.md)
@@ -1,8 +1,8 @@
# pineal-stream-demo
> Streaming backend demo for [pineal](../README.md).
> Streaming backend demo for [pineal](../../README.md).
Generates samples at 60 Hz (several simultaneous series) and pushes them to [`pineal-stream`](../pineal-stream/README.md). Demonstrates smooth scrolling and composition of multiple series on the same time axis.
Generates samples at 60 Hz (several simultaneous series) and pushes them to [`pineal-stream`](../../pineal-stream/README.md). Demonstrates smooth scrolling and composition of multiple series on the same time axis.
## Usage
@@ -12,4 +12,4 @@ cargo run --release -p pineal-stream-demo
## Deps
- [`pineal-stream`](../pineal-stream/README.md), [`pineal-render`](../pineal-render/README.md)
- [`pineal-stream`](../../pineal-stream/README.md), [`pineal-render`](../../pineal-render/README.md)
@@ -0,0 +1,25 @@
# pineal-treemap-demo
*Read this in English: [README.md](README.md).*
Treemap squarified con 12 tiles.
Pesos escogidos a mano para mostrar el algoritmo cuando hay
mezcla de tiles grandes y chicas. El squarified minimiza el
peor aspect ratio en cada fila/columna; las tiles chicas
quedan amontonadas en una banda angosta.
Lleva barra de menú principal (Archivo/Ver/Ayuda) + un menú
contextual sobre el plot. No hay edición ni clipboard — el treemap
es un canvas estático, así que el contextual sólo ofrece cambiar
tema.
## Uso
```sh
cargo run --release -p pineal-treemap-demo
```
---
Parte de **pineal** — ver [pineal](../../LEEME.md).
@@ -0,0 +1,11 @@
# pineal-treemap-demo
A squarified treemap with 12 tiles.
Weights chosen by hand to show the algorithm when there is a mix of large and
small tiles. Squarifying minimizes the worst aspect ratio in each row or column;
the small tiles end up crowded into a narrow band.
---
Part of **pineal** — see [pineal](../../README.md).
Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

+4 -4
View File
@@ -4,9 +4,9 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — barras: columnas/barras simples, agrupadas y apiladas (vertical u horizontal) + histograma. Painter agnóstico sobre el trait Canvas."
repository = { workspace = true }
publish = true
description = "Pineal — barras: columnas/barras simples, agrupadas y apiladas (vertical u horizontal) + histograma. Painter agnóstico sobre el trait Canvas."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
+26
View File
@@ -0,0 +1,26 @@
# pineal-bars
*Read this in English: [README.md](README.md).*
El painter de barras del catálogo.
El gráfico más común que faltaba: columnas/barras. Como todo en
pineal, es agnóstico del backend — sólo emite `fill_rect` contra el
trait `pineal_render::Canvas`, así que sirve igual sobre
vello/llimphi, PNG, SVG, PDF o el camino GPU directo.
- `paint_bars` — una serie: barras desde un baseline, vertical u
horizontal, con soporte para valores negativos.
- `paint_grouped` — varias series por categoría, agrupadas
(clustered) lado a lado.
- `paint_stacked` — segmentos apilados sobre un baseline común.
- `Histogram` — bineado de una muestra `&[f32]` en conteos, listo
para pasar a `paint_bars` como histograma.
El eje, los ticks y las etiquetas no son responsabilidad del painter
(regla del SDD: el texto va por una pasada vello hermana). Aquí sólo
viven los rectángulos.
---
Parte de **pineal** — ver [pineal](../LEEME.md).
+12
View File
@@ -0,0 +1,12 @@
# pineal-bars
The catalog's bar painter.
The most common chart that was missing: columns and bars. Like everything in
pineal it is backend-agnostic — it only emits `fill_rect` against the
`pineal_render::Canvas` trait, so it works equally over vello/llimphi, PNG, SVG,
PDF or the direct GPU path.
---
Part of **pineal** — see [pineal](../README.md).
+1 -1
View File
@@ -14,7 +14,7 @@
//! para pasar a `paint_bars` como histograma.
//!
//! El eje, los ticks y las etiquetas no son responsabilidad del painter
//! (regla del SDD: el texto va por una pasada vello hermana). A sólo
//! (regla del SDD: el texto va por una pasada vello hermana). Aquí sólo
//! viven los rectángulos.
#![forbid(unsafe_code)]
+1 -1
View File
@@ -33,7 +33,7 @@ pub struct BarStyle {
/// Fracción del slot de cada categoría que va a separación, en
/// `[0, 1)`. `0.0` = barras pegadas; `0.2` = 20 % de aire.
pub gap_ratio: f32,
/// Valor del cero. Las barras nacen a; valores por debajo crecen
/// Valor del cero. Las barras nacen aquí; valores por debajo crecen
/// en sentido contrario. Normalmente `0.0`.
pub baseline: f64,
/// Override del rango de valor. `None` = automático a partir de los
+13 -5
View File
@@ -4,10 +4,18 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — gráficos cartesianos: LineSeries / BarSeries / AreaSeries, viewport con pan/zoom, picture cache, ejes con decimación, tooltips."
repository = { workspace = true }
publish = true
description = "Pineal — gráficos cartesianos: LineSeries / BarSeries / AreaSeries, viewport con pan/zoom, picture cache, ejes con decimación, tooltips."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
llimphi-ui = { workspace = true }
pineal-core = { path = "../pineal-core", version = "0.1.0" }
pineal-render = { path = "../pineal-render", version = "0.1.0", default-features = false }
llimphi-ui = { workspace = true, optional = true }
# `llimphi` (default) habilita `ChartView` (el `View<Msg>` declarativo).
# Sin él quedan los painters puros (axis/series/coord_system/viewport),
# consumibles headless contra cualquier `Canvas` (p. ej. `PlanRecorder`).
[features]
default = ["llimphi"]
llimphi = ["dep:llimphi-ui", "pineal-render/llimphi"]
@@ -67,7 +67,7 @@ pub fn ticks_nice(min: f64, max: f64, target_ticks: usize) -> Vec<f64> {
///
/// `min_spacing_px` es la distancia mínima entre el borde
/// derecho de un label aprobado y el borde izquierdo del
/// siguiente. Si no tenés el ancho del label, pasá un valor
/// siguiente. Si no tienes el ancho del label, pasa un valor
/// conservador (≈ 48 px del Flutter doc).
pub fn decimate_labels(
positions_px: &[f32],
@@ -21,12 +21,14 @@ pub mod viewport;
pub mod coord_system;
pub mod series;
pub mod axis;
#[cfg(feature = "llimphi")]
pub mod view;
pub use viewport::ChartViewport;
pub use coord_system::CoordinateSystem;
pub use series::{LineSeries, PaintCtx, RenderMode, Series};
#[cfg(feature = "llimphi")]
pub use view::{
chart_cache, lapaloma_chart_view, ChartCache, ChartCacheHandle, ChartSeriesItem, ChartView,
};
@@ -2,7 +2,7 @@
//!
//! El viewport NO conoce pixeles. Sólo describe qué rango de
//! valores X/Y es visible. La proyección a píxeles la hace
//! [`crate::coord_system::CoordinateSystem`] cuando le pasás
//! [`crate::coord_system::CoordinateSystem`] cuando le pasas
//! el `plot_rect`.
//!
//! Pan y zoom mutan el viewport, no los datos. Esto preserva el
+5 -4
View File
@@ -4,9 +4,10 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — contour: marching squares sobre matriz escalar → polilíneas por isolínea."
repository = { workspace = true }
publish = true
description = "Pineal — contour: marching squares sobre matriz escalar → polilíneas por isolínea."
[dependencies]
pineal-render = { path = "../pineal-render" }
pineal-heatmap = { path = "../pineal-heatmap" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
pineal-heatmap = { path = "../pineal-heatmap", version = "0.1.0" }
@@ -0,0 +1,24 @@
# pineal-contour
*Read this in English: [README.md](README.md).*
Isolíneas vía marching squares.
Dada una `HeatmapMatrix` y un nivel `iso`, extrae los segmentos
donde la función vale exactamente `iso` aproximada por interpolación
lineal en los bordes de cada celda. Compone con `pineal-cartesian`
para mapas topográficos, líneas de presión, equipotenciales, etc.
- `extract_contour` — un nivel → `Vec` de segmentos `[x0,y0,x1,y1]`.
- `extract_contours` — N niveles equiespaciados.
- `paint_contours` — pinta cada nivel como polilínea contra `Canvas`.
Implementación: marching squares clásico — para cada celda 2×2 se
computa un índice 4-bit a partir del signo de cada esquina respecto
a `iso`; un lookup table de 16 casos da los 0/1/2 segmentos. Sin
ambigüedad resolution (los casos 5 y 10 saddle se rompen siempre
del mismo lado — suficiente para charts).
---
Parte de **pineal** — ver [pineal](../LEEME.md).
@@ -0,0 +1,12 @@
# pineal-contour
Isolines through marching squares.
Given a `HeatmapMatrix` and an `iso` level, it extracts the segments where the
function is exactly `iso`, approximated by linear interpolation on each cell's
edges. It composes with `pineal-cartesian` for topographic maps, pressure lines,
equipotentials and so on.
---
Part of **pineal** — see [pineal](../README.md).
+3 -2
View File
@@ -4,8 +4,9 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — primitivas agnósticas: DataBuffer interleaved, RingBuffer streaming, SpatialIndex, LTTB, escalas. Cero deps de UI, cero alloc en hot path."
repository = { workspace = true }
publish = true
description = "Pineal — primitivas agnósticas: DataBuffer interleaved, RingBuffer streaming, SpatialIndex, LTTB, escalas. Cero deps de UI, cero alloc en hot path."
[dependencies]
+1 -1
View File
@@ -23,7 +23,7 @@ impl DataBuffer {
Self::default()
}
/// Reserva espacio para `n` puntos sin agregarlos. Usalo al
/// Reserva espacio para `n` puntos sin agregarlos. Úsalo al
/// montar el widget para que `push` no realloque después.
pub fn with_capacity(n: usize) -> Self {
Self {
+3 -3
View File
@@ -1,6 +1,6 @@
//! `pineal-core` — primitivas agnósticas de Lapaloma.
//!
//! Cero `gpui`, cero `wgpu`, cero I/O. Todo lo que vive a puede
//! Cero `gpui`, cero `wgpu`, cero I/O. Todo lo que vive aquí puede
//! correr en un test unitario, en un worker thread o en un export
//! a SVG. Las tres reglas del documento de arquitectura aplican:
//!
@@ -10,7 +10,7 @@
//! - **P2 Zero alloc en hot path.** Buffers se reservan al construir,
//! se mutan in-place para siempre. Helpers escriben a `&mut Vec`
//! provistos por el caller, no devuelven `Vec` nuevos.
//! - **P3 Una draw call por capa.** A no se dibuja; pero los
//! - **P3 Una draw call por capa.** Aquí no se dibuja; pero los
//! tipos exponen slices contiguos listos para mandar al GPU
//! sin copia.
//!
@@ -28,7 +28,7 @@ pub mod lttb;
pub mod scale;
// Algoritmos de layout: cada uno vive en el crate de la viz que lo
// usa, no a. Esto es deliberado — `pineal-core` no debe arrastrar
// usa, no aquí. Esto es deliberado — `pineal-core` no debe arrastrar
// dependencias de visualización.
//
// - Barnes-Hut + Sugiyama + tree layout: `pineal-mesh`.
@@ -4,7 +4,7 @@
//! temporales) un binary search basta y es O(log n) sin estructuras
//! auxiliares. Para nodos que se mueven cada frame (mesh graph)
//! corresponde un spatial hash uniforme — ese va en `pineal-mesh`,
//! no a.
//! no aquí.
/// View sobre un buffer interleaved `[x0,y0,x1,y1,…]` sorted-asc por X.
///
+5 -4
View File
@@ -4,10 +4,11 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — exporters. SVG primero, PDF después. Decimación contextual por DPI: target = width_inches × dpi × vertices_per_pixel."
repository = { workspace = true }
publish = true
description = "Pineal — exporters. SVG primero, PDF después. Decimación contextual por DPI: target = width_inches × dpi × vertices_per_pixel."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
pineal-core = { path = "../pineal-core", version = "0.1.0" }
pineal-render = { path = "../pineal-render", version = "0.1.0", default-features = false }
png = { workspace = true }
+1 -1
View File
@@ -2,7 +2,7 @@
//! codifica como PNG.
//!
//! La estrategia es la misma que `svg.rs`: el painter grabó cada comando
//! en un plan; a lo replayeamos contra un rasterizador software
//! en un plan; aquí lo replayeamos contra un rasterizador software
//! propio. No depende de `tiny-skia` ni `cairo` — sólo `png` (ya en el
//! workspace) y aritmética f32. Esto mantiene la cadena
//! `core → render → export` libre de stack gráfico nativo.
+1 -1
View File
@@ -1,7 +1,7 @@
//! Exporter SVG: un [`RenderPlan`] → documento SVG completo.
//!
//! El mismo painter que dibuja en pantalla (vía el trait `Canvas`) se
//! graba con un `PlanRecorder` y el plan resultante se vuelca a. Un
//! graba con un `PlanRecorder` y el plan resultante se vuelca aquí. Un
//! solo camino de código para screen y export.
//!
//! v1: los comandos de clip (`PushClip`/`PopClip`) se ignoran — el
@@ -4,11 +4,11 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — gráficos financieros. OHLC / candlesticks con agregación que preserva volatilidad (no LTTB, time-bucketing con max/min de wicks)."
repository = { workspace = true }
publish = true
description = "Pineal — gráficos financieros. OHLC / candlesticks con agregación que preserva volatilidad (no LTTB, time-bucketing con max/min de wicks)."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
pineal-cartesian = { path = "../pineal-cartesian" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
pineal-cartesian = { path = "../pineal-cartesian", version = "0.1.0" }
llimphi-ui = { workspace = true }
@@ -10,7 +10,7 @@
//! - `volume` = sum(`volume`) del bucket.
//! - `t` = timestamp del primer bar del bucket (canónico para
//! ploteo; el doc original sugiere usar el inicio del bucket
//! pero a preferimos el sample real para no introducir bias).
//! pero aquí preferimos el sample real para no introducir bias).
//!
//! Buckets vacíos no se emiten — el length de salida es ≤ inputs.
//! Fallback a index-bucketing si el span temporal es cero (todos
+4 -4
View File
@@ -4,9 +4,9 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — diagramas de flujo Sankey: columnas topológicas + barycenter ordering + ribbons como triangle strips de béziers."
repository = { workspace = true }
publish = true
description = "Pineal — diagramas de flujo Sankey: columnas topológicas + barycenter ordering + ribbons como triangle strips de béziers."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
+4 -4
View File
@@ -4,9 +4,9 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — heatmap. Matriz [width × height] de f32 → imagen pre-encodeada que se rendea con un sólo drawImageRect."
repository = { workspace = true }
publish = true
description = "Pineal — heatmap. Matriz [width × height] de f32 → imagen pre-encodeada que se rendea con un sólo drawImageRect."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
+5 -4
View File
@@ -4,9 +4,10 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — hexbin: bineado hexagonal pointy-top de un dataset 2D + painter."
repository = { workspace = true }
publish = true
description = "Pineal — hexbin: bineado hexagonal pointy-top de un dataset 2D + painter."
[dependencies]
pineal-render = { path = "../pineal-render" }
pineal-heatmap = { path = "../pineal-heatmap" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
pineal-heatmap = { path = "../pineal-heatmap", version = "0.1.0" }
+21
View File
@@ -0,0 +1,21 @@
# pineal-hexbin
*Read this in English: [README.md](README.md).*
Bineado hexagonal de scatter densos.
Cuando un scatter tiene tantos puntos que la nube tapa el patrón,
binear sobre una rejilla hexagonal pointy-top y colorear por
densidad es la forma estándar de revelar la distribución. La
topología hexagonal evita los "estrías" rectangulares de un binning
cuadrado y respeta mejor la isotropía de la nube.
- `HexGrid` — calcula el bin de cada `(x, y)` con la rejilla
pointy-top (hex con dos vértices arriba/abajo). Cell size = radio
del circumcírculo.
- `paint_hexbin` — pinta los bins no vacíos como hexágonos
rellenos coloreados por densidad vía `pineal_heatmap::Ramp`.
---
Parte de **pineal** — ver [pineal](../LEEME.md).
@@ -0,0 +1,12 @@
# pineal-hexbin
Hexagonal binning of dense scatters.
When a scatter has so many points that the cloud hides the pattern, binning over a
pointy-top hexagonal grid and colouring by density is the standard way to reveal
the distribution. The hexagonal topology avoids the rectangular "striping" of a
square binning.
---
Part of **pineal** — see [pineal](../README.md).
+4 -4
View File
@@ -4,9 +4,9 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — grafos. NodeBuffer / EdgeBuffer + layouts (force-directed con Barnes-Hut, Sugiyama-lite jerárquico, subtree-width)."
repository = { workspace = true }
publish = true
description = "Pineal — grafos. NodeBuffer / EdgeBuffer + layouts (force-directed con Barnes-Hut, Sugiyama-lite jerárquico, subtree-width)."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
+1 -1
View File
@@ -2,7 +2,7 @@
> Canvas de triángulos arbitrarios para [pineal](../README.md).
Backend para geometría libre: tomá una malla `(vertices, indices, colors)` y se dibuja con shader-like flexibility. Útil para terrenos, geometría 2D compleja, ilustraciones generativas.
Backend para geometría libre: toma una malla `(vertices, indices, colors)` y se dibuja con shader-like flexibility. Útil para terrenos, geometría 2D compleja, ilustraciones generativas.
## API
@@ -6,7 +6,7 @@
//! Theta típico: 0.5 — más bajo = más preciso, más lento.
//!
//! Representación: `Vec<QuadNode>` indexado. El doc original de
//! `pineal-core::barnes_hut` propone stride 7 plano; a optamos por la
//! `pineal-core::barnes_hut` propone stride 7 plano; aquí optamos por la
//! versión idiomática — clara y suficiente para los rangos en que el
//! force-directed corre interactivo (hasta ~50 K nodos).
@@ -22,7 +22,7 @@ struct QuadNode {
/// Center of mass acumulado.
cm_x: f32,
cm_y: f32,
/// Masa total (suma de pesos — a usamos 1.0 por nodo).
/// Masa total (suma de pesos — aquí usamos 1.0 por nodo).
mass: f32,
/// Hoja con exactamente 1 cuerpo: índice del cuerpo en `positions`.
body: Option<usize>,
@@ -4,10 +4,11 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — decoración CRT sobre lapaloma-stream: trail con alpha decay por edad, ghost, anotaciones magnéticas ancladas a sample index."
repository = { workspace = true }
publish = true
description = "Pineal — decoración CRT sobre lapaloma-stream: trail con alpha decay por edad, ghost, anotaciones magnéticas ancladas a sample index."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
pineal-core = { path = "../pineal-core", version = "0.1.0" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
llimphi-ui = { workspace = true }
+4 -4
View File
@@ -4,9 +4,9 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — gráficos polares: pie chart, donut, radar."
repository = { workspace = true }
publish = true
description = "Pineal — gráficos polares: pie chart, donut, radar."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
+11 -4
View File
@@ -4,9 +4,16 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — abstracción de painter: trait Canvas + RenderPlan + color helpers. Backend Llimphi (vello/wgpu) consumido por demos y consumidores upstream sin tocar a los painters."
repository = { workspace = true }
publish = true
description = "Pineal — abstracción de painter: trait Canvas + RenderPlan + color helpers. Backend Llimphi (vello/wgpu) consumido por demos y consumidores upstream sin tocar a los painters."
[dependencies]
pineal-core = { path = "../pineal-core" }
llimphi-ui = { workspace = true }
llimphi-ui = { workspace = true, optional = true }
# `llimphi` (default) habilita los backends SceneCanvas/GpuSceneCanvas.
# Sin él queda el núcleo agnóstico (Canvas + RenderPlan + PlanRecorder),
# consumible headless sin arrastrar el stack gráfico.
[features]
default = ["llimphi"]
llimphi = ["dep:llimphi-ui"]
+7 -3
View File
@@ -1,13 +1,13 @@
//! `pineal-render` — abstracción de painter.
//!
//! Los crates de visualización (cartesian, mesh, polar…) no conocen
//! el runtime de UI: hablan contra el trait [`Canvas`] definido a.
//! el runtime de UI: hablan contra el trait [`Canvas`] definido aquí.
//! Eso deja a la cadena `core → render → painter` agnóstica.
//!
//! Backends activos:
//!
//! - **Llimphi/vello** ([`llimphi_backend::SceneCanvas`]) — canónico
//! para apps gioser; pinta dentro de un `paint_with` del `View<Msg>`
//! para apps tawasuyu; pinta dentro de un `paint_with` del `View<Msg>`
//! declarativo.
//! - **PlanRecorder** ([`recorder::PlanRecorder`]) — graba cada llamada
//! como `RenderCmd`. Consumido por `pineal-export` para emitir SVG,
@@ -18,7 +18,7 @@
//! en lugar de `paint_with`. Los painters no cambian. Sin texto y sin
//! AA fino — ver doc del módulo para trade-offs.
//!
//! Tipos primitivos (`Color`, `Point`, `Rect`) viven a para no
//! Tipos primitivos (`Color`, `Point`, `Rect`) viven aquí para no
//! atarlos al tipo de color/punto de ningún runtime.
#![forbid(unsafe_code)]
@@ -29,7 +29,9 @@ pub mod canvas;
pub mod plan;
pub mod recorder;
#[cfg(feature = "llimphi")]
pub mod llimphi_backend;
#[cfg(feature = "llimphi")]
pub mod gpu_canvas;
pub use color::Color;
@@ -38,5 +40,7 @@ pub use canvas::{Canvas, StrokeStyle};
pub use plan::{RenderCmd, RenderPlan};
pub use recorder::PlanRecorder;
#[cfg(feature = "llimphi")]
pub use llimphi_backend::SceneCanvas;
#[cfg(feature = "llimphi")]
pub use gpu_canvas::GpuSceneCanvas;
+5 -4
View File
@@ -4,10 +4,11 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — widget de telemetría tipo osciloscopio. Ring buffer + envelope min/max por columna + render en dos segmentos (split at head)."
repository = { workspace = true }
publish = true
description = "Pineal — widget de telemetría tipo osciloscopio. Ring buffer + envelope min/max por columna + render en dos segmentos (split at head)."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
pineal-core = { path = "../pineal-core", version = "0.1.0" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
llimphi-ui = { workspace = true }
+4 -4
View File
@@ -4,9 +4,9 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — treemap con algoritmo squarified (Bruls / d3-hierarchy formulation)."
repository = { workspace = true }
publish = true
description = "Pineal — treemap con algoritmo squarified (Bruls / d3-hierarchy formulation)."
[dependencies]
pineal-core = { path = "../pineal-core" }
pineal-render = { path = "../pineal-render" }
pineal-render = { path = "../pineal-render", version = "0.1.0" }
+20 -17
View File
@@ -4,25 +4,28 @@ version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = { workspace = true }
publish = { workspace = true }
description = "Lapaloma — paraguas: re-exporta los módulos para prototipos. En producción importar los crates hoja directamente para que tree-shaking descarte lo no usado."
repository = { workspace = true }
publish = true
description = "Pineal — paraguas: re-exporta los módulos para prototipos. En producción importar los crates hoja directamente para que tree-shaking descarte lo no usado."
keywords = ["visualization", "charts", "plotting", "graph", "svg"]
categories = ["visualization", "graphics", "rendering"]
[dependencies]
pineal-core = { path = "../pineal-core", optional = true }
pineal-render = { path = "../pineal-render", optional = true }
pineal-cartesian = { path = "../pineal-cartesian", optional = true }
pineal-stream = { path = "../pineal-stream", optional = true }
pineal-mesh = { path = "../pineal-mesh", optional = true }
pineal-financial = { path = "../pineal-financial", optional = true }
pineal-polar = { path = "../pineal-polar", optional = true }
pineal-heatmap = { path = "../pineal-heatmap", optional = true }
pineal-treemap = { path = "../pineal-treemap", optional = true }
pineal-flow = { path = "../pineal-flow", optional = true }
pineal-phosphor = { path = "../pineal-phosphor", optional = true }
pineal-export = { path = "../pineal-export", optional = true }
pineal-hexbin = { path = "../pineal-hexbin", optional = true }
pineal-contour = { path = "../pineal-contour", optional = true }
pineal-bars = { path = "../pineal-bars", optional = true }
pineal-core = { path = "../pineal-core", version = "0.1.0", optional = true }
pineal-render = { path = "../pineal-render", version = "0.1.0", optional = true }
pineal-cartesian = { path = "../pineal-cartesian", version = "0.1.0", optional = true }
pineal-stream = { path = "../pineal-stream", version = "0.1.0", optional = true }
pineal-mesh = { path = "../pineal-mesh", version = "0.1.0", optional = true }
pineal-financial = { path = "../pineal-financial", version = "0.1.0", optional = true }
pineal-polar = { path = "../pineal-polar", version = "0.1.0", optional = true }
pineal-heatmap = { path = "../pineal-heatmap", version = "0.1.0", optional = true }
pineal-treemap = { path = "../pineal-treemap", version = "0.1.0", optional = true }
pineal-flow = { path = "../pineal-flow", version = "0.1.0", optional = true }
pineal-phosphor = { path = "../pineal-phosphor", version = "0.1.0", optional = true }
pineal-export = { path = "../pineal-export", version = "0.1.0", optional = true }
pineal-hexbin = { path = "../pineal-hexbin", version = "0.1.0", optional = true }
pineal-contour = { path = "../pineal-contour", version = "0.1.0", optional = true }
pineal-bars = { path = "../pineal-bars", version = "0.1.0", optional = true }
[features]
default = ["full"]
Generated
+1615 -351
View File
File diff suppressed because it is too large Load Diff
+301 -107
View File
@@ -1,7 +1,13 @@
# Cargo.toml raíz STANDALONE de pineal — front-door sobre Llimphi.
# Solo el código de pineal; Llimphi y lo fundacional por git-dep del monorepo gioser.git.
# Cargo.toml raíz STANDALONE de pineal — front-door público.
# GENERADO por scripts/actualizar-standalone.py desde el monorepo tawasuyu.git
# (v0.2.0). No editar a mano: se regenera. Lo fundacional se consume por
# git-dep al tag; sólo el código del dominio vive en este repo.
[workspace]
resolver = "2"
# ============================================================
# Cuadrantes — agregar a medida que se migran
# ============================================================
members = [
"00_unanchay/pineal/demos/pineal-bars-demo",
"00_unanchay/pineal/demos/pineal-contour-demo",
@@ -35,6 +41,11 @@ members = [
"00_unanchay/pineal/pineal-umbrella",
]
# `wawa/` se excluye del workspace global porque corre en target
# `x86_64-unknown-none` y `panic = "abort"`, incompatibles con los
# perfiles globales. Los crates compartidos se referencian por `path`
# cruzando la frontera.
[workspace.package]
version = "0.1.0"
edition = "2021"
@@ -45,9 +56,32 @@ publish = false
repository = "https://git.tawasuyu.net/tawasuyu/pineal"
[workspace.dependencies]
# === Configuración declarativa (vocabulario de esquemas) ===
allichay = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
marca = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# Telemetría local de desarrollo (stderr+panics+tracing → $XDG_STATE_HOME/tawasuyu/<dominio>/<bin>.log)
bitacora = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# default = [] (núcleo liviano sin vello); `bake` es opt-in. Declaramos
# default-features = false aquí para que el `default-features = false` de los
# consumidores (splash/greeter/compositor) se honre y no quede el warning.
mirada-fondo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
mirada-teclado-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
mirada-teclado-widget = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# === Registro de apps / menú global ===
app-bus = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
app-bus = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
app-iconset = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# === Centro de eventos (notificaciones + capturas + clipboard + …) ===
willay-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
willay-checkpoint = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
willay-hilo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
willay-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
willay-emit = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
rag-motor = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
churay-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
churay-welcome = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
churay-welcome-runner = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# === Puente de drag-and-drop compositor → app (suple winit en Wayland) ===
drop-bridge = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# === Serialización ===
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -74,8 +108,12 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
# === Linux primitives (arje) ===
nix = { version = "0.29", features = ["signal", "process", "sched", "mount", "fs", "socket", "net", "user"] }
nix = { version = "0.29", features = ["signal", "process", "sched", "mount", "fs", "socket", "net", "user", "reboot"] }
libc = "0.2"
# DRM/KMS legacy (dumb buffer) para el splash nativo del arranque sin parpadeo.
# Misma versión que reexporta smithay (mirada-compositor) — el lock ya la fija.
drm = "0.14"
font8x8 = { version = "0.3", default-features = false }
# === IDs / Hash / Crypto ===
ulid = { version = "1", features = ["serde"] }
@@ -89,10 +127,15 @@ argon2 = "0.5"
rand = "0.8"
# === WASM (arje) ===
# wasmi 1.0: unifica la versión con renaser (su kernel ya corre 1.0), para
# wasmi 1.0: unifica la versión con wawa (su kernel ya corre 1.0), para
# que el ABI WASM del host sea idéntico en Linux y en bare-metal.
wasmi = "1.0"
wat = "1"
# wasmtime 46 (Fase 1 del pipeline JS): tier 0 de puriy-js pasa de wasmi
# (intérprete) a Wasmtime AOT (Cranelift → nativo) en Linux — quita la doble
# interpretación de QuickJS.wasm. En wawa bare-metal el runtime es Pulley (spike
# Q1 cerrado); mismo crate, distinto backend.
wasmtime = "46"
# === Storage / DB ===
sled = "0.34"
@@ -107,6 +150,10 @@ bzip2 = "0.4"
# === Compresión (minga multi-bundle) ===
zstd = "0.13"
xz2 = "0.1"
# Contenedores adicionales del visor Archive de nahual (sólo listado).
sevenz-rust = "0.6"
unrar = "0.5"
# === HTTP server (iniy-server) ===
axum = "0.7"
@@ -116,7 +163,7 @@ tower = "0.5"
instant-distance = "0.6"
# === P2P (minga) ===
libp2p = { version = "0.56", features = ["tokio", "tcp", "noise", "yamux", "macros", "kad", "identify", "relay", "dcutr", "autonat", "mdns"] }
libp2p = { version = "0.56", features = ["tokio", "tcp", "dns", "noise", "yamux", "macros", "kad", "identify", "relay", "dcutr", "autonat", "mdns"] }
libp2p-stream = "=0.4.0-alpha"
libp2p-allow-block-list = "0.6"
@@ -131,7 +178,11 @@ libm = "0.2"
midly = "0.5"
# === Code parsing (minga) ===
arboard = "3"
# `wayland-data-control` trae el backend wl-clipboard-rs: bajo mirada (Wayland
# propio, sin Xwayland para apps nativas) arboard-x11 fallaba a no-op silencioso
# y el clipboard de los text-editor quedaba muerto. Con la feature habla
# zwlr_data_control_manager_v1 (que mirada expone) y cae a X11 si no hay Wayland.
arboard = { version = "3", features = ["wayland-data-control"] }
ropey = "1.6"
tree-sitter = "0.24"
tree-sitter-rust = "0.23"
@@ -150,7 +201,19 @@ petgraph = "0.6"
# default-features = false: nos quedamos con PNG + JPEG + WebP (lossless).
# tullpu-render exporta a las tres; AVIF/TIFF/… los habilitamos si una app
# los pide específicamente.
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp"] }
# Decoders puro-Rust baratos; avif queda fuera (arrastra dav1d/rav1e).
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif", "bmp", "ico", "tiff", "tga", "qoi"] }
# ONNX Runtime para los proveedores de píxel reales (pixel-verbo-onnx: segmentación
# u2net/isnet, inpaint, restyle…). `download-binaries` (default) baja el runtime nativo
# solo; ya lo arrastraba fastembed (rimay-verbo) transitivo — aquí lo declaramos directo.
ort = "2.0.0-rc.9"
# Rasterizador vectorial CPU (relleno/trazo anti-aliased) para capas vectoriales de tullpu.
tiny-skia = "0.11"
# Booleanos de polígonos robustos (union/intersection/difference/xor) para el
# clipper vectorial de tullpu — restar/intersecar paths que se solapan
# parcialmente, que la regla de relleno no expresa. Ya estaba en el lock.
geo = "0.28"
# === FUSE (minga-vfs) ===
# default-features = false: prescinde de pkg-config/libfuse-dev en build.
@@ -172,91 +235,164 @@ tempfile = "3"
# === Llimphi (motor gráfico soberano) ===
# wgpu sobre Vulkan/Metal/DX12, winit para ventana en dev Linux.
# raw-window-handle 0.6 alinea winit 0.30 con wgpu 24.
# vello 0.5 = rasterizador vectorial sobre wgpu 24.
# raw-window-handle 0.6 alinea winit 0.30 con wgpu 27.
# vello 0.7 = rasterizador vectorial sobre wgpu 27 (renderer GPU "wgpu" + opt-in "hybrid" CPU+GPU).
# taffy 0.9 = motor Flexbox/Grid puro Rust (ya pulled por transitivos, lo alineamos).
# parley 0.2 = shaping/layout de texto compatible con peniko 0.4 (que vello 0.5 expone).
wgpu = "24"
# parley 0.6 = shaping/layout de texto compatible con peniko 0.6 (que vello 0.7 expone).
wgpu = "27"
winit = "0.30"
raw-window-handle = "0.6"
pollster = "0.4"
vello = "0.5"
vello = "0.7"
# Renderer hybrid CPU+GPU sin compute shaders (mejor compat WebGL2/Adreno viejas).
# Opt-in; el renderer "wgpu" sigue siendo el default.
vello_hybrid = "0.0.9"
# foreign-lottie = fork vendorizado de velato 0.9 (Lottie → vello::Scene),
# completado para no paniquear ante features no soportadas. Lo consume
# llimphi-lottie. Ver shared/foreign-lottie.
foreign-lottie = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# Árbol de accesibilidad para lectores de pantalla (NVDA/VoiceOver/Orca/TalkBack).
# Lo consume el runtime de Llimphi (iter 2/3 del plan AccessKit); el modelo
# `SemanticsSpec` del compositor es independiente de estas crates.
accesskit = "0.24"
accesskit_winit = "0.33"
taffy = "0.9"
# parley = shaping completo (bidi, ligatures, fallback CJK/emoji vía fontique, line break).
parley = "0.4"
parley = "0.6"
# Bucle Elm (input→update→view→layout→raster→present). Lo consumen las apps.
llimphi-ui = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-ui = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Tier 3 — apps WASM con UI Llimphi real. El guest pinta un WireNode (IR
# serializable), el host (runner) lo materializa en View<Msg> y rebota los
# eventos al update del guest. SDK = lado guest, runner = lado host.
llimphi-wire-view = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-wasm-app-sdk = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-wasm-runner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Núcleo puro de distribución (CAS + verificación + resolve), SIN runner/GPU.
llimphi-wasm-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Cara con baterías: re-exporta core + puente al runner para correr la app.
llimphi-wasm-dist = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-wasm-open = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-wasm-registry = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-wasm-wasi = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Transporte P2P del bytecode por hash sobre BrahmanNet (card-net).
llimphi-wasm-net = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-3d = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Runner wlr-layer-shell: corre un `App` de Llimphi como barra anclada a un
# borde (no como ventana), reusando la plumbing sctk+wgpu que pata probó.
llimphi-layer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Paleta semántica compartida por las apps y los widgets.
llimphi-theme = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-theme = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Tweens y helpers de animación sobre el bucle Elm.
llimphi-motion = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-motion = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Máquina de estados de animación (estilo Rive) clip-agnóstica. Núcleo puro;
# el render lo cablea el consumidor (llimphi-lottie). Escalón sobre el playback.
llimphi-anim = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Studio de animación «rive»: además del editor (bin), expone como lib los
# documentos serializables (Doc + RigDoc + Project) que carga mirada-fondo.
llimphi-anim-studio = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Render de mallas deformables (skel::Mesh) a vello Scene: vectorial y texturizada.
llimphi-mesh = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Iconos vectoriales (BezPath en grid 24×24) compartidos por todas las apps.
llimphi-icons = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-icons = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Puente fino vello_svg → Llimphi para SVG arbitrario (íconos .desktop, logos).
llimphi-svg = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Puente fino velato (Lottie) → Llimphi para animación vectorial autorada (.json).
llimphi-lottie = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-image = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Decodificador de protocolos de gráficos de terminal (kitty/sixel) → RGBA.
llimphi-term-graphics = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Widgets reusables sobre llimphi-ui — uno por crate.
llimphi-widget-app-header = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-banner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-clipboard = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-context-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-edit-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-menubar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-grid = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-scroll = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-splitter = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-stat-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-tabs = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-module-command-palette = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-module-diff-viewer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-module-fif = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-module-file-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-module-bookmarks = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-module-mini-map = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-module-shuma-term = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-module-symbol-outline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-plugin-host = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-theme-switcher = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-text-area = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-text-editor-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-text-editor = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-text-editor-lsp = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-text-input = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-tiled = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-nodegraph = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-tree = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-navigator = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-app-header = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-banner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-rive-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-clipboard = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-context-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-edit-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-menubar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-grid = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-table = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-color-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-scroll = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-lazy-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-splitter = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-stat-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-tabs = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-module-allichay = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-module-command-palette = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-module-diff-viewer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-module-fif = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-module-file-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-module-bookmarks = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-module-mini-map = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-module-shuma-term = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-module-symbol-outline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-plugin-host = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-theme-switcher = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-text-area = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-text-editor-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-text-editor = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-text-editor-lsp = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-text-input = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-tiled = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-nodegraph = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-tree = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
grafo-nav = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
llimphi-widget-navigator = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-detail-table = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-select = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-terminal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Sello vectorial wawa (rombo + W implícita + Merkle Core).
llimphi-widget-wawa-mark = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-wawa-mark = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Widgets de elegancia transversal (tooltip, spinner, progress, toast,
# modal, empty, status-bar, shortcuts-help, splash).
llimphi-widget-tooltip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-spinner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-progress = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-toast = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-modal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-empty = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-status-bar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-shortcuts-help = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-timeline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-splash = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-tooltip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-spinner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-progress = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-toast = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-modal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-empty = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-status-bar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-shortcuts-help = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-timeline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-transport = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-waveform = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-splash = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Controles de formulario y signaling (switch, segmented, breadcrumb,
# badge, avatar, skeleton, field).
llimphi-widget-switch = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-segmented = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-dock-rail = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-breadcrumb = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-badge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-avatar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-skeleton = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-field = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-switch = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-segmented = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-rag-sidebar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-dock-rail = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-toolbar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-breadcrumb = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-badge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-avatar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-skeleton = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-field = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Firma visual transversal (gradient sutil + hairline accent).
llimphi-widget-panel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-panes = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-workspace = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-widget-panel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-panes = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Widgets Flutter-like — composición sobre primitivas Tier 1/3 ya
# expuestas (sombra, gradient, animated, ripple).
llimphi-widget-chip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-fab = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-wrap = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-range-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-calendar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-fitted-box = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-carousel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-rating = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-gauge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-scaffold = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-hero = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-widget-router = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
llimphi-workspace = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# Abstracción Selector — host (paths) + wawa (khipus).
llimphi-module-selector = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.1.0" }
llimphi-module-selector = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
# === Filesystem helpers ===
directories = "5"
@@ -290,7 +426,7 @@ hex = "0.4"
portable-pty = "0.9"
vt100 = "0.16"
# === WASM web (gioser) ===
# === WASM web (tawasuyu) ===
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
@@ -315,23 +451,46 @@ ttf-parser = "0.25"
# ============================================================
# Intra-workspace deps de nahual (referenciadas por workspace = true)
# ============================================================
nahual-text-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-image-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-thumb-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-gallery-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-video-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-card-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-audio-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-tree-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-hex-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-table-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-markdown-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-archive-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-font-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-map-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-geo-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-viewer-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-file-explorer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
nahual-text-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-image-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-pdf-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-cbz-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-thumb-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-gallery-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-video-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-card-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-audio-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
media-module = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
tullpu-module = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
tullpu-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
tullpu-icon-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
tullpu-icon-cli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
tullpu-icon-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
tullpu-icon-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
foreign-svg = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
foreign-pdf = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
tullpu-render = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
tullpu-render-gpu = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
tullpu-ops = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
tullpu-paint = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-tree-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-hex-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-svg-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-sheet-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-dbf-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-table-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-markdown-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-pluma-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-cotejo-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-deck-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-archive-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-font-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-map-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-geo-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-geo-voxel = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-iconos = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-viewer-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
nahual-file-explorer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# ============================================================
# Intra-workspace deps de pineal (módulo de gráficos)
@@ -356,20 +515,28 @@ pineal = { path = "00_unanchay/pineal/pineal-umbrella" }
# ============================================================
# Intra-workspace deps de iniy (laboratorio semántico de creencias)
# ============================================================
iniy-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
iniy-ingest = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
iniy-extract = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
iniy-nli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
iniy-nli-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
iniy-graph = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
iniy-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
iniy-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-evidencia = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-derive = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-emisores = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-cache = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-daemon = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-ingest = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-extract = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-nli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-nli-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-graph = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
iniy-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# la física de decaimiento de khipu la importa iniy-derive (SDD iniy §3)
khipu-gravity = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# === auto: declarados por crates internos faltantes ===
cosmos-coords = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
cosmos-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
cosmos-ephemeris = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
cosmos-time = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
cosmos-wcs = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.1.0" }
cosmos-coords = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
cosmos-cities = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
cosmos-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
cosmos-ephemeris = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
cosmos-time = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
cosmos-wcs = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
# === auto: externas de eternal ===
celestial-eop-data = { version = "0.1"}
@@ -377,6 +544,7 @@ approx = "0.5"
byteorder = "1.5"
cc = "1.0"
chrono = "0.4"
chrono-tz = "0.10"
crc32fast = "1.4"
criterion = "0.5"
csv = "1.4"
@@ -385,6 +553,11 @@ glob = "0.3"
indicatif = "0.18"
lz4_flex = "0.11"
memmap2 = "0.9"
# Cliente Wayland (lado cliente) — lo usa hapiy para hablar zwlr_screencopy
# contra mirada (u otro compositor wlroots) y capturar la pantalla.
wayland-client = "0.31"
wayland-protocols-wlr = { version = "0.3", features = ["client"] }
wayland-protocols-misc = { version = "0.3", features = ["client"] }
mockito = "1.0"
ndarray = "0.15"
num-traits = "0.2"
@@ -418,6 +591,9 @@ markup5ever_rcdom = "0.39"
cssparser = "0.35"
url = "2"
ureq = { version = "2", default-features = false, features = ["tls"] }
# Charset de la web real: no todo es UTF-8 (google.com sirve ISO-8859-1).
# Ya venía transitivo por el stack de Servo — aquí se hace explícito.
encoding_rs = "0.8"
# === takiy-synth (SoundFont MIDI) ===
# rustysynth = sintetizador SF2 puro Rust, MIT. Reemplaza el oscilador
@@ -446,7 +622,7 @@ hound = "3.5"
symphonia = { version = "0.5", default-features = false, features = ["mp3", "flac", "vorbis", "ogg"] }
# === media-source-opus (decoder Opus NATIVO puro-Rust) ===
# Opus es el formato de audio nativo de gioser (par del video AV1). ogg
# Opus es el formato de audio nativo de tawasuyu (par del video AV1). ogg
# demuxea las páginas Ogg; opus-wave es un port puro-Rust de libopus
# (SILK+CELT, sin C ni FFI) — par del rav1d del lado video.
ogg = "0.9"
@@ -457,3 +633,21 @@ opus-wave = "3"
# paquetes de los tracks V_AV1 y A_OPUS para alimentar a media-source-av1
# y media-source-opus — un .webm AV1+Opus se reproduce 100% nativo.
matroska-demuxer = "0.7"
# === Perfil `dist`: binarios de reparto livianos (equipos chicos / poca RAM) ===
# Hereda de `release` pero prioriza HUELLA sobre tiempo de compilación: LTO thin
# (poda código muerto entre crates), un solo codegen-unit (mejor inlining/DCE) y
# `strip` de símbolos. Reduce el tamaño en disco Y el segmento de código mapeado
# en RAM de cada binario (mirada, pata, apps Llimphi). No toca `cargo build
# --release` de iteración diaria; se usa sólo al empaquetar:
# cargo build --profile dist -p mirada-compositor -p pata-host …
# `panic = "unwind"` se mantiene a propósito: mirada-plugin-host aísla plugins
# con `catch_unwind`, así que `abort` rompería el sandbox.
[profile.dist]
inherits = "release"
lto = "thin"
codegen-units = 1
strip = "symbols"
panic = "unwind"