use std::any::TypeId;
use bevy_ecs::{component::ComponentId, system::System, world::World};
use bevy_render::color::Color;
use super::system_style::{color_to_hex, system_to_style, SystemStyle};
#[derive(Default, Clone, Copy)]
pub enum RankDir {
TopDown,
#[default]
LeftRight,
}
impl RankDir {
pub(crate) fn as_dot(&self) -> &'static str {
match self {
RankDir::TopDown => "TD",
RankDir::LeftRight => "LR",
}
}
}
#[derive(Default, Clone, Copy)]
pub enum EdgeStyle {
None,
Line,
Polyline,
Curved,
Ortho,
#[default]
Spline,
}
impl EdgeStyle {
pub fn as_dot(&self) -> &'static str {
match self {
EdgeStyle::None => "none",
EdgeStyle::Line => "line",
EdgeStyle::Polyline => "polyline",
EdgeStyle::Curved => "curved",
EdgeStyle::Ortho => "ortho",
EdgeStyle::Spline => "spline",
}
}
}
#[derive(Clone)]
pub struct Style {
pub schedule_rankdir: RankDir,
pub edge_style: EdgeStyle,
pub fontname: String,
pub color_background: String,
pub color_set: String,
pub color_set_label: String,
pub color_set_border: String,
pub color_edge: Vec<String>,
pub multiple_set_edge_color: String,
pub ambiguity_color: String,
pub ambiguity_bgcolor: String,
pub penwidth_edge: f32,
}
impl Style {
pub fn light() -> Style {
Style {
schedule_rankdir: RankDir::default(),
edge_style: EdgeStyle::default(),
fontname: "Helvetica".into(),
color_background: "white".into(),
color_set: "#00000008".into(),
color_set_border: "#00000040".into(),
color_set_label: "#000000".into(),
color_edge: vec![
"#eede00".into(),
"#881877".into(),
"#00b0cc".into(),
"#aa3a55".into(),
"#44d488".into(),
"#0090cc".into(),
"#ee9e44".into(),
"#663699".into(),
"#3363bb".into(),
"#22c2bb".into(),
"#99d955".into(),
],
multiple_set_edge_color: "blue".into(),
ambiguity_color: "#c93526".into(),
ambiguity_bgcolor: "#d3d3d3".into(),
penwidth_edge: 2.0,
}
}
pub fn dark_discord() -> Style {
Style {
schedule_rankdir: RankDir::default(),
edge_style: EdgeStyle::default(),
fontname: "Helvetica".into(),
color_background: "#35393f".into(),
color_set: "#ffffff44".into(),
color_set_border: "#ffffff50".into(),
color_set_label: "#ffffff".into(),
color_edge: vec![
"#eede00".into(),
"#881877".into(),
"#00b0cc".into(),
"#aa3a55".into(),
"#44d488".into(),
"#0090cc".into(),
"#ee9e44".into(),
"#663699".into(),
"#3363bb".into(),
"#22c2bb".into(),
"#99d955".into(),
],
ambiguity_color: "#c93526".into(),
ambiguity_bgcolor: "#c5daeb".into(),
multiple_set_edge_color: "blue".into(),
penwidth_edge: 2.0,
}
}
pub fn dark_github() -> Style {
Style {
schedule_rankdir: RankDir::default(),
edge_style: EdgeStyle::default(),
fontname: "Helvetica".into(),
color_background: "#0d1117".into(),
color_set: "#ffffff44".into(),
color_set_border: "#ffffff50".into(),
color_set_label: "#ffffff".into(),
color_edge: vec![
"#eede00".into(),
"#881877".into(),
"#00b0cc".into(),
"#aa3a55".into(),
"#44d488".into(),
"#0090cc".into(),
"#ee9e44".into(),
"#663699".into(),
"#3363bb".into(),
"#22c2bb".into(),
"#99d955".into(),
],
ambiguity_color: "#c93526".into(),
ambiguity_bgcolor: "#c6e6ff".into(),
multiple_set_edge_color: "blue".into(),
penwidth_edge: 2.0,
}
}
}
impl Default for Style {
fn default() -> Self {
Style::dark_github()
}
}
type IncludeAmbiguityFn = dyn Fn(
&dyn System<In = (), Out = ()>,
&dyn System<In = (), Out = ()>,
&[ComponentId],
&World,
) -> bool;
pub struct NodeStyle {
pub bg_color: String,
pub text_color: String,
pub border_color: String,
pub border_width: String,
}
type SystemMapperFn<T> = Box<dyn Fn(&dyn System<In = (), Out = ()>) -> T>;
pub struct Settings {
pub style: Style,
pub system_style: SystemMapperFn<SystemStyle>,
pub include_system: Option<SystemMapperFn<bool>>,
pub collapse_single_system_sets: bool,
pub ambiguity_enable: bool,
pub ambiguity_enable_on_world: bool,
pub include_ambiguity: Option<Box<IncludeAmbiguityFn>>,
pub prettify_system_names: bool,
}
impl Settings {
pub fn filter_name(mut self, filter: impl Fn(&str) -> bool + 'static) -> Self {
self.include_system = Some(Box::new(move |system| {
let name = system.name();
filter(&name)
}));
self
}
pub fn filter_in_crate(mut self, crate_: &str) -> Self {
let crate_ = crate_.to_owned();
self.include_system = Some(Box::new(move |system| {
let name = system.name();
name.starts_with(&crate_)
}));
self
}
pub fn filter_in_crates(mut self, crates: &[&str]) -> Self {
let crates: Vec<_> = crates.iter().map(|&s| s.to_owned()).collect();
self.include_system = Some(Box::new(move |system| {
let name = system.name();
crates.iter().any(|crate_| name.starts_with(crate_))
}));
self
}
pub fn get_system_style(&self, system: &dyn System<In = (), Out = ()>) -> NodeStyle {
let style = (self.system_style)(system);
let [h, s, l, _] = style.bg_color.as_hsla_f32();
let is_dark = l < 0.6;
let text_color = style.text_color.unwrap_or_else(|| {
if is_dark {
Color::hsl(h, s, 0.9)
} else {
Color::hsl(h, s, 0.1)
}
});
let border_color = style.border_color.unwrap_or_else(|| {
let offset = if is_dark { 0.2 } else { -0.2 };
let border_l = (l + offset).clamp(0.0, 1.0);
Color::hsl(h, s, border_l)
});
NodeStyle {
bg_color: color_to_hex(style.bg_color),
text_color: color_to_hex(text_color),
border_color: color_to_hex(border_color),
border_width: style.border_width.to_string(),
}
}
pub fn without_single_ambiguities_on<T: 'static>(mut self) -> Self {
self.include_ambiguity = Some(Box::new(move |_, _, conflicts, world| {
let &[conflict] = conflicts else { return true };
let Some(type_id) = world
.components()
.get_info(conflict)
.and_then(|info| info.type_id())
else {
return true;
};
type_id != TypeId::of::<T>()
}));
self
}
pub fn without_single_ambiguities_on_one_of(mut self, type_ids: &[TypeId]) -> Self {
let type_ids = type_ids.to_vec();
self.include_ambiguity = Some(Box::new(move |_, _, conflicts, world| {
let &[conflict] = conflicts else { return true };
let Some(type_id) = world
.components()
.get_info(conflict)
.and_then(|info| info.type_id())
else {
return true;
};
!type_ids.contains(&type_id)
}));
self
}
}
impl Default for Settings {
fn default() -> Self {
Self {
style: Style::default(),
system_style: Box::new(system_to_style),
include_system: None,
collapse_single_system_sets: false,
ambiguity_enable: true,
ambiguity_enable_on_world: false,
include_ambiguity: None,
prettify_system_names: true,
}
}
}