nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
//! Developer console for runtime debugging and commands.
//!
//! An in-game terminal for executing commands, inspecting entities, and tweaking settings:
//!
//! - [`ShellState`]: Console state (visibility, input, history, commands)
//! - [`Command`]: Trait for custom commands
//! - [`CommandRegistry`]: Built-in and app-specific command storage
//! - [`shell_retained_ui`]: Render console with retained UI overlay
//! - [`shell_ui`]: Render console with egui (requires `egui` feature)
//!
//! # Opening the Console
//!
//! Toggle with `Alt+C`. Close with `Escape` or `Alt+C` again.
//!
//! # Basic Setup
//!
//! ```ignore
//! struct MyGame {
//!     shell: ShellState<()>,
//! }
//!
//! impl State for MyGame {
//!     fn on_start(&mut self, world: &mut World) {
//!         self.shell.register_builtin_commands();  // spawn_cube, grid, fov, etc.
//!     }
//!
//!     fn on_key_input(&mut self, world: &mut World, key: KeyCode, state: KeyState) {
//!         if self.shell.visible {
//!             self.shell.handle_key(key, state == KeyState::Pressed);
//!         }
//!         if state == KeyState::Pressed && key == KeyCode::AltLeft {
//!             // Track alt for toggle
//!         }
//!     }
//!
//!     fn on_char_input(&mut self, world: &mut World, ch: char) {
//!         if self.shell.visible && ch.is_ascii_graphic() || ch == ' ' {
//!             self.shell.input_buffer.push(ch);
//!         }
//!     }
//!
//!     fn run_systems(&mut self, world: &mut World) {
//!         self.shell.update_animation(world.resources.window.timing.delta_time);
//!         shell_retained_ui(&mut self.shell, world);
//!     }
//! }
//! ```
//!
//! # Creating Custom Commands
//!
//! Implement the [`Command`] trait:
//!
//! ```ignore
//! struct DebugCommand;
//!
//! impl Command<()> for DebugCommand {
//!     fn name(&self) -> &str { "debug" }
//!     fn description(&self) -> &str { "Toggle debug mode" }
//!     fn usage(&self) -> &str { "debug [on|off]" }
//!
//!     fn execute(&self, args: &[&str], world: &mut World, _ctx: &mut ()) -> String {
//!         if args.is_empty() {
//!             return format!("Debug: {}", world.resources.graphics.debug_mode);
//!         }
//!         match args[0] {
//!             "on" => world.resources.graphics.debug_mode = true,
//!             "off" => world.resources.graphics.debug_mode = false,
//!             _ => return "Usage: debug [on|off]".to_string(),
//!         }
//!         "OK".to_string()
//!     }
//! }
//!
//! // Register in on_start
//! shell.register_command(Box::new(DebugCommand));
//! ```
//!
//! # Using Context for State
//!
//! Pass game-specific state via the generic `C` type:
//!
//! ```ignore
//! struct GameContext {
//!     score: i32,
//!     paused: bool,
//! }
//!
//! struct ScoreCommand;
//!
//! impl Command<GameContext> for ScoreCommand {
//!     fn name(&self) -> &str { "score" }
//!     fn description(&self) -> &str { "Get or set score" }
//!     fn usage(&self) -> &str { "score [value]" }
//!
//!     fn execute(&self, args: &[&str], _world: &mut World, ctx: &mut GameContext) -> String {
//!         if let Some(value) = args.first() {
//!             ctx.score = value.parse().unwrap_or(0);
//!         }
//!         format!("Score: {}", ctx.score)
//!     }
//! }
//!
//! let mut shell: ShellState<GameContext> = ShellState::new(GameContext { score: 0, paused: false });
//! ```
//!
//! # Built-in Commands
//!
//! Register with [`ShellState::register_builtin_commands`]:
//!
//! | Command | Description |
//! |---------|-------------|
//! | `help [cmd]` | List commands or show command help |
//! | `clear` | Clear console output |
//! | `spawn_cube <x> <y> <z>` | Spawn a cube at position |
//! | `spawn_sphere <x> <y> <z>` | Spawn a sphere at position |
//! | `spawn_cylinder <x> <y> <z>` | Spawn a cylinder at position |
//! | `spawn_cone <x> <y> <z>` | Spawn a cone at position |
//! | `spawn_torus <x> <y> <z>` | Spawn a torus at position |
//! | `spawn_plane <x> <y> <z>` | Spawn a plane at position |
//! | `grid [on\|off]` | Toggle grid visibility |
//! | `atmosphere <type>` | Set atmosphere (none, sky, cloudy, space, nebula, sunset) |
//! | `fov [degrees]` | Get or set camera FOV |
//! | `timescale [value]` | Get or set time speed (1.0 = normal) |
//! | `fps` | Show current framerate |
//! | `pos` | Show camera position |
//! | `tp <x> <y> <z>` | Teleport camera to position |
//! | `unlit [on\|off]` | Toggle unlit rendering |
//! | `bloom [on\|off\|intensity]` | Toggle or set bloom |
//! | `rotate <entity> <x> <y> <z>` | Rotate entity by degrees |
//! | `scale <entity> <factor>` | Scale entity uniformly |
//! | `move <entity> <x> <y> <z>` | Move entity to position |
//! | `delete <entity>` | Delete an entity |
//! | `inspect <entity>` | Show entity information |
//! | `quit` | Exit the application |
//!
//! # Entity References
//!
//! Entities are referenced as `entity_<id>_<generation>`:
//!
//! ```ignore
//! > spawn_cube 0 1 0
//! Spawned cube at (0, 1, 0)
//!
//! > inspect entity_5_0
//! Entity: entity_5_0
//! Position: (0.00, 1.00, 0.00)
//! Scale: (1.00, 1.00, 1.00)
//!
//! > rotate entity_5_0 0 45 0
//! Rotated entity_5_0 by (0, 45, 0) degrees
//! ```
//!
//! # Inserting Entity References
//!
//! Insert entities from picking results:
//!
//! ```ignore
//! if let Some(result) = pick_closest_entity(world, &ray, &options) {
//!     shell.insert_entity(result.entity);  // Appends "entity_X_Y" to input
//! }
//! ```
//!
//! # Console Features
//!
//! - Command history: `Up`/`Down` arrows cycle through history
//! - Resizable: Drag the bottom edge to resize
//! - Scrollable: Output area scrolls automatically
//! - Smooth animation: Slides in/out when toggling
//!
//! [`ShellState`]: ShellState
//! [`Command`]: Command
//! [`CommandRegistry`]: CommandRegistry
//! [`shell_retained_ui`]: shell_retained_ui
//! [`shell_ui`]: shell_ui

mod commands;
mod egui_ui;
mod retained_ui;
mod state;

use crate::ecs::world::{Entity, World};
use std::collections::HashMap;

pub use self::egui_ui::*;
pub use self::retained_ui::*;
pub use self::state::*;

pub fn format_entity(entity: Entity) -> String {
    format!("entity_{}_{}", entity.id, entity.generation)
}

pub fn parse_entity(s: &str) -> Option<Entity> {
    let parts: Vec<&str> = s.split('_').collect();
    if parts.len() != 3 || parts[0] != "entity" {
        return None;
    }
    let id: u32 = parts[1].parse().ok()?;
    let generation: u32 = parts[2].parse().ok()?;
    Some(Entity { id, generation })
}

pub trait Command<C>: Send + Sync {
    fn name(&self) -> &str;
    fn description(&self) -> &str;
    fn usage(&self) -> &str;
    fn execute(&self, args: &[&str], world: &mut World, context: &mut C) -> String;
}

pub struct CommandRegistry<C> {
    builtin_commands: HashMap<String, Box<dyn Command<C>>>,
    app_commands: HashMap<String, Box<dyn Command<C>>>,
}

impl<C> Default for CommandRegistry<C> {
    fn default() -> Self {
        Self::new()
    }
}

impl<C> CommandRegistry<C> {
    pub fn new() -> Self {
        Self {
            builtin_commands: HashMap::new(),
            app_commands: HashMap::new(),
        }
    }

    pub fn register(&mut self, command: Box<dyn Command<C>>) {
        self.app_commands
            .insert(command.name().to_string(), command);
    }

    pub fn register_builtin(&mut self, command: Box<dyn Command<C>>) {
        self.builtin_commands
            .insert(command.name().to_string(), command);
    }

    pub fn get(&self, name: &str) -> Option<&dyn Command<C>> {
        self.builtin_commands
            .get(name)
            .or_else(|| self.app_commands.get(name))
            .map(|c| c.as_ref())
    }

    pub fn contains(&self, name: &str) -> bool {
        self.builtin_commands.contains_key(name) || self.app_commands.contains_key(name)
    }

    pub fn builtin_command_names(&self) -> Vec<&str> {
        let mut names: Vec<_> = self.builtin_commands.keys().map(|s| s.as_str()).collect();
        names.sort();
        names
    }

    pub fn app_command_names(&self) -> Vec<&str> {
        let mut names: Vec<_> = self.app_commands.keys().map(|s| s.as_str()).collect();
        names.sort();
        names
    }
}

pub struct OutputLine {
    pub text: String,
    pub is_command: bool,
}