bevy_animation_graph_editor 0.10.0

Animation graph editor for the Bevy game engine
Documentation
use bevy_animation_graph::core::{id::BoneId, skeleton::Skeleton};

use crate::ui::generic_widgets::uuid::UuidWidget;

pub struct BoneIdWidget<'a> {
    pub bone_id: &'a mut BoneId,
    pub id_hash: egui::Id,
    pub skeleton: Option<&'a Skeleton>,
}

impl<'a> BoneIdWidget<'a> {
    pub fn new_salted(bone_id: &'a mut BoneId, salt: impl std::hash::Hash) -> Self {
        Self {
            bone_id,
            id_hash: egui::Id::new(salt),
            skeleton: None,
        }
    }

    pub fn with_skeleton(mut self, skeleton: Option<&'a Skeleton>) -> Self {
        self.skeleton = skeleton;
        self
    }
}

impl<'a> egui::Widget for BoneIdWidget<'a> {
    fn ui(self, ui: &mut egui::Ui) -> egui::Response {
        ui.push_id(self.id_hash, |ui| {
            ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| {
                let mut response = ui
                    .horizontal(|ui| {
                        let picker_response =
                            self.skeleton.map(|skn| picker(ui, self.bone_id, skn));

                        let mut uuid = self.bone_id.id();
                        let mut response =
                            ui.add(UuidWidget::new_salted(&mut uuid, "bone id uuid"));
                        if let Some(picker_response) = picker_response {
                            response |= picker_response;
                        }
                        *self.bone_id = BoneId::from_uuid(uuid);

                        response
                    })
                    .inner;

                if let Some(skeleton) = self.skeleton
                    && let Some(bone_path) = skeleton.id_to_path(*self.bone_id)
                {
                    response |= ui.label(bone_path.to_slashed_string());
                } else {
                    response |= ui.label("No label available");
                }

                response
            })
            .inner
        })
        .inner
    }
}

fn picker(ui: &mut egui::Ui, bone_id: &mut BoneId, skeleton: &Skeleton) -> egui::Response {
    let mut changed = false;
    let mut popup_response = ui
        .menu_button("🔍", |ui| {
            for available_bone_id in skeleton.iter_bones() {
                let Some(available_bone_path) = skeleton.id_to_path(available_bone_id) else {
                    continue;
                };
                let label = available_bone_path.to_slashed_string();
                if ui.button(label).clicked() {
                    *bone_id = available_bone_id;
                    changed = true;
                }
            }
        })
        .response;

    if changed {
        popup_response.mark_changed();
    }

    popup_response
}

pub struct BoneIdReadonlyWidget<'a> {
    pub bone_id: &'a BoneId,
    pub id_hash: egui::Id,
    pub skeleton: Option<&'a Skeleton>,
}

impl<'a> BoneIdReadonlyWidget<'a> {
    pub fn new_salted(bone_id: &'a BoneId, salt: impl std::hash::Hash) -> Self {
        Self {
            bone_id,
            id_hash: egui::Id::new(salt),
            skeleton: None,
        }
    }

    pub fn with_skeleton(mut self, skeleton: Option<&'a Skeleton>) -> Self {
        self.skeleton = skeleton;
        self
    }
}

impl<'a> egui::Widget for BoneIdReadonlyWidget<'a> {
    fn ui(self, ui: &mut egui::Ui) -> egui::Response {
        ui.push_id(self.id_hash, |ui| {
            ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| {
                let mut response = ui.label(format!("{}", self.bone_id.id().hyphenated()));

                if let Some(skeleton) = self.skeleton
                    && let Some(bone_path) = skeleton.id_to_path(*self.bone_id)
                {
                    response |= ui.label(bone_path.to_slashed_string());
                } else {
                    response |= ui.label("No label available");
                }

                response
            })
            .inner
        })
        .inner
    }
}