rustial-engine 0.0.1

Framework-agnostic 2.5D map engine for rustial
Documentation
//! Legend metadata for client-side UI rendering.

use super::ColorRamp;

/// Normalization mode for legend value display.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NormalizationMode {
    /// Values are displayed as-is.
    Absolute,
    /// Values are normalized to `[0, 1]` using the field min/max.
    ZeroToOne,
    /// Values are displayed as percentages `[0, 100]`.
    Percentage,
}

/// A labeled stop in a legend.
#[derive(Debug, Clone)]
pub struct LabeledStop {
    /// Normalized position in `[0.0, 1.0]`.
    pub position: f32,
    /// Human-readable label for this stop.
    pub label: String,
}

/// Legend metadata sufficient for client-side UI rendering.
///
/// Rustial does **not** render the legend itself. This struct carries
/// enough information for a client application to build a legend widget
/// in Bevy UI, egui, HTML, or any other framework.
#[derive(Debug, Clone)]
pub struct LegendSpec {
    /// Human-readable title (e.g. `"Agent Density"`).
    pub title: String,
    /// Units string (e.g. `"agents/m2"`, `"dBm"`, `"%"`).
    pub units: String,
    /// The colour ramp driving both renderer output and legend display.
    pub ramp: ColorRamp,
    /// Minimum data value (maps to ramp position 0.0).
    pub min_value: f64,
    /// Maximum data value (maps to ramp position 1.0).
    pub max_value: f64,
    /// Optional labeled tick marks for the legend.
    pub labeled_stops: Vec<LabeledStop>,
    /// How values should be presented.
    pub normalization: NormalizationMode,
}

impl LegendSpec {
    /// Create a minimal legend spec.
    pub fn new(title: impl Into<String>, ramp: ColorRamp, min: f64, max: f64) -> Self {
        Self {
            title: title.into(),
            units: String::new(),
            ramp,
            min_value: min,
            max_value: max,
            labeled_stops: Vec::new(),
            normalization: NormalizationMode::Absolute,
        }
    }

    /// Set the units string.
    pub fn with_units(mut self, units: impl Into<String>) -> Self {
        self.units = units.into();
        self
    }

    /// Add a labeled stop.
    pub fn with_stop(mut self, position: f32, label: impl Into<String>) -> Self {
        self.labeled_stops.push(LabeledStop {
            position,
            label: label.into(),
        });
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::visualization::{ColorRamp, ColorStop};

    #[test]
    fn legend_spec_builder() {
        let ramp = ColorRamp::new(vec![
            ColorStop {
                value: 0.0,
                color: [0.0, 0.0, 1.0, 1.0],
            },
            ColorStop {
                value: 1.0,
                color: [1.0, 0.0, 0.0, 1.0],
            },
        ]);
        let legend = LegendSpec::new("Test", ramp, 0.0, 100.0)
            .with_units("m/s")
            .with_stop(0.0, "Low")
            .with_stop(1.0, "High");

        assert_eq!(legend.title, "Test");
        assert_eq!(legend.units, "m/s");
        assert_eq!(legend.labeled_stops.len(), 2);
        assert!((legend.min_value - 0.0).abs() < 1e-9);
        assert!((legend.max_value - 100.0).abs() < 1e-9);
    }
}