Files
brahman/crates/apps/lapaloma-phosphor-demo/src/main.rs
T
sergio ab03a61db4 feat(lapaloma-phosphor): trail CRT con alpha decay + glow
- lapaloma-phosphor: feature `gpui` (default). LapalomaPhosphorElement
  divide el RingBuffer en N segmentos (default 16, configurable) y
  pinta cada uno como una stroke_polyline con alpha = (k+1)/N. El
  segmento más nuevo va con alpha 1.0, el más viejo casi
  transparente — efecto fósforo persistente.
- Cada segmento incluye el primer punto del siguiente para evitar
  gaps visibles entre tramos.
- Wraparound se parte en dos sub-polilíneas (no concatenadas) para
  no introducir la línea horizontal "del slot cap-1 al slot 0".
- Glow opcional: pasada adicional con width × glow_width_mult y
  alpha × glow_alpha — efecto halo CRT.
- crates/apps/lapaloma-phosphor-demo: misma señal sintética que
  stream-demo, paleta verde Tektronix (#9bff8c sobre #050805),
  trail 24 segs + glow 4× α 0.18.

Limitación v0.1: el doc canónico usa triangle strip con per-vertex
color (sección 4.3); GPUI 0.2 no expone esa API directa. La impl
actual es funcionalmente equivalente con N draw calls en lugar de 1.
Cuando wgpu directo esté disponible, swap inmediato sin tocar las
API públicas.

46 tests verdes (sin cambios; phosphor se valida via demo).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 03:05:16 +00:00

117 lines
3.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! `lapaloma-phosphor-demo` — osciloscopio con trail CRT.
//!
//! Igual setup que `lapaloma-stream-demo` (RingBuffer 512 +
//! timer 60 Hz) pero el render usa `LapalomaPhosphorElement`:
//! el trail decae en alpha del cursor hacia atrás y arrastra un
//! halo (glow). Visualmente queda como un osciloscopio analógico
//! con fósforo persistente.
//!
//! Sliders para `trail_segments` y `glow` se dejan para más
//! adelante; este demo usa los defaults.
use std::time::Duration;
use gpui::{div, prelude::*, px, Context, IntoElement, Render, Window};
use lapaloma_core::ring::RingBuffer;
use lapaloma_phosphor::lapaloma_phosphor;
use lapaloma_render::{Color, StrokeStyle};
use yahweh_launcher::launch_app;
use yahweh_theme::Theme;
const RING_CAPACITY: usize = 512;
const SAMPLE_PERIOD: Duration = Duration::from_millis(16);
fn main() {
launch_app(
"Lapaloma — phosphor trail (CRT 60 Hz)",
(900., 480.),
PhosphorDemo::new,
);
}
struct PhosphorDemo {
buffer: RingBuffer,
t: u64,
}
impl PhosphorDemo {
fn new(cx: &mut Context<Self>) -> Self {
cx.spawn(async move |this, cx| {
let timer = cx.background_executor().clone();
loop {
timer.timer(SAMPLE_PERIOD).await;
let r = this.update(cx, |me, cx| {
me.tick();
cx.notify();
});
if r.is_err() {
break;
}
}
})
.detach();
Self {
buffer: RingBuffer::new(RING_CAPACITY),
t: 0,
}
}
fn tick(&mut self) {
let v = synthesize(self.t);
self.buffer.push(v);
self.t = self.t.wrapping_add(1);
}
}
fn synthesize(t: u64) -> f32 {
let phase = t as f32;
let signal = (phase * 0.07).sin() * 0.75 + (phase * 0.19).sin() * 0.22;
let jitter = ((phase * 37.0).sin() * 1000.0).fract() * 0.04;
signal + jitter
}
impl Render for PhosphorDemo {
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = Theme::global(cx).clone();
// Verde fósforo CRT clásico (Tektronix vibes).
let plot_bg = Color::rgba(0.03, 0.05, 0.04, 1.0);
let trace = StrokeStyle::new(1.6, Color::from_hex(0x9bff8c));
let phosphor = lapaloma_phosphor(self.buffer.clone(), trace)
.background(plot_bg)
.y_range(-1.2, 1.2)
.trail_segments(24)
.glow(4.0, 0.18);
div()
.size_full()
.bg(theme.bg_app.clone())
.p(px(16.))
.flex()
.flex_col()
.gap(px(10.))
.child(
div()
.text_color(theme.fg_text)
.text_size(px(18.))
.child("Lapaloma — phosphor"),
)
.child(
div()
.flex()
.gap(px(16.))
.text_size(px(11.))
.text_color(theme.fg_muted)
.child(format!("cap = {}", RING_CAPACITY))
.child(format!("head = {}", self.buffer.head()))
.child("trail = 24 segs")
.child("glow = 4× / α 0.18")
.child(format!("t = {}", self.t)),
)
.child(div().w_full().flex_grow().child(phosphor))
}
}