⎇ d3649bfd1a80153652c2a028315369bb4b184024
crates/modules/ui_engine/widgets/lapaloma-financial/src/element.rs
Historia
Quién tocó qué
crudo
feat(lapaloma-financial): OHLC + candlesticks con preserva-volatilidad
1//! `LapalomaCandlestickElement` — Element GPUI para charts OHLC.
2//!
3//! Reusa `lapaloma_cartesian::axis::paint_axes` para los ejes y el
4//! `WindowCanvas` adapter de `lapaloma-render` para el output.
5//! El render mismo de las velas lo hace `paint_candlesticks` que
6//! es agnóstico de gpui — facilita futuros backends (SVG, wgpu).
7//!
8//! Sin cache pan-blit en v0.1: las velas se redibujan cada frame.
9//! Para hasta ~500 bars on-screen son sub-millisecond; con
10//! aggregation razonable (1 bar por columna de pixel) eso cubre
11//! cualquier caso humano. Si se necesita más, se replica el
12//! patrón de `LapalomaChartElement::with_cache`.
13
14use std::panic;
15
16use gpui::{
17 App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
18 Pixels, Style, Window,
19};
20
21use lapaloma_cartesian::axis::{self, AxisStyle};
22use lapaloma_cartesian::{ChartViewport, CoordinateSystem};
23use lapaloma_render::{Canvas, Color, Rect, WindowCanvas};
24
25use crate::candlestick::{paint_candlesticks, CandlestickStyle};
26use crate::ohlc_buffer::OhlcBuffer;
27
28const TARGET_TICKS_X: usize = 8;
29const TARGET_TICKS_Y: usize = 6;
30
31pub struct LapalomaCandlestickElement {
32 pub data: OhlcBuffer,
33 pub viewport: ChartViewport,
34 pub style: CandlestickStyle,
35 pub background: Option<Color>,
36 pub axis_color: Color,
37 pub axis_style: AxisStyle,
38 pub margin_bottom: f32,
39 pub margin_left: f32,
40 pub margin_top: f32,
41 pub margin_right: f32,
42}
43
44impl LapalomaCandlestickElement {
45 pub fn new(data: OhlcBuffer, viewport: ChartViewport) -> Self {
46 Self {
47 data,
48 viewport,
49 style: CandlestickStyle::default(),
50 background: None,
51 axis_color: Color::rgba(0.6, 0.6, 0.65, 0.8),
52 axis_style: AxisStyle::default(),
53 margin_bottom: 24.0,
54 margin_left: 48.0,
55 margin_top: 8.0,
56 margin_right: 8.0,
57 }
58 }
59
60 pub fn background(mut self, color: Color) -> Self {
61 self.background = Some(color);
62 self
63 }
64 pub fn axis_color(mut self, color: Color) -> Self {
65 self.axis_color = color;
66 self
67 }
68 pub fn style(mut self, style: CandlestickStyle) -> Self {
69 self.style = style;
70 self
71 }
72 pub fn margins(mut self, top: f32, right: f32, bottom: f32, left: f32) -> Self {
73 self.margin_top = top;
74 self.margin_right = right;
75 self.margin_bottom = bottom;
76 self.margin_left = left;
77 self
78 }
79
80 fn plot_rect(&self, bounds: Rect) -> Rect {
81 Rect::new(
82 bounds.x + self.margin_left,
83 bounds.y + self.margin_top,
84 (bounds.w - self.margin_left - self.margin_right).max(1.0),
85 (bounds.h - self.margin_top - self.margin_bottom).max(1.0),
86 )
87 }
88}
89
90impl IntoElement for LapalomaCandlestickElement {
91 type Element = Self;
92 fn into_element(self) -> Self::Element {
93 self
94 }
95}
96
97impl Element for LapalomaCandlestickElement {
98 type RequestLayoutState = ();
99 type PrepaintState = ();
100
101 fn id(&self) -> Option<ElementId> {
102 None
103 }
104 fn source_location(&self) -> Option<&'static panic::Location<'static>> {
105 None
106 }
107
108 fn request_layout(
109 &mut self,
110 _id: Option<&GlobalElementId>,
111 _inspector_id: Option<&InspectorElementId>,
112 window: &mut Window,
113 cx: &mut App,
114 ) -> (LayoutId, Self::RequestLayoutState) {
115 let mut style = Style::default();
116 style.size.width = gpui::Length::Definite(gpui::DefiniteLength::Fraction(1.0));
117 style.size.height = gpui::Length::Definite(gpui::DefiniteLength::Fraction(1.0));
118 let id = window.request_layout(style, [], cx);
119 (id, ())
120 }
121
122 fn prepaint(
123 &mut self,
124 _id: Option<&GlobalElementId>,
125 _inspector_id: Option<&InspectorElementId>,
126 _bounds: Bounds<Pixels>,
127 _request_layout: &mut Self::RequestLayoutState,
128 _window: &mut Window,
129 _cx: &mut App,
130 ) -> Self::PrepaintState {
131 }
132
133 fn paint(
134 &mut self,
135 _id: Option<&GlobalElementId>,
136 _inspector_id: Option<&InspectorElementId>,
137 bounds: Bounds<Pixels>,
138 _request_layout: &mut Self::RequestLayoutState,
139 _prepaint: &mut Self::PrepaintState,
140 window: &mut Window,
141 _cx: &mut App,
142 ) {
143 let ox: f32 = bounds.origin.x.into();
144 let oy: f32 = bounds.origin.y.into();
145 let w: f32 = bounds.size.width.into();
146 let h: f32 = bounds.size.height.into();
147 let outer = Rect::new(ox, oy, w, h);
148 let plot = self.plot_rect(outer);
149
150 let cs = CoordinateSystem::new(self.viewport, plot);
151 let mut canvas = WindowCanvas::new(window);
152
153 if let Some(bg) = self.background {
154 canvas.fill_rect(outer, bg);
155 }
156
157 axis::paint_axes(
158 &mut canvas,
159 &cs,
160 &self.viewport,
161 self.axis_color,
162 self.axis_style,
163 TARGET_TICKS_X,
164 TARGET_TICKS_Y,
165 );
166
167 paint_candlesticks(&mut canvas, &cs, &self.data, self.style);
168 }
169}
170
171pub fn lapaloma_candlestick(
172 data: OhlcBuffer,
173 viewport: ChartViewport,
174) -> LapalomaCandlestickElement {
175 LapalomaCandlestickElement::new(data, viewport)
176}