use crate::canvas::definition_drawing::mode_color;
use crate::fixture_editor::state::{CanvasMode, EditorState};
use egui::Color32;
use gled_model::collection::Collections;
use gled_model::patching::Patch;
use gled_model::patching::fixture::FixtureId;
use gled_model::patching::layout::LayoutRef;
use std::collections::BTreeMap;
pub(crate) fn chain_color(chain: usize) -> Color32 {
let hue = (chain as f32 * 0.618_034).fract();
egui::ecolor::Hsva::new(hue, 0.65, 1.0, 1.0).into()
}
pub(super) fn draw_order(patch: &Patch, mode: CanvasMode, drawn: LayoutRef) -> Vec<Vec<FixtureId>> {
let chains = patch
.topology
.chain_heads()
.into_iter()
.map(|head| patch.topology.chain_from(head));
if mode == CanvasMode::Topology {
return chains.collect();
}
let Some(layout) = patch.layout(drawn) else {
return vec![];
};
chains
.flat_map(|chain| {
let mut runs = vec![];
let mut run = vec![];
for id in chain {
if layout.contains(id) {
run.push(id);
} else if !run.is_empty() {
runs.push(std::mem::take(&mut run));
}
}
if !run.is_empty() {
runs.push(run);
}
runs
})
.collect()
}
pub(super) fn fixture_colors(
patch: &Patch,
state: &EditorState,
chains: &[Vec<FixtureId>],
mode: CanvasMode,
collections: &Collections,
) -> BTreeMap<FixtureId, Color32> {
let by_chain = mode == CanvasMode::Topology && state.selected_mode == 0;
let chain_index: BTreeMap<FixtureId, usize> = patch
.topology
.chain_heads()
.into_iter()
.enumerate()
.map(|(index, head)| (head, index))
.collect();
chains
.iter()
.flatten()
.map(|id| {
let resolved =
patch.fixture_mode(state.selection, state.selected_mode, *id, collections);
let chain = chain_index.get(&patch.topology.chain_head_of(*id));
let color = match (by_chain, resolved, chain) {
(true, Some(_), Some(chain)) => chain_color(*chain),
_ => mode_color(resolved),
};
(*id, color)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use gled_model::patching::fixture::{Fixture, LayoutPosition};
#[test]
fn draw_order_walks_chains_in_the_topology() {
let mut patch = Patch::default();
let a = patch.add_fixture(Fixture::new("a"), LayoutPosition::default());
let b = patch.add_fixture(Fixture::new("b"), LayoutPosition::default());
patch.topology.make_successor(a, b);
let order = draw_order(&patch, CanvasMode::Topology, LayoutRef::Topology);
assert_eq!(order, vec![vec![a, b]]);
}
#[test]
fn a_layout_leaves_out_what_it_does_not_hold() {
let mut patch = Patch::default();
let a = patch.add_fixture(Fixture::new("a"), LayoutPosition::default());
let b = patch.add_fixture(Fixture::new("b"), LayoutPosition::default());
patch.topology.make_successor(a, b);
let layout = LayoutRef::Layout(patch.add_layout());
patch.add_member(layout, a);
let order = draw_order(&patch, CanvasMode::Layout, layout);
assert_eq!(order, vec![vec![a]]);
}
#[test]
fn a_chain_is_cut_where_a_layout_skips_a_fixture() {
let mut patch = Patch::default();
let a = patch.add_fixture(Fixture::new("a"), LayoutPosition::default());
let b = patch.add_fixture(Fixture::new("b"), LayoutPosition::default());
let c = patch.add_fixture(Fixture::new("c"), LayoutPosition::default());
patch.topology.make_successor(a, b);
patch.topology.make_successor(b, c);
let layout = LayoutRef::Layout(patch.add_layout());
patch.add_member(layout, a);
patch.add_member(layout, c);
let order = draw_order(&patch, CanvasMode::Layout, layout);
assert_eq!(order, vec![vec![a], vec![c]]);
}
#[test]
fn neighbouring_chains_get_clearly_different_colours() {
let distance = |a: Color32, b: Color32| {
(a.r() as i32 - b.r() as i32).abs()
+ (a.g() as i32 - b.g() as i32).abs()
+ (a.b() as i32 - b.b() as i32).abs()
};
for chain in 0..40 {
assert!(
distance(chain_color(chain), chain_color(chain + 1)) > 60,
"chain {chain}"
);
}
}
}