Skip to main content

mittens_engine/engine/ecs/component/
editor.rs

1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::IntentValue;
3use crate::engine::ecs::SignalEmitter;
4use crate::engine::ecs::component::Component;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum TransformGizmoCoordSpace {
8    Local,
9    World,
10}
11
12impl Default for TransformGizmoCoordSpace {
13    fn default() -> Self {
14        Self::World
15    }
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum EditorInteractionMode {
20    Select,
21    Cursor3d,
22    SelectAndCursor,
23}
24
25impl Default for EditorInteractionMode {
26    fn default() -> Self {
27        Self::Select
28    }
29}
30
31/// Marks an "editor root" subtree.
32///
33/// When a renderable under this subtree is clicked, the editor selection system can reattach
34/// the editor's gizmos (e.g. TransformGizmo) to the clicked target.
35#[derive(Debug, Clone, Copy)]
36pub struct EditorComponent {
37    /// Declaratively prefer this editor as the active workspace editor.
38    pub active: bool,
39
40    /// Runtime cache: resolved TransformGizmoComponent id within this editor subtree.
41    ///
42    /// Not serialized.
43    pub transform_gizmo: Option<ComponentId>,
44
45    /// Runtime: currently selected TransformComponent.
46    ///
47    /// Set by EditorSystem on DragStart. Read by InspectorSystem to drive panel content.
48    /// Not serialized.
49    pub selected: Option<ComponentId>,
50
51    /// Runtime editor interaction mode.
52    pub interaction_mode: EditorInteractionMode,
53
54    /// Coordinate space used for translation handles (arrows).
55    pub transform_gizmo_translation_space: TransformGizmoCoordSpace,
56
57    /// Coordinate space used for rotation handles (rings).
58    pub transform_gizmo_rotation_space: TransformGizmoCoordSpace,
59
60    /// Spawn world-tree and inspector panels automatically on init. Default: true.
61    pub spawn_panels: bool,
62
63    /// Include editor-owned runtime UI and editor wrappers when serializing a scene.
64    /// Default: false.
65    pub serialize_editor_panels: bool,
66
67    /// World-space position of the world-tree panel. Default: (-0.7, 1.6, -1.2).
68    pub world_panel_pos: (f32, f32, f32),
69
70    /// World-space position of the inspector panel.
71    /// If set to the same x as `world_panel_pos` (the default), the inspector is
72    /// auto-placed to the right of the world panel using `estimate_panel_width`.
73    pub inspector_panel_pos: (f32, f32, f32),
74
75    component: Option<ComponentId>,
76}
77
78impl Default for EditorComponent {
79    fn default() -> Self {
80        Self {
81            active: false,
82            transform_gizmo: None,
83            selected: None,
84            interaction_mode: EditorInteractionMode::Select,
85            // Default to the common editor expectation: translate in World, rotate in Local.
86            transform_gizmo_translation_space: TransformGizmoCoordSpace::World,
87            transform_gizmo_rotation_space: TransformGizmoCoordSpace::Local,
88            spawn_panels: true,
89            serialize_editor_panels: false,
90            world_panel_pos: (-0.7, 1.6, -1.2),
91            // Same x as world_panel_pos intentionally — InspectorSystem::setup_panels_for_editor
92            // detects this and auto-places the inspector to the right of the world panel using
93            // LayoutSystem::estimate_panel_width + PANEL_GAP.
94            inspector_panel_pos: (-0.7, 1.6, -1.2),
95            component: None,
96        }
97    }
98}
99
100impl EditorComponent {
101    pub fn new() -> Self {
102        Self::default()
103    }
104
105    pub fn with_active(mut self, active: bool) -> Self {
106        self.active = active;
107        self
108    }
109
110    pub fn with_transform_gizmo_translation_space(
111        mut self,
112        space: TransformGizmoCoordSpace,
113    ) -> Self {
114        self.transform_gizmo_translation_space = space;
115        self
116    }
117
118    pub fn with_transform_gizmo_rotation_space(mut self, space: TransformGizmoCoordSpace) -> Self {
119        self.transform_gizmo_rotation_space = space;
120        self
121    }
122
123    pub fn with_interaction_mode(mut self, mode: EditorInteractionMode) -> Self {
124        self.interaction_mode = mode;
125        self
126    }
127
128    /// Suppress automatic panel spawning. Call as `.with_panels(false)`.
129    pub fn with_panels(mut self, enabled: bool) -> Self {
130        self.spawn_panels = enabled;
131        self
132    }
133
134    pub fn with_serialize_editor_panels(mut self, enabled: bool) -> Self {
135        self.serialize_editor_panels = enabled;
136        self
137    }
138
139    /// Override panel positions (world_panel, inspector_panel).
140    pub fn with_panel_positions(
141        mut self,
142        world_panel: (f32, f32, f32),
143        inspector_panel: (f32, f32, f32),
144    ) -> Self {
145        self.world_panel_pos = world_panel;
146        self.inspector_panel_pos = inspector_panel;
147        self
148    }
149
150    pub fn id(&self) -> Option<ComponentId> {
151        self.component
152    }
153}
154
155impl Component for EditorComponent {
156    fn set_id(&mut self, component: ComponentId) {
157        self.component = Some(component);
158    }
159
160    fn name(&self) -> &'static str {
161        "editor"
162    }
163
164    fn as_any(&self) -> &dyn std::any::Any {
165        self
166    }
167
168    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
169        self
170    }
171
172    fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId) {
173        emit.push_intent_now(
174            component,
175            IntentValue::RegisterEditor {
176                component_ids: vec![component],
177            },
178        );
179    }
180
181    fn to_mms_ast(
182        &self,
183        _world: &crate::engine::ecs::World,
184    ) -> crate::scripting::ast::ComponentExpression {
185        use crate::engine::ecs::component::ce_helpers::*;
186        let translation = match self.transform_gizmo_translation_space {
187            TransformGizmoCoordSpace::Local => "local",
188            TransformGizmoCoordSpace::World => "world",
189        };
190        let rotation = match self.transform_gizmo_rotation_space {
191            TransformGizmoCoordSpace::Local => "local",
192            TransformGizmoCoordSpace::World => "world",
193        };
194        let interaction_mode = match self.interaction_mode {
195            EditorInteractionMode::Select => "select",
196            EditorInteractionMode::Cursor3d => "cursor_3d",
197            EditorInteractionMode::SelectAndCursor => "select_cursor",
198        };
199        let mut expr = ce("Editor")
200            .with_call("interaction_mode", vec![s(interaction_mode)])
201            .with_call("translation_space", vec![s(translation)])
202            .with_call("rotation_space", vec![s(rotation)]);
203        if self.active {
204            expr = expr.with_call("active", vec![]);
205        }
206        expr
207    }
208}