bevy_animation_graph_editor 0.8.0

Animation graph editor for the Bevy game engine
Documentation
//! Actions are the way we mutate editor state. When UI code wants to change something, they should
//! do it through an editor action.
//!
//! This will pave the way for undo/redo support later on.

pub mod clip_preview;
pub mod event_tracks;
pub mod fsm;
pub mod graph;
pub mod ragdoll;
pub mod saving;
pub mod window;

use std::{any::Any, cmp::Ordering, collections::VecDeque, fmt::Display};

use bevy::{
    ecs::{
        resource::Resource,
        system::{Commands, In, IntoSystem, ResMut, SystemInput},
        world::World,
    },
    log::error,
};
use event_tracks::{EventTrackAction, handle_event_track_action};
use fsm::{FsmAction, handle_fsm_action};
use graph::{GraphAction, handle_graph_action};
use saving::{SaveAction, handle_save_action};
use window::WindowAction;

use crate::ui::native_views::EditorViewUiState;

use super::{UiState, core::ViewAction, windows::WindowId};

#[derive(Resource, Default)]
pub struct PendingActions {
    pub actions: PushQueue<EditorAction>,
}

#[derive(Default)]
pub struct ActionContext {
    /// Queue further actions.
    ///
    /// Be careful not to create an inifinite loop
    actions: PushQueue<EditorAction>,
}

/// A "push-only" queue
pub struct PushQueue<T>(VecDeque<T>);

impl<T> Default for PushQueue<T> {
    fn default() -> Self {
        Self(VecDeque::new())
    }
}

impl<T> PushQueue<T> {
    pub fn push(&mut self, item: T) {
        self.0.push_back(item);
    }
}

pub enum EditorAction {
    View(ViewAction),
    Save(SaveAction),
    EventTrack(EventTrackAction),
    Graph(GraphAction),
    Fsm(FsmAction),
    Dynamic(Box<dyn DynamicAction>),
}

pub fn handle_editor_action_queue(world: &mut World, actions: impl Iterator<Item = EditorAction>) {
    let mut ctx = ActionContext::default();
    for action in actions {
        handle_editor_action(world, action, &mut ctx);
    }

    while let Some(action) = ctx.actions.0.pop_front() {
        handle_editor_action(world, action, &mut ctx);
    }
}

pub fn handle_editor_action(world: &mut World, action: EditorAction, ctx: &mut ActionContext) {
    match action {
        EditorAction::View(action) => {
            if let Err(err) = world.run_system_cached_with(handle_view_action, action) {
                error!("Failed to apply view action: {:?}", err);
            }
        }
        EditorAction::Save(action) => handle_save_action(world, action),
        EditorAction::EventTrack(action) => handle_event_track_action(world, action),
        EditorAction::Graph(action) => handle_graph_action(world, action),
        EditorAction::Fsm(action) => handle_fsm_action(world, action),
        EditorAction::Dynamic(action) => action.handle(world, ctx),
    }
}

fn handle_view_action(
    In(view_action): In<ViewAction>,
    mut ui_state: ResMut<UiState>,
    mut commands: Commands,
) {
    match view_action {
        ViewAction::Close(index) => {
            let view_state = ui_state.views.remove(index);
            commands.entity(view_state.entity).despawn();

            if let Some(idx) = ui_state.active_view {
                match idx.cmp(&index) {
                    Ordering::Less => {}
                    Ordering::Equal => {
                        ui_state.active_view = None;
                    }
                    Ordering::Greater => {
                        ui_state.active_view = Some(idx - 1);
                    }
                }
            }
        }
        ViewAction::Select(index) => {
            ui_state.active_view = Some(index);
        }
        ViewAction::New(name) => {
            commands.queue(|world: &mut World| {
                let view_state = EditorViewUiState::empty(world, name);
                let mut ui_state = world.resource_mut::<UiState>();
                ui_state.new_native_view(view_state);
            });
        }
    }
}

pub fn process_actions_system(world: &mut World) {
    world.try_resource_scope::<PendingActions, ()>(|world, mut actions| {
        handle_editor_action_queue(world, actions.actions.0.drain(..));
    });
}

impl PushQueue<EditorAction> {
    pub fn window(&mut self, window: WindowId, event: impl Any + Send + Sync) {
        self.push(EditorAction::Dynamic(Box::new(WindowAction {
            target: window,
            action: Box::new(event),
        })))
    }

    pub fn dynamic(&mut self, action: impl DynamicAction) {
        self.push(EditorAction::Dynamic(Box::new(action)));
    }
}

pub fn run_handler<I, O, M, S>(
    world: &mut World,
    msg: impl Display + 'static,
) -> impl FnOnce(S, I::Inner<'_>) + '_
where
    I: SystemInput + 'static,
    O: 'static,
    S: IntoSystem<I, O, M> + 'static,
{
    move |closure, input| {
        let _ = world
            .run_system_cached_with(closure, input)
            .inspect_err(|err| error!("{}: {}", msg, err));
    }
}

pub trait DynamicAction: Send + Sync + 'static {
    fn handle(self: Box<Self>, world: &mut World, ctx: &mut ActionContext);
}