Skip to main content

oxihuman_viewer/
material_graph_view.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Material node graph view.
6
7/// A node in the material graph.
8#[derive(Debug, Clone)]
9pub struct MaterialNode {
10    pub id: u32,
11    pub label: String,
12    pub x: f32,
13    pub y: f32,
14}
15
16/// State for the material graph view.
17#[derive(Debug, Clone)]
18pub struct MaterialGraphView {
19    pub nodes: Vec<MaterialNode>,
20    pub zoom: f32,
21    pub pan: [f32; 2],
22    pub enabled: bool,
23}
24
25/// Create a new material graph view.
26pub fn new_material_graph_view() -> MaterialGraphView {
27    MaterialGraphView {
28        nodes: Vec::new(),
29        zoom: 1.0,
30        pan: [0.0, 0.0],
31        enabled: true,
32    }
33}
34
35/// Add a node to the graph.
36pub fn mgv_add_node(v: &mut MaterialGraphView, id: u32, label: &str, x: f32, y: f32) {
37    v.nodes.push(MaterialNode {
38        id,
39        label: label.to_string(),
40        x,
41        y,
42    });
43}
44
45/// Remove a node by ID. Returns true if found.
46pub fn mgv_remove_node(v: &mut MaterialGraphView, id: u32) -> bool {
47    let before = v.nodes.len();
48    v.nodes.retain(|n| n.id != id);
49    v.nodes.len() < before
50}
51
52/// Set zoom level (clamped 0.1–10).
53pub fn mgv_set_zoom(v: &mut MaterialGraphView, zoom: f32) {
54    v.zoom = zoom.clamp(0.1, 10.0);
55}
56
57/// Set pan offset.
58pub fn mgv_set_pan(v: &mut MaterialGraphView, x: f32, y: f32) {
59    v.pan = [x, y];
60}
61
62/// Serialise to JSON.
63pub fn mgv_to_json(v: &MaterialGraphView) -> String {
64    format!(
65        r#"{{"node_count":{},"zoom":{:.2},"enabled":{}}}"#,
66        v.nodes.len(),
67        v.zoom,
68        v.enabled
69    )
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn default_empty() {
78        let v = new_material_graph_view();
79        assert!(v.nodes.is_empty() /* no nodes */);
80    }
81
82    #[test]
83    fn add_node_increases_count() {
84        let mut v = new_material_graph_view();
85        mgv_add_node(&mut v, 1, "PBR", 0.0, 0.0);
86        assert_eq!(v.nodes.len(), 1 /* one node */);
87    }
88
89    #[test]
90    fn remove_node_by_id() {
91        let mut v = new_material_graph_view();
92        mgv_add_node(&mut v, 1, "Tex", 0.0, 0.0);
93        mgv_add_node(&mut v, 2, "Mix", 10.0, 0.0);
94        assert!(mgv_remove_node(&mut v, 1) /* removed */);
95        assert_eq!(v.nodes.len(), 1 /* one remains */);
96    }
97
98    #[test]
99    fn remove_missing_returns_false() {
100        let mut v = new_material_graph_view();
101        assert!(!mgv_remove_node(&mut v, 99) /* not found */);
102    }
103
104    #[test]
105    fn set_zoom_clamps() {
106        let mut v = new_material_graph_view();
107        mgv_set_zoom(&mut v, 999.0);
108        assert!((v.zoom - 10.0).abs() < 1e-6 /* clamped to 10 */);
109    }
110
111    #[test]
112    fn pan_set_correctly() {
113        let mut v = new_material_graph_view();
114        mgv_set_pan(&mut v, 50.0, -30.0);
115        assert!((v.pan[0] - 50.0).abs() < 1e-6 /* pan x */);
116    }
117
118    #[test]
119    fn json_has_node_count() {
120        let v = new_material_graph_view();
121        assert!(mgv_to_json(&v).contains("node_count") /* json has field */);
122    }
123
124    #[test]
125    fn enabled_default() {
126        let v = new_material_graph_view();
127        assert!(v.enabled /* enabled */);
128    }
129}