Skip to main content

oxihuman_viewer/
state_machine_view.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Animation state machine view stub.
6
7/// State machine state kind.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum StateKind {
10    Entry,
11    Exit,
12    Any,
13    Normal,
14    SubStateMachine,
15}
16
17/// A state machine state node.
18#[derive(Debug, Clone)]
19pub struct SmState {
20    pub id: u32,
21    pub kind: StateKind,
22    pub label: String,
23    pub position: [f32; 2],
24    pub is_active: bool,
25}
26
27/// A state transition entry.
28#[derive(Debug, Clone)]
29pub struct SmTransition {
30    pub from_id: u32,
31    pub to_id: u32,
32    pub condition: String,
33}
34
35/// State machine view configuration.
36#[derive(Debug, Clone)]
37pub struct StateMachineView {
38    pub states: Vec<SmState>,
39    pub transitions: Vec<SmTransition>,
40    pub show_conditions: bool,
41    pub zoom: f32,
42    pub enabled: bool,
43}
44
45impl StateMachineView {
46    pub fn new() -> Self {
47        StateMachineView {
48            states: Vec::new(),
49            transitions: Vec::new(),
50            show_conditions: true,
51            zoom: 1.0,
52            enabled: true,
53        }
54    }
55}
56
57impl Default for StateMachineView {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63/// Create a new state machine view.
64pub fn new_state_machine_view() -> StateMachineView {
65    StateMachineView::new()
66}
67
68/// Add a state node.
69pub fn smv_add_state(view: &mut StateMachineView, state: SmState) {
70    view.states.push(state);
71}
72
73/// Add a transition.
74pub fn smv_add_transition(view: &mut StateMachineView, transition: SmTransition) {
75    view.transitions.push(transition);
76}
77
78/// Clear all states and transitions.
79pub fn smv_clear(view: &mut StateMachineView) {
80    view.states.clear();
81    view.transitions.clear();
82}
83
84/// Set zoom level.
85pub fn smv_set_zoom(view: &mut StateMachineView, zoom: f32) {
86    view.zoom = zoom.max(0.05);
87}
88
89/// Toggle condition label display.
90pub fn smv_show_conditions(view: &mut StateMachineView, show: bool) {
91    view.show_conditions = show;
92}
93
94/// Enable or disable.
95pub fn smv_set_enabled(view: &mut StateMachineView, enabled: bool) {
96    view.enabled = enabled;
97}
98
99/// Return state count.
100pub fn smv_state_count(view: &StateMachineView) -> usize {
101    view.states.len()
102}
103
104/// Return transition count.
105pub fn smv_transition_count(view: &StateMachineView) -> usize {
106    view.transitions.len()
107}
108
109/// Serialize to JSON-like string.
110pub fn smv_to_json(view: &StateMachineView) -> String {
111    format!(
112        r#"{{"state_count":{},"transition_count":{},"show_conditions":{},"zoom":{},"enabled":{}}}"#,
113        view.states.len(),
114        view.transitions.len(),
115        view.show_conditions,
116        view.zoom,
117        view.enabled
118    )
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    fn make_state(id: u32) -> SmState {
126        SmState {
127            id,
128            kind: StateKind::Normal,
129            label: format!("state_{id}"),
130            position: [0.0, 0.0],
131            is_active: false,
132        }
133    }
134
135    fn make_transition(from: u32, to: u32) -> SmTransition {
136        SmTransition {
137            from_id: from,
138            to_id: to,
139            condition: "speed > 0".to_string(),
140        }
141    }
142
143    #[test]
144    fn test_initial_empty() {
145        let v = new_state_machine_view();
146        assert_eq!(smv_state_count(&v), 0 /* no states initially */);
147    }
148
149    #[test]
150    fn test_add_state() {
151        let mut v = new_state_machine_view();
152        smv_add_state(&mut v, make_state(0));
153        assert_eq!(smv_state_count(&v), 1 /* one state after add */);
154    }
155
156    #[test]
157    fn test_add_transition() {
158        let mut v = new_state_machine_view();
159        smv_add_transition(&mut v, make_transition(0, 1));
160        assert_eq!(
161            smv_transition_count(&v),
162            1 /* one transition after add */
163        );
164    }
165
166    #[test]
167    fn test_clear() {
168        let mut v = new_state_machine_view();
169        smv_add_state(&mut v, make_state(0));
170        smv_add_transition(&mut v, make_transition(0, 1));
171        smv_clear(&mut v);
172        assert_eq!(smv_state_count(&v), 0 /* states cleared */);
173        assert_eq!(smv_transition_count(&v), 0 /* transitions cleared */);
174    }
175
176    #[test]
177    fn test_zoom_min() {
178        let mut v = new_state_machine_view();
179        smv_set_zoom(&mut v, 0.0);
180        assert!((v.zoom - 0.05).abs() < 1e-6 /* minimum zoom must be 0.05 */);
181    }
182
183    #[test]
184    fn test_show_conditions() {
185        let mut v = new_state_machine_view();
186        smv_show_conditions(&mut v, false);
187        assert!(!v.show_conditions /* conditions must be hidden */);
188    }
189
190    #[test]
191    fn test_set_enabled() {
192        let mut v = new_state_machine_view();
193        smv_set_enabled(&mut v, false);
194        assert!(!v.enabled /* must be disabled */);
195    }
196
197    #[test]
198    fn test_to_json_has_state_count() {
199        let v = new_state_machine_view();
200        let j = smv_to_json(&v);
201        assert!(j.contains("\"state_count\"") /* JSON must have state_count */);
202    }
203
204    #[test]
205    fn test_enabled_default() {
206        let v = new_state_machine_view();
207        assert!(v.enabled /* must be enabled by default */);
208    }
209}