mittens-engine 0.6.0

A Vulkan and OpenXR scene engine with ECS, reactive signals, and Meow Meow scripting
Documentation
use crate::engine::ecs::ComponentId;
use crate::engine::ecs::system::editor::context::EditorContextState;

pub const COLOR_PANEL_ROOT_SELECTOR: &str = "#color_panel_root";
pub const COLOR_PANEL_SELECTION_SELECTOR: &str = "#color_panel_selection";
pub const COLOR_SWATCH_PAYLOAD_NAME: &str = "color_swatch_payload";

const FREE_DRAW_LABEL: &str = "Free Draw";
const LINE_LABEL: &str = "Line";
const SPRAY_CAN_LABEL: &str = "Spray Can";
const FILL_LABEL: &str = "Fill";
const ERASE_LABEL: &str = "Erase";
const GRID_TOOL_LABEL: &str = "Grid Tool";

#[derive(Debug, Clone, PartialEq)]
pub struct PaintState {
    pub selected_asset: Option<PaintSelection>,
    pub selected_tool: PaintTool,
    pub selected_color: Option<[f32; 4]>,
    pub stroke: PaintStrokeMode,
}

impl Default for PaintState {
    fn default() -> Self {
        Self {
            selected_asset: None,
            selected_tool: PaintTool::Unknown(None),
            selected_color: None,
            stroke: PaintStrokeMode::Idle,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PaintSelection {
    pub item: Option<String>,
    pub component: Option<ComponentId>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PaintTool {
    FreeDraw,
    GridTool,
    Line,
    SprayCan,
    Fill,
    Erase,
    Unknown(Option<String>),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PaintStrokeMode {
    Idle,
    Dragging,
}

#[derive(Debug, Clone)]
pub enum PaintEvent {
    ActiveEditorChanged {
        editor: Option<ComponentId>,
    },
    AssetSelectionChanged {
        item: Option<String>,
        component: Option<ComponentId>,
    },
    ToolSelectionChanged {
        tool: PaintTool,
        item: Option<String>,
        component: Option<ComponentId>,
    },
    ColorSelectionChanged {
        item: Option<String>,
        component: Option<ComponentId>,
        rgba: Option<[f32; 4]>,
    },
    PanelFocusChanged {
        focused_panel: Option<ComponentId>,
    },
    WorldPanelSelectionChanged {
        component: Option<ComponentId>,
        editor: Option<ComponentId>,
    },
    EditorSelectionChanged {
        editor: ComponentId,
        component: Option<ComponentId>,
    },
    SceneClick {
        editor: ComponentId,
        renderable: ComponentId,
        hit_point: [f32; 3],
    },
    StrokeStarted {
        editor: ComponentId,
        renderable: ComponentId,
        hit_point: [f32; 3],
    },
    StrokeMoved {
        editor: ComponentId,
        renderable: ComponentId,
        hit_point: [f32; 3],
    },
    StrokeEnded {
        editor: ComponentId,
    },
}

pub fn reduce_paint_state(old: &PaintState, event: &PaintEvent) -> PaintState {
    let mut new = old.clone();
    match event {
        PaintEvent::AssetSelectionChanged { item, component } => {
            new.selected_asset = Some(PaintSelection {
                item: item.clone(),
                component: *component,
            });
        }
        PaintEvent::ToolSelectionChanged {
            tool,
            item,
            component,
        } => {
            let _ = (item, component);
            new.selected_tool = tool.clone();
        }
        PaintEvent::ColorSelectionChanged {
            item,
            component,
            rgba,
        } => {
            let _ = (item, component);
            new.selected_color = *rgba;
        }
        PaintEvent::ActiveEditorChanged { editor }
        | PaintEvent::PanelFocusChanged {
            focused_panel: editor,
        } => {
            let _ = editor;
        }
        PaintEvent::WorldPanelSelectionChanged { component, editor } => {
            let _ = (component, editor);
        }
        PaintEvent::EditorSelectionChanged { editor, component } => {
            let _ = (editor, component);
        }
        PaintEvent::SceneClick { editor, .. }
        | PaintEvent::StrokeStarted { editor, .. }
        | PaintEvent::StrokeMoved { editor, .. }
        | PaintEvent::StrokeEnded { editor } => {
            let _ = editor;
        }
    }

    match event {
        PaintEvent::StrokeStarted { .. } => {
            new.stroke = PaintStrokeMode::Dragging;
        }
        PaintEvent::StrokeEnded { .. } => {
            new.stroke = PaintStrokeMode::Idle;
        }
        _ => {}
    }

    new
}

pub fn paint_tool_from_item(item: Option<String>) -> PaintTool {
    match item.as_deref() {
        Some(FREE_DRAW_LABEL) => PaintTool::FreeDraw,
        Some(GRID_TOOL_LABEL) => PaintTool::GridTool,
        Some(LINE_LABEL) => PaintTool::Line,
        Some(SPRAY_CAN_LABEL) => PaintTool::SprayCan,
        Some(FILL_LABEL) => PaintTool::Fill,
        Some(ERASE_LABEL) => PaintTool::Erase,
        _ => PaintTool::Unknown(item),
    }
}

pub fn is_paint_workspace_focused(
    paint_panel_root: Option<ComponentId>,
    color_panel_root: Option<ComponentId>,
    editor_context: &EditorContextState,
) -> bool {
    let focused = paint_panel_root
        .is_some_and(|panel_root| editor_context.focused_panel == Some(panel_root))
        || color_panel_root
            .is_some_and(|panel_root| editor_context.focused_panel == Some(panel_root));
    eprintln!(
        "🎨🖌️ paint_debug is_paint_workspace_focused paint_panel_root={paint_panel_root:?} color_panel_root={color_panel_root:?} focused_panel={:?}{focused}",
        editor_context.focused_panel
    );
    focused
}

pub fn is_paint_panel_focused(
    paint_panel_root: Option<ComponentId>,
    editor_context: &EditorContextState,
) -> bool {
    let focused =
        paint_panel_root.is_some_and(|panel_root| editor_context.focused_panel == Some(panel_root));
    eprintln!(
        "🎨🖌️ paint_debug is_paint_panel_focused paint_panel_root={paint_panel_root:?} focused_panel={:?}{focused}",
        editor_context.focused_panel
    );
    focused
}

pub fn is_paint_active(
    paint_panel_root: Option<ComponentId>,
    color_panel_root: Option<ComponentId>,
    paint_state: &PaintState,
    editor_context: &EditorContextState,
) -> bool {
    let focused = is_paint_workspace_focused(paint_panel_root, color_panel_root, editor_context);
    let tool_ok = !matches!(paint_state.selected_tool, PaintTool::Unknown(_));
    let asset_ok = if paint_state.selected_tool == PaintTool::Erase {
        true
    } else {
        paint_state
            .selected_asset
            .as_ref()
            .and_then(|selection| selection.component)
            .is_some()
    };
    let result = focused && tool_ok && asset_ok;
    eprintln!(
        "🎨🖌️ paint_debug is_paint_active result={result} focused={focused} tool_ok={tool_ok} asset_ok={asset_ok} tool={:?} asset={:?} color={:?} panel={paint_panel_root:?} focused_panel={:?}",
        paint_state.selected_tool,
        paint_state.selected_asset,
        paint_state.selected_color,
        editor_context.focused_panel
    );
    result
}