scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use serde::{Deserialize, Serialize};

use crate::scene::SceneMaterialInspectionV1;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AppearanceIntrospectionOptions {
    pub(super) detail: bool,
    pub(super) background_rgba8: [u8; 4],
    pub(super) content_tolerance_rgba8: u8,
    pub(super) active_variant: Option<String>,
    pub(super) available_variants: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AppearanceExpectationV1 {
    pub schema: String,
    #[serde(default)]
    pub targets: Vec<AppearanceTargetExpectationV1>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AppearanceTargetExpectationV1 {
    pub id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub node: Option<u64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tag: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub variant: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub color_family: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub swatch_srgb8: Option<[u8; 3]>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub swatch_tolerance: Option<f32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub alpha_mode: Option<String>,
    #[serde(default, skip_serializing_if = "is_false")]
    pub require_source_material: bool,
    #[serde(default, skip_serializing_if = "is_false")]
    pub require_base_color_texture: bool,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AppearanceIntrospectionReportV1 {
    pub schema: String,
    pub ok: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub active_variant: Option<String>,
    #[serde(default)]
    pub available_variants: Vec<String>,
    pub summary: AppearanceSummaryV1,
    pub targets: Vec<AppearanceTargetReportV1>,
    pub reasons: Vec<AppearanceReasonV1>,
    pub fixes: Vec<AppearanceFixV1>,
    pub artifacts: AppearanceArtifactsV1,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct AppearanceSummaryV1 {
    pub targets: usize,
    pub matched: usize,
    pub errors: usize,
    pub warnings: usize,
    pub sampled_pixels: u64,
    pub luminance_mean: f32,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AppearanceTargetReportV1 {
    pub id: String,
    pub matched: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub node: Option<u64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub material: Option<SceneMaterialInspectionV1>,
    pub sampled_region: AppearanceSampleRegionV1,
    pub sampled_color_srgb8: [u8; 4],
    pub sampled_color_family: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub swatch_distance: Option<f32>,
    pub alpha: AppearanceAlphaSummaryV1,
    pub expected: AppearanceTargetExpectationV1,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AppearanceSampleRegionV1 {
    pub kind: String,
    pub sampled_pixels: u64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub bbox_css_px: Option<AppearanceRectV1>,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct AppearanceRectV1 {
    pub min_x: f32,
    pub min_y: f32,
    pub max_x: f32,
    pub max_y: f32,
    pub width: f32,
    pub height: f32,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AppearanceAlphaSummaryV1 {
    pub mode: String,
    pub base_color_alpha: f32,
    pub hidden: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AppearanceReasonV1 {
    pub code: String,
    pub severity: String,
    pub target_id: String,
    #[serde(default)]
    pub affected_handles: Vec<u64>,
    pub message: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AppearanceFixV1 {
    pub action: String,
    pub target_id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub target_handle: Option<u64>,
    pub help: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AppearanceArtifactsV1 {
    pub capture: AppearanceCaptureSummaryV1,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AppearanceCaptureSummaryV1 {
    pub schema: String,
    pub width: u32,
    pub height: u32,
    pub payload_fnv1a64: String,
}

impl AppearanceIntrospectionOptions {
    pub const fn summary() -> Self {
        Self {
            detail: false,
            background_rgba8: [0, 0, 0, 255],
            content_tolerance_rgba8: 2,
            active_variant: None,
            available_variants: Vec::new(),
        }
    }

    pub const fn detail() -> Self {
        Self {
            detail: true,
            background_rgba8: [0, 0, 0, 255],
            content_tolerance_rgba8: 2,
            active_variant: None,
            available_variants: Vec::new(),
        }
    }

    pub fn with_active_material_variant(mut self, variant: Option<String>) -> Self {
        self.active_variant = variant;
        self
    }

    pub fn with_available_material_variants(mut self, variants: Vec<String>) -> Self {
        self.available_variants = variants;
        self
    }

    pub fn with_background_rgba8(mut self, background_rgba8: [u8; 4]) -> Self {
        self.background_rgba8 = background_rgba8;
        self
    }

    pub const fn with_content_tolerance_rgba8(mut self, tolerance: u8) -> Self {
        self.content_tolerance_rgba8 = tolerance;
        self
    }

    pub const fn detail_enabled(&self) -> bool {
        self.detail
    }
}

impl Default for AppearanceIntrospectionOptions {
    fn default() -> Self {
        Self::summary()
    }
}

const fn is_false(value: &bool) -> bool {
    !*value
}