nightshade 0.8.0

A cross-platform data-oriented game engine.
Documentation
use super::{ComponentInspector, InspectorContext, impl_simple_inspector};
use crate::ecs::prefab::resources::animation_cache_names;
use crate::prelude::*;

fn add_animation_player(world: &mut World, entity: Entity) {
    world.add_components(entity, ANIMATION_PLAYER);
    world.set_animation_player(entity, AnimationPlayer::default());
}

fn animation_ui(
    world: &mut World,
    entity: Entity,
    ui: &mut egui::Ui,
    _context: &mut InspectorContext,
) {
    let cached_names: Vec<String> = animation_cache_names(&world.resources.animation_cache)
        .cloned()
        .collect();

    let player_clip_names: Vec<String> = world
        .get_animation_player(entity)
        .map(|p| p.clips.iter().map(|c| c.name.clone()).collect())
        .unwrap_or_default();

    let mut clip_to_add: Option<String> = None;

    if let Some(player) = world.get_animation_player_mut(entity) {
        animation_player_controls_ui(ui, player);
    }

    ui.separator();

    animation_cache_ui(ui, &cached_names, &player_clip_names, &mut clip_to_add);

    if let Some(clip_name) = clip_to_add {
        let clip = world
            .resources
            .animation_cache
            .clips
            .get(&clip_name)
            .cloned();
        if let Some(clip) = clip
            && let Some(player) = world.get_animation_player_mut(entity)
        {
            let index = player.clips.len();
            player.clips.push(clip);
            player.play(index);
        }
    }
}

fn animation_player_controls_ui(ui: &mut egui::Ui, player: &mut AnimationPlayer) {
    ui.label(format!("Clips: {}", player.clips.len()));

    let mut clip_to_play = None;
    let mut clip_to_remove = None;

    if !player.clips.is_empty() {
        ui.horizontal(|ui| {
            ui.label("Current Clip:");
            egui::ComboBox::from_id_salt("animation_clip_selector")
                .selected_text(
                    player
                        .current_clip
                        .and_then(|index| player.clips.get(index))
                        .map(|clip| clip.name.as_str())
                        .unwrap_or("None"),
                )
                .show_ui(ui, |ui| {
                    for (index, clip) in player.clips.iter().enumerate() {
                        let is_selected = player.current_clip == Some(index);
                        if ui.selectable_label(is_selected, &clip.name).clicked() {
                            clip_to_play = Some(index);
                        }
                    }
                });
        });

        if let Some(clip_index) = player.current_clip
            && let Some(clip) = player.clips.get(clip_index)
        {
            ui.label(format!("Duration: {:.2}s", clip.duration));
            ui.label(format!("Channels: {}", clip.channels.len()));
        }

        ui.separator();

        ui.horizontal(|ui| {
            ui.label("Time:");
            let duration = player
                .current_clip
                .and_then(|index| player.clips.get(index))
                .map(|clip| clip.duration)
                .unwrap_or(1.0);
            ui.add(egui::Slider::new(&mut player.time, 0.0..=duration).suffix("s"));
        });

        ui.horizontal(|ui| {
            ui.label("Speed:");
            ui.add(
                egui::DragValue::new(&mut player.speed)
                    .speed(0.1)
                    .range(-5.0..=5.0),
            );
        });

        ui.horizontal(|ui| {
            ui.checkbox(&mut player.looping, "Looping");
        });

        ui.separator();

        ui.horizontal(|ui| {
            if player.playing {
                if ui.button("Pause").clicked() {
                    player.pause();
                }
            } else if ui.button("Play").clicked() {
                player.resume();
            }

            if ui.button("Stop").clicked() {
                player.stop();
            }

            if ui.button("Restart").clicked()
                && let Some(clip_index) = player.current_clip
            {
                player.play(clip_index);
            }
        });

        ui.label(if player.playing {
            "Status: Playing"
        } else {
            "Status: Paused"
        });

        ui.separator();

        ui.collapsing("Loaded Clips", |ui| {
            for (index, clip) in player.clips.iter().enumerate() {
                ui.horizontal(|ui| {
                    ui.label(format!("[{}] {}", index, clip.name));
                    if ui.small_button("X").clicked() {
                        clip_to_remove = Some(index);
                    }
                });
            }
        });
    }

    if let Some(index) = clip_to_play {
        player.play(index);
    }

    if let Some(index) = clip_to_remove {
        player.clips.remove(index);
        if player.current_clip == Some(index) {
            player.current_clip = None;
            player.playing = false;
        } else if let Some(current) = player.current_clip
            && current > index
        {
            player.current_clip = Some(current - 1);
        }
    }
}

fn animation_cache_ui(
    ui: &mut egui::Ui,
    cached_names: &[String],
    player_clip_names: &[String],
    clip_to_add: &mut Option<String>,
) {
    ui.collapsing(format!("Animation Cache ({})", cached_names.len()), |ui| {
        if cached_names.is_empty() {
            ui.label("No cached animations");
            ui.label("Load FBX files to populate the cache");
        } else {
            egui::ScrollArea::vertical()
                .max_height(200.0)
                .show(ui, |ui| {
                    for name in cached_names {
                        let already_loaded = player_clip_names.contains(name);
                        ui.horizontal(|ui| {
                            if already_loaded {
                                ui.add_enabled(false, egui::Button::new("+"))
                                    .on_disabled_hover_text("Already loaded");
                            } else if ui.small_button("+").clicked() {
                                *clip_to_add = Some(name.clone());
                            }
                            ui.label(name);
                        });
                    }
                });
        }
    });
}

impl_simple_inspector!(AnimationInspector, "Animation Player", entity_has_animation_player, remove_animation_player, animation_ui, custom_add => add_animation_player);