bevy_animation_graph_editor 0.9.0

Animation graph editor for the Bevy game engine
Documentation
use std::any::{Any, TypeId};

use bevy::platform::collections::{HashMap, HashSet};

#[derive(Hash, Clone, Copy)]
pub struct StyleModifiers(u64);

bitflags::bitflags! {
    impl StyleModifiers: u64 {
        const HOVERED  = 0b00000001;
        const SELECTED = 0b00000010;
    }
}

pub trait StyleObject: Default + Clone + Any + Send + Sync {
    fn merge(&self, other: &Self) -> Self;
    fn base() -> Self;
}

#[derive(Clone)]
pub struct StyleRule<T> {
    pub modifiers: StyleModifiers,
    pub classes: HashSet<String>,
    pub value: T,
}

impl<T> StyleRule<T> {
    pub fn val(value: T) -> Self {
        Self {
            modifiers: StyleModifiers::empty(),
            classes: HashSet::new(),
            value,
        }
    }

    pub fn with_modifiers(mut self, modifiers: StyleModifiers) -> Self {
        self.modifiers |= modifiers;
        self
    }

    pub fn with_class(mut self, class: impl Into<String>) -> Self {
        self.classes.insert(class.into());
        self
    }
}

trait DynCloneRule: Any + Send + Sync {
    fn clone_box(&self) -> Box<dyn DynCloneRule>;
}

impl<T: StyleObject + Clone> DynCloneRule for StyleRule<T> {
    fn clone_box(&self) -> Box<dyn DynCloneRule> {
        Box::new(self.clone())
    }
}

impl<T> StyleRule<T> {
    pub fn matches(&self, modifiers: StyleModifiers, classes: &HashSet<String>) -> bool {
        modifiers.contains(self.modifiers)
            && self.classes.iter().all(|class| classes.contains(class))
    }
}

pub struct DynStyleRule {
    value: Box<dyn DynCloneRule + Send + Sync>,
}

impl DynStyleRule {
    pub fn new<T>(rule: StyleRule<T>) -> Self
    where
        T: StyleObject + Clone + Send + Sync,
    {
        Self {
            value: Box::new(rule),
        }
    }

    pub fn get_static<T: StyleObject + Clone>(&self) -> Option<&StyleRule<T>> {
        let val: &dyn Any = self.value.as_ref();
        val.downcast_ref::<StyleRule<T>>()
    }
}

impl Clone for DynStyleRule {
    fn clone(&self) -> Self {
        Self {
            value: self.value.clone_box(),
        }
    }
}

#[derive(Default, Clone)]
pub struct StyleEngine {
    by_type: HashMap<TypeId, Vec<DynStyleRule>>,
}

impl StyleEngine {
    pub fn add_rule<T: StyleObject>(&mut self, rule: StyleRule<T>) -> &mut Self {
        let ty = TypeId::of::<T>();
        self.by_type
            .entry(ty)
            .or_default()
            .push(DynStyleRule::new(rule));
        self
    }

    pub fn evaluate<T: StyleObject>(
        &self,
        modifiers: StyleModifiers,
        classes: &HashSet<String>,
    ) -> T {
        let ty = TypeId::of::<T>();
        self.by_type
            .get(&ty)
            .map(|rules| {
                rules
                    .iter()
                    .filter_map(|dynrule| dynrule.get_static::<T>())
                    .filter(|rule| rule.matches(modifiers, classes))
                    .map(|rule| &rule.value)
                    .fold(T::base(), |acc: T, v| acc.merge(v))
            })
            .unwrap_or_else(|| T::base())
    }
}

pub fn rgb(r: u8, g: u8, b: u8) -> egui::Color32 {
    egui::Color32::from_rgb(r, g, b)
}

pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> egui::Color32 {
    egui::Color32::from_rgba_unmultiplied(r, g, b, a)
}

pub fn path_stroke(
    stroke: egui::Stroke,
    kind: egui::epaint::StrokeKind,
) -> egui::epaint::PathStroke {
    egui::epaint::PathStroke {
        width: stroke.width,
        color: egui::epaint::ColorMode::Solid(stroke.color),
        kind,
    }
}