⎇ 2b2a92a72b8aa6d4e006189cbeda5a53e74a80c0
crates/apps/pineal-demo/src/main.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG
1//! `pineal-demo` — demo visual de Lapaloma sobre nahual.
2//!
3//! Ventana 900×560 con un chart cartesiano de **3 series**
4//! simultáneas sobre 1024 muestras:
5//!
6//! - `sin(x · 0.04)` — azul nórdico
7//! - `cos(x · 0.04)` — naranja
8//! - `0.5·sin(x · 0.02) + 0.5·cos(x · 0.08)` — verde
9//!
10//! Interacción: click+drag = pan, wheel = zoom, doble-click = reset.
11
12use gpui::{
13 div, prelude::*, px, ClickEvent, Context, IntoElement, MouseButton, MouseDownEvent,
14 MouseMoveEvent, MouseUpEvent, Point, Render, ScrollDelta, ScrollWheelEvent, Window,
15};
16
17use pineal_cartesian::{chart_cache, ChartCacheHandle, ChartViewport, LapalomaChartElement};
18use pineal_core::buffer::DataBuffer;
19use pineal_render::{Color, StrokeStyle};
20use nahual_launcher::launch_app;
21use nahual_theme::Theme;
22
23const N_SAMPLES: usize = 1024;
24const WHEEL_SENSITIVITY: f64 = 0.0015;
25
26fn main() {
27 launch_app(
28 "Lapaloma — multi-series (drag = pan, wheel = zoom, dbl-click = reset)",
29 (900., 560.),
30 Demo::new,
31 );
32}
33
34struct Demo {
35 series_sin: DataBuffer,
36 series_cos: DataBuffer,
37 series_mix: DataBuffer,
38 viewport: ChartViewport,
39 initial_viewport: ChartViewport,
40 drag: Option<DragAnchor>,
41 chart_cache: ChartCacheHandle,
42}
43
44#[derive(Clone, Copy)]
45struct DragAnchor {
46 start_position: Point<gpui::Pixels>,
47 viewport_at_start: ChartViewport,
48}
49
50impl Demo {
51 fn new(_cx: &mut Context<Self>) -> Self {
52 let mut sin = DataBuffer::with_capacity(N_SAMPLES);
53 let mut cos = DataBuffer::with_capacity(N_SAMPLES);
54 let mut mix = DataBuffer::with_capacity(N_SAMPLES);
55 for i in 0..N_SAMPLES {
56 let x = i as f32;
57 sin.push(x, (x * 0.04).sin());
58 cos.push(x, (x * 0.04).cos());
59 mix.push(x, 0.5 * (x * 0.02).sin() + 0.5 * (x * 0.08).cos());
60 }
61 let viewport = ChartViewport::new(0.0, (N_SAMPLES - 1) as f64, -1.3, 1.3);
62 Self {
63 series_sin: sin,
64 series_cos: cos,
65 series_mix: mix,
66 viewport,
67 initial_viewport: viewport,
68 drag: None,
69 chart_cache: chart_cache(),
70 }
71 }
72
73 fn on_mouse_down(&mut self, e: &MouseDownEvent, _w: &mut Window, cx: &mut Context<Self>) {
74 self.drag = Some(DragAnchor {
75 start_position: e.position,
76 viewport_at_start: self.viewport,
77 });
78 cx.notify();
79 }
80
81 fn on_mouse_move(&mut self, e: &MouseMoveEvent, window: &mut Window, cx: &mut Context<Self>) {
82 let Some(anchor) = self.drag else { return };
83 let win = window.viewport_size();
84 let w: f32 = win.width.into();
85 let h: f32 = win.height.into();
86 if w <= 0.0 || h <= 0.0 {
87 return;
88 }
89 let sx: f32 = e.position.x.into();
90 let sy: f32 = e.position.y.into();
91 let ax: f32 = anchor.start_position.x.into();
92 let ay: f32 = anchor.start_position.y.into();
93 let dfx = ((sx - ax) / w) as f64;
94 let dfy = ((sy - ay) / h) as f64;
95 let mut vp = anchor.viewport_at_start;
96 vp.pan_fraction(dfx, dfy);
97 self.viewport = vp;
98 cx.notify();
99 }
100
101 fn on_mouse_up(&mut self, _e: &MouseUpEvent, _w: &mut Window, cx: &mut Context<Self>) {
102 if self.drag.take().is_some() {
103 cx.notify();
104 }
105 }
106
107 fn on_scroll(&mut self, e: &ScrollWheelEvent, window: &mut Window, cx: &mut Context<Self>) {
108 let win = window.viewport_size();
109 let w: f32 = win.width.into();
110 let h: f32 = win.height.into();
111 if w <= 0.0 || h <= 0.0 {
112 return;
113 }
114 let dy_px: f32 = match e.delta {
115 ScrollDelta::Pixels(p) => p.y.into(),
116 ScrollDelta::Lines(p) => p.y * 16.0,
117 };
118 let factor = (-dy_px as f64 * WHEEL_SENSITIVITY).exp();
119 let sx: f32 = e.position.x.into();
120 let sy: f32 = e.position.y.into();
121 let ax = (sx / w).clamp(0.0, 1.0) as f64;
122 let ay = (1.0 - sy / h).clamp(0.0, 1.0) as f64;
123 self.viewport.zoom_uniform(factor, (ax, ay));
124 cx.notify();
125 }
126
127 fn on_click(&mut self, e: &ClickEvent, _w: &mut Window, cx: &mut Context<Self>) {
128 if let ClickEvent::Mouse(m) = e {
129 if m.up.click_count >= 2 {
130 self.viewport = self.initial_viewport;
131 cx.notify();
132 }
133 }
134 }
135}
136
137/// Color helper para usar el mismo hex tanto en `pineal_render`
138/// como en el body de texto del header del demo.
139const COLOR_SIN: u32 = 0x88c0d0; // azul nórdico
140const COLOR_COS: u32 = 0xd08770; // naranja
141const COLOR_MIX: u32 = 0xa3be8c; // verde
142
143impl Render for Demo {
144 fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
145 let theme = Theme::global(cx).clone();
146
147 let plot_bg = Color::rgba(0.10, 0.12, 0.16, 1.0);
148 let chart = LapalomaChartElement::new(self.viewport)
149 .background(plot_bg)
150 .with_cache(self.chart_cache.clone())
151 .add_series_named(
152 self.series_sin.clone(),
153 StrokeStyle::new(2.0, Color::from_hex(COLOR_SIN)),
154 "sin",
155 )
156 .add_series_named(
157 self.series_cos.clone(),
158 StrokeStyle::new(2.0, Color::from_hex(COLOR_COS)),
159 "cos",
160 )
161 .add_series_named(
162 self.series_mix.clone(),
163 StrokeStyle::new(2.0, Color::from_hex(COLOR_MIX)),
164 "mix",
165 );
166
167 let drag_active = self.drag.is_some();
168 let (pan_blits, rebuilds) = {
169 let c = self.chart_cache.lock().unwrap();
170 (c.pan_blits(), c.rebuilds())
171 };
172
173 div()
174 .id("pineal-demo-root")
175 .size_full()
176 .bg(theme.bg_app.clone())
177 .p(px(16.))
178 .flex()
179 .flex_col()
180 .gap(px(12.))
181 .child(
182 div()
183 .text_color(theme.fg_text)
184 .text_size(px(18.))
185 .child("Lapaloma — demo cartesian multi-series"),
186 )
187 .child(
188 div()
189 .flex()
190 .gap(px(14.))
191 .text_size(px(11.))
192 .child(
193 div()
194 .text_color(gpui::rgb(COLOR_SIN))
195 .child("■ sin(x · 0.04)"),
196 )
197 .child(
198 div()
199 .text_color(gpui::rgb(COLOR_COS))
200 .child("■ cos(x · 0.04)"),
201 )
202 .child(
203 div()
204 .text_color(gpui::rgb(COLOR_MIX))
205 .child("■ ½·sin(x · 0.02) + ½·cos(x · 0.08)"),
206 )
207 .child(
208 div()
209 .text_color(theme.fg_muted)
210 .child(format!(
211 "· cache: {} pan-blits / {} rebuilds {}",
212 pan_blits,
213 rebuilds,
214 if drag_active { "· dragging" } else { "" },
215 )),
216 ),
217 )
218 .child(
219 div()
220 .id("pineal-chart-host")
221 .w_full()
222 .flex_grow()
223 .child(chart)
224 .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
225 .on_mouse_move(cx.listener(Self::on_mouse_move))
226 .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
227 .on_scroll_wheel(cx.listener(Self::on_scroll))
228 .on_click(cx.listener(Self::on_click)),
229 )
230 }
231}