bevy_animation_graph_editor 0.10.0

Animation graph editor for the Bevy game engine
Documentation
use std::path::PathBuf;

use crate::ui::generic_widgets::tree::{Tree, TreeInner, TreeRenderer};

impl<T> Tree<PathBuf, (PathBuf, T)> {
    pub fn from_paths(mut paths: Vec<(PathBuf, T)>) -> Self {
        let mut tree = Tree(Vec::new());
        paths.sort_by_key(|(p, _)| p.clone());

        for (path, val) in paths {
            insert_to_tree(&mut tree, path.clone(), val, path);
        }

        tree
    }
}

fn insert_to_subtrees<T>(
    subtrees: &mut Vec<TreeInner<PathBuf, (PathBuf, T)>>,
    path: PathBuf,
    value: T,
    remaining_path: PathBuf,
) -> Option<()> {
    if remaining_path.components().count() == 1 {
        subtrees.push(TreeInner::Leaf((path, value)));
        return Some(());
    }

    for subtree in subtrees.iter_mut() {
        let p = match subtree {
            TreeInner::Leaf(_) => continue,
            TreeInner::Node(p, _) => p.clone(),
        };
        if p.file_name().map(|s| s.to_string_lossy().to_string()) == first(remaining_path.clone()) {
            return insert_to_inner(subtree, path, value, without_first(remaining_path));
        }
    }

    let mut new_subtree = TreeInner::Node(
        up_to_including(path.clone(), first(remaining_path.clone()).unwrap()),
        vec![],
    );
    let result = insert_to_inner(&mut new_subtree, path, value, without_first(remaining_path));
    subtrees.push(new_subtree);
    result
}

fn insert_to_tree<T>(
    tree: &mut Tree<PathBuf, (PathBuf, T)>,
    path: PathBuf,
    value: T,
    remaining_path: PathBuf,
) -> Option<()> {
    insert_to_subtrees(&mut tree.0, path, value, remaining_path)
}

fn insert_to_inner<T>(
    tree: &mut TreeInner<PathBuf, (PathBuf, T)>,
    path: PathBuf,
    value: T,
    remaining_path: PathBuf,
) -> Option<()> {
    match tree {
        TreeInner::Leaf(_) => None,
        TreeInner::Node(_, subtrees) => insert_to_subtrees(subtrees, path, value, remaining_path),
    }
}

fn first(path: PathBuf) -> Option<String> {
    path.components()
        .next()
        .map(|c| c.as_os_str().to_string_lossy().to_string())
}

fn without_first(path: PathBuf) -> PathBuf {
    let mut components = path.components();
    components.next();
    PathBuf::from_iter(components)
}

fn up_to_including(full_path: PathBuf, component: String) -> PathBuf {
    let mut components = Vec::new();
    for c in full_path.components() {
        components.push(c);
        if c.as_os_str().to_string_lossy() == component {
            break;
        }
    }

    PathBuf::from_iter(components.iter())
}

pub struct PathTreeRenderer<T> {
    #[allow(clippy::type_complexity)]
    pub is_selected: Box<dyn Fn(&PathBuf, &T) -> bool>,
}

#[derive(Default, Clone)]
pub struct PathTreeResult<T> {
    pub clicked: Option<T>,
}

impl<T: Clone> TreeRenderer<PathBuf, (PathBuf, T)> for PathTreeRenderer<T> {
    type Output = PathTreeResult<T>;

    fn render_inner(
        &self,
        data: &PathBuf,
        children: &[TreeInner<PathBuf, (PathBuf, T)>],
        result: &mut Self::Output,
        ui: &mut egui::Ui,
        render_child: impl Fn(
            &TreeInner<PathBuf, (PathBuf, T)>,
            &mut Self::Output,
            &mut egui::Ui,
        ) -> egui::Response,
    ) -> egui::Response {
        let label = data
            .components()
            .next_back()
            .map(|s| match s {
                std::path::Component::RootDir => "/".to_string(),
                std::path::Component::Prefix(prefix_component) => {
                    prefix_component.as_os_str().to_string_lossy().to_string()
                }
                std::path::Component::CurDir => ".".to_string(),
                std::path::Component::ParentDir => "..".to_string(),
                std::path::Component::Normal(os_str) => os_str.to_string_lossy().to_string(),
            })
            .unwrap_or("".into());

        let response = egui::CollapsingHeader::new(label)
            .default_open(true)
            .show(ui, |ui| {
                children
                    .iter()
                    .map(|c| render_child(c, result, ui))
                    .reduce(|l, r| l | r)
            });

        response.header_response.clicked();

        let mut r = response.header_response;
        if let Some(body_response) = response.body_returned.flatten() {
            r |= body_response;
        }

        r
    }

    fn render_leaf(
        &self,
        (path, value): &(PathBuf, T),
        result: &mut Self::Output,
        ui: &mut egui::Ui,
    ) -> egui::Response {
        let mut response = ui.selectable_label(
            (self.is_selected)(path, value),
            path.file_name()
                .map(|s| s.to_string_lossy().to_string())
                .unwrap_or("".into()),
        );
        if response.clicked() {
            response.mark_changed();
            result.clicked = Some(value.clone());
        }

        response
    }
}