Skip to main content

shape_viz_core/layers/
mod.rs

1//! Modular layer system for chart rendering
2//!
3//! The layer system allows composing complex charts from simple, reusable components.
4//! Each layer is responsible for rendering a specific aspect of the chart (candlesticks,
5//! grid, axes, indicators, etc.) and can be independently configured and updated.
6
7pub mod auxiliary_bar;
8pub mod background;
9pub mod crosshair;
10pub mod current_price;
11pub mod grid;
12pub mod indicators;
13pub mod price_axis;
14pub mod range_bar;
15pub mod region_shading;
16pub mod time_axis;
17pub mod watermark;
18
19// Re-export layer implementations
20pub use auxiliary_bar::AuxiliaryBarLayer;
21pub use background::BackgroundLayer;
22pub use crosshair::CrosshairLayer;
23pub use current_price::{CurrentPriceConfig, CurrentPriceLayer, LineStyle};
24pub use grid::GridLayer;
25pub use indicators::band::{BandConfig, BandLayer};
26pub use indicators::line_series::{LineSeriesConfig, LineSeriesLayer};
27pub use price_axis::PriceAxisLayer;
28pub use range_bar::{RangeBarConfig, RangeBarLayer, RangeBarStyle};
29pub use region_shading::{RegionBoundary, RegionShadingLayer, ShadingRegion};
30pub use time_axis::TimeAxisLayer;
31pub use watermark::{WatermarkConfig, WatermarkLayer};
32
33// Backwards compatibility type aliases
34pub use auxiliary_bar::VolumeLayer;
35pub use range_bar::{CandlestickConfig, CandlestickLayer};
36pub use region_shading::SessionShadingLayer;
37
38use crate::data::ChartData;
39use crate::error::Result;
40use crate::renderer::RenderContext;
41use crate::style::ChartStyle;
42use crate::theme::ChartTheme;
43use crate::viewport::{Rect, Viewport};
44
45/// Defines the coarse rendering order and default clipping region for a layer.
46///
47/// Layers are rendered from the smallest discriminant (background) to the
48/// largest (HUD). Within each stage, [`Layer::z_order`] determines the exact
49/// ordering so features can interleave when necessary.
50#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
51pub enum LayerStage {
52    /// Fills the entire render target (e.g. clear colour, full-screen background).
53    ScreenBackground,
54    /// Renders behind the main chart area (session shading, watermark).
55    ChartBackground,
56    /// Elements that sit underneath price data but inside the chart pane (gridlines).
57    ChartUnderlay,
58    /// Primary data plotted in the main chart pane (candles, bars).
59    ChartMain,
60    /// Technical overlays that should appear above price data (indicators).
61    ChartIndicator,
62    /// Bars/overlays that live exclusively in the volume pane.
63    VolumePane,
64    /// Foreground overlays sharing the main pane and axis surface (current price, annotations).
65    ChartOverlay,
66    /// Price-axis specific rendering (labels, markers).
67    PriceAxis,
68    /// Time-axis specific rendering (labels, markers).
69    TimeAxis,
70    /// Heads-up display drawn last (crosshair, tooltips).
71    Hud,
72}
73
74impl LayerStage {
75    /// Default clipping rectangle for the stage.
76    pub fn default_clip_rect(self, viewport: &Viewport) -> Rect {
77        match self {
78            LayerStage::ScreenBackground => viewport.screen_rect,
79            LayerStage::ChartBackground
80            | LayerStage::ChartUnderlay
81            | LayerStage::ChartMain
82            | LayerStage::ChartIndicator
83            | LayerStage::ChartOverlay => viewport.chart_content_rect(),
84            LayerStage::VolumePane => viewport.volume_rect(),
85            LayerStage::PriceAxis => viewport.price_axis_rect(),
86            LayerStage::TimeAxis => viewport.time_axis_rect(),
87            LayerStage::Hud => viewport.screen_rect,
88        }
89    }
90}
91
92/// Core trait for all chart layers
93///
94/// Layers are the building blocks of the chart rendering system. Each layer
95/// is responsible for rendering a specific visual component and can be
96/// independently updated and configured.
97pub trait Layer: Send + Sync {
98    /// Get the layer's name for debugging and identification
99    fn name(&self) -> &str;
100
101    /// Update the layer with new data
102    ///
103    /// This is called when the chart data changes. Layers should update
104    /// their internal state and prepare for rendering.
105    fn update(
106        &mut self,
107        data: &ChartData,
108        viewport: &Viewport,
109        theme: &ChartTheme,
110        style: &ChartStyle,
111    );
112
113    /// Rendering stage for the layer. Stages define coarse ordering buckets.
114    fn stage(&self) -> LayerStage {
115        LayerStage::ChartMain
116    }
117
118    /// Default clipping rectangle for this layer.
119    ///
120    /// Implementers can override this when a layer spans multiple panes (e.g.
121    /// the current-price line extends into the price axis).
122    fn clip_rect(&self, viewport: &Viewport) -> Rect {
123        self.stage().default_clip_rect(viewport)
124    }
125
126    /// Render the layer to the GPU context
127    ///
128    /// This is where the actual drawing happens. Layers should submit
129    /// their geometry to the render context for GPU rendering.
130    fn render(&self, context: &mut RenderContext, render_pass: &mut wgpu::RenderPass)
131    -> Result<()>;
132
133    /// Check if the layer needs to be rendered
134    ///
135    /// This allows for optimization by skipping layers that haven't changed
136    /// or are not visible in the current viewport.
137    fn needs_render(&self) -> bool {
138        true // Default: always render
139    }
140
141    /// Get the layer's Z-order for depth sorting
142    ///
143    /// Lower values are rendered first (background), higher values
144    /// are rendered last (foreground).
145    fn z_order(&self) -> i32 {
146        0 // Default: middle layer
147    }
148
149    /// Check if the layer is currently enabled
150    fn is_enabled(&self) -> bool {
151        true // Default: enabled
152    }
153
154    /// Enable or disable the layer
155    fn set_enabled(&mut self, enabled: bool);
156
157    /// Get layer configuration as a JSON-serializable value
158    ///
159    /// This allows saving and restoring layer configurations.
160    fn get_config(&self) -> serde_json::Value {
161        serde_json::Value::Null // Default: no configuration
162    }
163
164    /// Set layer configuration from a JSON value
165    fn set_config(&mut self, _config: serde_json::Value) -> Result<()> {
166        Ok(()) // Default: ignore configuration
167    }
168}
169
170/// Layer manager for organizing and rendering multiple layers
171pub struct LayerManager {
172    layers: Vec<Box<dyn Layer>>,
173    dirty: bool,
174}
175
176impl LayerManager {
177    /// Create a new empty layer manager
178    pub fn new() -> Self {
179        Self {
180            layers: Vec::new(),
181            dirty: true,
182        }
183    }
184
185    /// Add a layer to the manager
186    pub fn add_layer(&mut self, layer: Box<dyn Layer>) {
187        self.layers.push(layer);
188        self.sort_layers();
189        self.dirty = true;
190    }
191
192    /// Remove a layer by name
193    pub fn remove_layer(&mut self, name: &str) -> Option<Box<dyn Layer>> {
194        if let Some(index) = self.layers.iter().position(|layer| layer.name() == name) {
195            self.dirty = true;
196            Some(self.layers.remove(index))
197        } else {
198            None
199        }
200    }
201
202    /// Get a mutable reference to a layer by name
203    pub fn get_layer_mut(&mut self, name: &str) -> Option<&mut (dyn Layer + '_)> {
204        for layer in &mut self.layers {
205            if layer.name() == name {
206                return Some(&mut **layer);
207            }
208        }
209        None
210    }
211
212    /// Get an immutable reference to a layer by name
213    pub fn get_layer(&self, name: &str) -> Option<&dyn Layer> {
214        self.layers
215            .iter()
216            .find(|layer| layer.name() == name)
217            .map(|layer| layer.as_ref())
218    }
219
220    /// Update all layers with new data
221    pub fn update_all(
222        &mut self,
223        data: &ChartData,
224        viewport: &Viewport,
225        theme: &ChartTheme,
226        style: &ChartStyle,
227    ) {
228        for layer in &mut self.layers {
229            if layer.is_enabled() {
230                layer.update(data, viewport, theme, style);
231            }
232        }
233        self.dirty = true;
234    }
235
236    /// Render all enabled layers in Z-order
237    pub fn render_all(
238        &self,
239        context: &mut RenderContext,
240        render_pass: &mut wgpu::RenderPass,
241    ) -> Result<()> {
242        for layer in &self.layers {
243            if layer.is_enabled() && layer.needs_render() {
244                // Set stage priority for text grouping (stage enum discriminant + z_order for fine ordering)
245                #[cfg(feature = "text-rendering")]
246                {
247                    let stage_priority = (layer.stage() as i32) * 1000 + layer.z_order();
248                    context.set_stage_priority(stage_priority);
249                }
250
251                let clip_rect = {
252                    let viewport = context.viewport();
253                    layer.clip_rect(viewport)
254                };
255
256                // Clamp to positive extents before converting to u32.
257                let x = clip_rect.x.max(0.0) as u32;
258                let y = clip_rect.y.max(0.0) as u32;
259                let width = clip_rect.width.max(0.0) as u32;
260                let height = clip_rect.height.max(0.0) as u32;
261
262                render_pass.set_scissor_rect(x, y, width, height);
263
264                layer.render(context, render_pass)?;
265                context.commit(render_pass)?;
266            }
267        }
268        // Reset scissor rect to full screen after all layers are rendered
269        let full_rect = context.viewport().screen_rect;
270        render_pass.set_scissor_rect(
271            full_rect.x as u32,
272            full_rect.y as u32,
273            full_rect.width as u32,
274            full_rect.height as u32,
275        );
276        Ok(())
277    }
278
279    /// Check if any layer needs rendering
280    pub fn needs_render(&self) -> bool {
281        self.dirty
282            || self
283                .layers
284                .iter()
285                .any(|layer| layer.is_enabled() && layer.needs_render())
286    }
287
288    /// Mark the layer manager as clean (after rendering)
289    pub fn mark_clean(&mut self) {
290        self.dirty = false;
291    }
292
293    /// Get the number of layers
294    pub fn layer_count(&self) -> usize {
295        self.layers.len()
296    }
297
298    /// Get the number of enabled layers
299    pub fn enabled_layer_count(&self) -> usize {
300        self.layers
301            .iter()
302            .filter(|layer| layer.is_enabled())
303            .count()
304    }
305
306    /// List all layer names
307    pub fn layer_names(&self) -> Vec<&str> {
308        self.layers.iter().map(|layer| layer.name()).collect()
309    }
310
311    /// Enable or disable a layer by name
312    pub fn set_layer_enabled(&mut self, name: &str, enabled: bool) -> bool {
313        if let Some(layer) = self.get_layer_mut(name) {
314            layer.set_enabled(enabled);
315            self.dirty = true;
316            true
317        } else {
318            false
319        }
320    }
321
322    /// Clear all layers
323    pub fn clear(&mut self) {
324        self.layers.clear();
325        self.dirty = true;
326    }
327
328    /// Sort layers by Z-order
329    fn sort_layers(&mut self) {
330        use std::cmp::Ordering;
331
332        self.layers.sort_by(|a, b| match a.stage().cmp(&b.stage()) {
333            Ordering::Equal => a.z_order().cmp(&b.z_order()),
334            other => other,
335        });
336    }
337}
338
339impl Default for LayerManager {
340    fn default() -> Self {
341        Self::new()
342    }
343}
344
345/// Helper trait for creating common layer combinations
346pub trait LayerBuilder {
347    /// Create a basic range bar chart with grid and axes
348    fn basic_range_chart() -> LayerManager {
349        let mut manager = LayerManager::new();
350
351        // Add layers in rendering order (background to foreground)
352        manager.add_layer(Box::new(BackgroundLayer::new()));
353        manager.add_layer(Box::new(RegionShadingLayer::new()));
354        manager.add_layer(Box::new(GridLayer::new()));
355        manager.add_layer(Box::new(RangeBarLayer::new()));
356        manager.add_layer(Box::new(AuxiliaryBarLayer::new()));
357
358        let mut price_config = CurrentPriceConfig::default();
359        price_config.line_style = LineStyle::Dashed;
360        price_config.line_width = 1.0;
361        manager.add_layer(Box::new(CurrentPriceLayer::with_config(price_config)));
362        manager.add_layer(Box::new(PriceAxisLayer::new()));
363        manager.add_layer(Box::new(TimeAxisLayer::new()));
364        manager.add_layer(Box::new(CrosshairLayer::new()));
365
366        manager
367    }
368
369    /// Backwards compatibility alias
370    fn basic_financial_chart() -> LayerManager {
371        Self::basic_range_chart()
372    }
373
374    /// Create a chart with watermark
375    fn chart_with_watermark(label: String) -> LayerManager {
376        let mut manager = Self::basic_range_chart();
377        manager.add_layer(Box::new(WatermarkLayer::new(label)));
378        manager
379    }
380}
381
382impl LayerBuilder for LayerManager {}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387    use crate::theme::ChartTheme;
388    use crate::viewport::Viewport;
389
390    // Mock layer for testing
391    struct MockLayer {
392        name: String,
393        enabled: bool,
394        z_order: i32,
395        stage: LayerStage,
396        render_count: std::sync::Arc<std::sync::Mutex<u32>>,
397    }
398
399    impl MockLayer {
400        fn new(name: &str, z_order: i32) -> Self {
401            Self::with_stage(name, z_order, LayerStage::ChartMain)
402        }
403
404        fn with_stage(name: &str, z_order: i32, stage: LayerStage) -> Self {
405            Self {
406                name: name.to_string(),
407                enabled: true,
408                z_order,
409                stage,
410                render_count: std::sync::Arc::new(std::sync::Mutex::new(0)),
411            }
412        }
413    }
414
415    impl Layer for MockLayer {
416        fn name(&self) -> &str {
417            &self.name
418        }
419
420        fn update(
421            &mut self,
422            _data: &ChartData,
423            _viewport: &Viewport,
424            _theme: &ChartTheme,
425            _style: &ChartStyle,
426        ) {
427            // Mock update
428        }
429
430        fn stage(&self) -> LayerStage {
431            self.stage
432        }
433
434        fn render(
435            &self,
436            _context: &mut RenderContext,
437            _render_pass: &mut wgpu::RenderPass,
438        ) -> Result<()> {
439            *self.render_count.lock().unwrap() += 1;
440            Ok(())
441        }
442
443        fn z_order(&self) -> i32 {
444            self.z_order
445        }
446
447        fn is_enabled(&self) -> bool {
448            self.enabled
449        }
450
451        fn set_enabled(&mut self, enabled: bool) {
452            self.enabled = enabled;
453        }
454    }
455
456    #[test]
457    fn test_layer_manager_basic_operations() {
458        let mut manager = LayerManager::new();
459        assert_eq!(manager.layer_count(), 0);
460
461        // Add layers
462        manager.add_layer(Box::new(MockLayer::new("layer1", 1)));
463        manager.add_layer(Box::new(MockLayer::new("layer2", 0)));
464        manager.add_layer(Box::new(MockLayer::new("layer3", 2)));
465
466        assert_eq!(manager.layer_count(), 3);
467        assert_eq!(manager.enabled_layer_count(), 3);
468
469        // Check Z-order sorting
470        let names = manager.layer_names();
471        assert_eq!(names, vec!["layer2", "layer1", "layer3"]); // Sorted by z_order
472    }
473
474    #[test]
475    fn test_layer_enable_disable() {
476        let mut manager = LayerManager::new();
477        manager.add_layer(Box::new(MockLayer::new("test", 0)));
478
479        assert_eq!(manager.enabled_layer_count(), 1);
480
481        manager.set_layer_enabled("test", false);
482        assert_eq!(manager.enabled_layer_count(), 0);
483
484        manager.set_layer_enabled("test", true);
485        assert_eq!(manager.enabled_layer_count(), 1);
486    }
487
488    #[test]
489    fn test_layer_removal() {
490        let mut manager = LayerManager::new();
491        manager.add_layer(Box::new(MockLayer::new("remove_me", 0)));
492        manager.add_layer(Box::new(MockLayer::new("keep_me", 1)));
493
494        assert_eq!(manager.layer_count(), 2);
495
496        let removed = manager.remove_layer("remove_me");
497        assert!(removed.is_some());
498        assert_eq!(manager.layer_count(), 1);
499
500        let not_found = manager.remove_layer("not_found");
501        assert!(not_found.is_none());
502    }
503
504    #[test]
505    fn test_stage_sorting() {
506        let mut manager = LayerManager::new();
507        manager.add_layer(Box::new(MockLayer::with_stage("hud", 0, LayerStage::Hud)));
508        manager.add_layer(Box::new(MockLayer::with_stage(
509            "background",
510            0,
511            LayerStage::ScreenBackground,
512        )));
513        manager.add_layer(Box::new(MockLayer::with_stage(
514            "indicator",
515            0,
516            LayerStage::ChartIndicator,
517        )));
518
519        let names = manager.layer_names();
520        assert_eq!(names, vec!["background", "indicator", "hud"]);
521    }
522}