nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
use crate::ecs::world::{Entity, World};
use winit::keyboard::KeyCode;

use super::{Command, CommandRegistry, OutputLine, format_entity};

pub struct ShellState<C> {
    pub context: C,
    pub visible: bool,
    pub input_buffer: String,
    pub history: Vec<String>,
    pub output: Vec<OutputLine>,
    pub history_index: Option<usize>,
    pub registry: CommandRegistry<C>,
    pub animation_progress: f32,
    pub scroll_to_bottom: bool,
    pub height: f32,
    pub scroll_offset: f32,
    pub dragging_resize: bool,
    pub drag_start_y: f32,
    pub drag_start_height: f32,
    pub(crate) pending_enter: bool,
    pub(crate) pending_up: bool,
    pub(crate) pending_down: bool,
    pub(crate) pending_escape: bool,
}

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

impl<C> ShellState<C> {
    pub fn new(context: C) -> Self {
        Self {
            context,
            visible: false,
            input_buffer: String::new(),
            history: Vec::new(),
            output: vec![OutputLine {
                text: "Type 'help' for available commands.".to_string(),
                is_command: false,
            }],
            history_index: None,
            registry: CommandRegistry::new(),
            animation_progress: 0.0,
            scroll_to_bottom: true,
            height: 100.0,
            scroll_offset: 0.0,
            dragging_resize: false,
            drag_start_y: 0.0,
            drag_start_height: 0.0,
            pending_enter: false,
            pending_up: false,
            pending_down: false,
            pending_escape: false,
        }
    }

    pub fn toggle(&mut self) {
        self.visible = !self.visible;
    }

    pub fn register_command(&mut self, command: Box<dyn Command<C>>) {
        self.registry.register(command);
    }

    pub fn insert_text(&mut self, text: &str) {
        if !self.input_buffer.is_empty() && !self.input_buffer.ends_with(' ') {
            self.input_buffer.push(' ');
        }
        self.input_buffer.push_str(text);
    }

    pub fn insert_entity(&mut self, entity: Entity) {
        self.insert_text(&format_entity(entity));
    }

    pub fn handle_key(&mut self, key: KeyCode, pressed: bool) {
        if pressed {
            match key {
                KeyCode::Enter => self.pending_enter = true,
                KeyCode::ArrowUp => self.pending_up = true,
                KeyCode::ArrowDown => self.pending_down = true,
                KeyCode::Escape => self.pending_escape = true,
                KeyCode::Backspace => {
                    self.input_buffer.pop();
                }
                _ => {}
            }
        }
    }

    pub fn execute_command(&mut self, world: &mut World) {
        let input = self.input_buffer.trim().to_string();
        if input.is_empty() {
            return;
        }

        self.output.push(OutputLine {
            text: format!("> {}", input),
            is_command: true,
        });

        self.history.push(input.clone());
        self.history_index = None;

        let parts: Vec<&str> = input.split_whitespace().collect();
        if parts.is_empty() {
            self.input_buffer.clear();
            return;
        }

        let command_name = parts[0];
        let args = &parts[1..];

        if command_name == "clear" {
            self.output.clear();
        } else if command_name == "help" {
            if args.is_empty() {
                let mut help_text = String::from("Commands:\n");
                help_text.push_str("  clear - Clear the console output\n");
                help_text.push_str("  help - List all available commands\n");

                let builtin_names = self.registry.builtin_command_names();
                if !builtin_names.is_empty() {
                    help_text.push_str("\nBuilt-in:\n");
                    for name in builtin_names {
                        if let Some(cmd) = self.registry.get(name) {
                            help_text.push_str(&format!("  {} - {}\n", name, cmd.description()));
                        }
                    }
                }

                let app_names = self.registry.app_command_names();
                if !app_names.is_empty() {
                    help_text.push_str("\nApp:\n");
                    for name in app_names {
                        if let Some(cmd) = self.registry.get(name) {
                            help_text.push_str(&format!("  {} - {}\n", name, cmd.description()));
                        }
                    }
                }

                self.output.push(OutputLine {
                    text: help_text,
                    is_command: false,
                });
            } else if let Some(cmd) = self.registry.get(args[0]) {
                self.output.push(OutputLine {
                    text: format!(
                        "{}\nUsage: {}\n{}",
                        cmd.name(),
                        cmd.usage(),
                        cmd.description()
                    ),
                    is_command: false,
                });
            } else {
                self.output.push(OutputLine {
                    text: format!("Unknown command: {}", args[0]),
                    is_command: false,
                });
            }
        } else if self.registry.contains(command_name) {
            if let Some(cmd) = self.registry.get(command_name) {
                let result = cmd.execute(args, world, &mut self.context);
                if !result.is_empty() {
                    self.output.push(OutputLine {
                        text: result,
                        is_command: false,
                    });
                }
            }
        } else {
            self.output.push(OutputLine {
                text: format!("Unknown command: {}", command_name),
                is_command: false,
            });
        }

        self.input_buffer.clear();
        self.scroll_to_bottom = true;
    }

    pub fn history_up(&mut self) {
        if self.history.is_empty() {
            return;
        }

        match self.history_index {
            None => {
                self.history_index = Some(self.history.len() - 1);
            }
            Some(index) if index > 0 => {
                self.history_index = Some(index - 1);
            }
            _ => {}
        }

        if let Some(index) = self.history_index {
            self.input_buffer = self.history[index].clone();
        }
    }

    pub fn history_down(&mut self) {
        match self.history_index {
            Some(index) if index < self.history.len() - 1 => {
                self.history_index = Some(index + 1);
                self.input_buffer = self.history[index + 1].clone();
            }
            Some(_) => {
                self.history_index = None;
                self.input_buffer.clear();
            }
            None => {}
        }
    }

    pub fn update_animation(&mut self, delta_time: f32) {
        let target = if self.visible { 1.0 } else { 0.0 };
        let speed = 8.0;
        self.animation_progress += (target - self.animation_progress) * speed * delta_time;
        self.animation_progress = self.animation_progress.clamp(0.0, 1.0);
    }

    pub fn should_render(&self) -> bool {
        self.animation_progress > 0.001
    }
}