mittens-engine 0.7.0

A Vulkan and OpenXR scene engine with ECS, reactive signals, and Meow Meow scripting
use crate::engine::ecs::ComponentId;
use crate::engine::ecs::component::Component;
use crate::engine::ecs::{IntentValue, SignalEmitter};

#[derive(Debug, Clone)]
pub struct TextInputComponent {
    pub text: String,
    pub caret: usize,
    pub focused: bool,
    pub read_only: bool,
    component: Option<ComponentId>,
}

impl TextInputComponent {
    pub fn new(text: impl Into<String>) -> Self {
        let text = text.into();
        let caret = text.chars().count();
        Self {
            text,
            caret,
            focused: false,
            read_only: false,
            component: None,
        }
    }

    pub fn set_text(&mut self, text: impl Into<String>) {
        self.text = text.into();
        self.clamp_caret();
    }

    pub fn clamp_caret(&mut self) {
        self.caret = self.caret.min(self.text.chars().count());
    }
}

impl Default for TextInputComponent {
    fn default() -> Self {
        Self::new("")
    }
}

impl Component for TextInputComponent {
    fn name(&self) -> &'static str {
        "text_input"
    }

    fn set_id(&mut self, id: ComponentId) {
        self.component = Some(id);
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
        self
    }

    fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId) {
        let _ = self.component;
        emit.push_intent_now(
            component,
            IntentValue::RegisterTextInput {
                component_id: component,
            },
        );
    }

    fn encode(&self) -> std::collections::HashMap<String, serde_json::Value> {
        let mut out = std::collections::HashMap::new();
        out.insert(
            "text".to_string(),
            serde_json::Value::String(self.text.clone()),
        );
        if self.read_only {
            out.insert("read_only".to_string(), serde_json::Value::Bool(true));
        }
        out
    }

    fn decode(
        &mut self,
        data: &std::collections::HashMap<String, serde_json::Value>,
    ) -> Result<(), String> {
        if let Some(serde_json::Value::String(text)) = data.get("text") {
            self.text = text.clone();
        }
        if let Some(serde_json::Value::Bool(read_only)) = data.get("read_only") {
            self.read_only = *read_only;
        }
        self.clamp_caret();
        Ok(())
    }

    fn to_mms_ast(
        &self,
        _world: &crate::engine::ecs::World,
    ) -> crate::scripting::ast::ComponentExpression {
        use crate::engine::ecs::component::ce_helpers::*;
        use crate::scripting::ast::{Expression, Statement};

        let mut node = ce("TextInput");
        if !self.text.is_empty() {
            node.body
                .statements
                .push(Statement::Expression(Expression::String(self.text.clone())));
        }
        if self.read_only {
            node = node.with_call("read_only", vec![b(true)]);
        }
        node
    }
}

#[derive(Debug, Clone, Copy)]
pub struct TextInputGlyphHitComponent {
    pub text_input_root: ComponentId,
    pub text_target: ComponentId,
    pub char_index: usize,
}

impl Component for TextInputGlyphHitComponent {
    fn name(&self) -> &'static str {
        "text_input_glyph_hit"
    }

    fn set_id(&mut self, _id: ComponentId) {}

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
        self
    }
}