use super::{ComponentInspector, InspectorContext};
use crate::editor::code_editor::CodeEditor;
use crate::prelude::*;
struct ScriptPreset {
name: &'static str,
description: &'static str,
source: &'static str,
}
const SCRIPT_PRESETS: &[ScriptPreset] = &[
ScriptPreset {
name: "Spin (Y-Axis)",
description: "Rotate continuously around the Y axis",
source: "rot_y = rot_y + 1.0 * dt;",
},
ScriptPreset {
name: "Spin (All Axes)",
description: "Rotate around all axes at different speeds",
source: "rot_x = rot_x + 0.5 * dt;\nrot_y = rot_y + 1.0 * dt;\nrot_z = rot_z + 0.3 * dt;",
},
ScriptPreset {
name: "Bob Up/Down",
description: "Move up and down in a sine wave pattern",
source: "let amplitude = 1.0;\nlet speed = 2.0;\npos_y = (time * speed).sin() * amplitude;",
},
ScriptPreset {
name: "Orbit (XZ Circle)",
description: "Move in a circle on the XZ plane",
source: "let speed = 1.0;\nlet radius = 3.0;\npos_x = (time * speed).cos() * radius;\npos_z = (time * speed).sin() * radius;",
},
ScriptPreset {
name: "Follow Mouse",
description: "Smoothly follow the mouse cursor position",
source: "let target_x = (mouse_x - 640.0) * 0.01;\nlet target_y = (360.0 - mouse_y) * 0.01;\npos_x = pos_x + (target_x - pos_x) * 5.0 * dt;\npos_y = pos_y + (target_y - pos_y) * 5.0 * dt;",
},
ScriptPreset {
name: "WASD Movement",
description: "Move with WASD keys",
source: "let speed = 5.0;\nif pressed_keys.contains(\"W\") { pos_z = pos_z - speed * dt; }\nif pressed_keys.contains(\"S\") { pos_z = pos_z + speed * dt; }\nif pressed_keys.contains(\"A\") { pos_x = pos_x - speed * dt; }\nif pressed_keys.contains(\"D\") { pos_x = pos_x + speed * dt; }",
},
ScriptPreset {
name: "Spawn on Space",
description: "Spawn a cube above this entity when Space is pressed",
source: "if just_pressed_keys.contains(\"SPACE\") {\n do_spawn_cube = true;\n spawn_cube_x = pos_x;\n spawn_cube_y = pos_y + 2.0;\n spawn_cube_z = pos_z;\n log(\"Spawned!\");\n}",
},
ScriptPreset {
name: "Pulse Scale",
description: "Pulse the scale up and down",
source: "let speed = 3.0;\nlet pulse = 1.0 + (time * speed).sin() * 0.3;\nscale_x = pulse;\nscale_y = pulse;\nscale_z = pulse;",
},
ScriptPreset {
name: "Hover",
description: "Gently hover up and down",
source: "let base_y = 0.0;\nlet amplitude = 0.5;\nlet speed = 2.0;\npos_y = base_y + (time * speed).sin() * amplitude;",
},
ScriptPreset {
name: "Look at Mouse",
description: "Rotate to face the mouse cursor direction",
source: "let dx = mouse_x - 640.0;\nrot_y = rot_y + dx * 0.001 * dt;",
},
];
#[derive(Default)]
pub struct ScriptInspector {
code_editor: CodeEditor,
pending_preset: Option<usize>,
}
impl ComponentInspector for ScriptInspector {
fn name(&self) -> &str {
"Script"
}
fn has_component(&self, world: &World, entity: Entity) -> bool {
world.entity_has_script(entity)
}
fn add_component(&self, world: &mut World, entity: Entity) {
world.add_components(entity, crate::ecs::world::SCRIPT);
world.set_script(
entity,
Script {
source: ScriptSource::Embedded {
source: String::new(),
},
enabled: false,
},
);
}
fn remove_component(&self, world: &mut World, entity: Entity) {
world.remove_script(entity);
}
fn ui(
&mut self,
world: &mut World,
entity: Entity,
ui: &mut egui::Ui,
_context: &mut InspectorContext,
) {
let Some(mut script) = world.get_script(entity).cloned() else {
return;
};
let is_file = matches!(script.source, ScriptSource::File { .. });
ui.horizontal(|ui| {
ui.label("Source Type");
if ui.selectable_label(is_file, "File").clicked() && !is_file {
script.source = ScriptSource::File {
path: String::new(),
};
}
if ui.selectable_label(!is_file, "Embedded").clicked() && is_file {
script.source = ScriptSource::Embedded {
source: String::new(),
};
}
});
ui.separator();
if !is_file {
ui.horizontal(|ui| {
ui.label("Presets");
ui.menu_button("Select preset...", |ui| {
for (index, preset) in SCRIPT_PRESETS.iter().enumerate() {
if ui
.button(preset.name)
.on_hover_text(preset.description)
.clicked()
{
self.pending_preset = Some(index);
ui.close();
}
}
});
});
if let Some(index) = self.pending_preset.take()
&& let Some(preset) = SCRIPT_PRESETS.get(index)
{
script.source = ScriptSource::Embedded {
source: preset.source.to_string(),
};
}
ui.separator();
}
match &mut script.source {
ScriptSource::File { path } => {
ui.horizontal(|ui| {
ui.label("Path");
ui.text_edit_singleline(path);
});
}
ScriptSource::Embedded { source } => {
ui.label("Script");
egui::ScrollArea::vertical()
.max_height(200.0)
.show(ui, |ui| {
self.code_editor.ui(ui, source);
});
}
}
ui.separator();
ui.collapsing("API Reference", |ui| {
ui.label("Transform (read/write):");
ui.monospace(" pos_x, pos_y, pos_z");
ui.monospace(" rot_x, rot_y, rot_z (euler radians)");
ui.monospace(" scale_x, scale_y, scale_z");
ui.add_space(4.0);
ui.label("Time & Input (read-only):");
ui.monospace(" time (accumulated seconds)");
ui.monospace(" dt, delta_time (frame delta)");
ui.monospace(" mouse_x, mouse_y");
ui.monospace(" pressed_keys, just_pressed_keys");
ui.monospace(" entity_id");
ui.add_space(4.0);
ui.label("Spawning:");
ui.monospace(" do_spawn_cube = true;");
ui.monospace(" spawn_cube_x/y/z = position;");
ui.monospace(" do_spawn_sphere = true;");
ui.monospace(" spawn_sphere_x/y/z = position;");
ui.add_space(4.0);
ui.label("Shared State:");
ui.monospace(" state[\"key\"] = value;");
ui.monospace(" let val = state[\"key\"];");
ui.add_space(4.0);
ui.label("Other:");
ui.monospace(" do_despawn = true;");
ui.monospace(" log(\"message\");");
});
world.set_script(entity, script);
}
}