use crate::naming::unique_name;
use crate::patching::fixture::{FixtureId, LayoutPosition};
use emath::{Pos2, Rect, Vec2};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::BTreeMap;
use std::path::PathBuf;
pub const SCENE_SIZE: f32 = 2000.0;
pub fn scene_bounds() -> Rect {
Rect::from_min_size(Pos2::ZERO, Vec2::splat(SCENE_SIZE))
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
pub struct LayoutMode {
pub name: String,
pub fixture_modes: BTreeMap<FixtureId, usize>,
}
impl LayoutMode {
pub const DEFAULT_NAME: &'static str = "Default";
pub fn default_mode() -> Self {
Self {
name: Self::DEFAULT_NAME.to_string(),
fixture_modes: BTreeMap::new(),
}
}
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
fixture_modes: BTreeMap::new(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Layout {
pub name: String,
pub background: Option<PathBuf>,
pub members: BTreeMap<FixtureId, LayoutPosition>,
#[serde(deserialize_with = "at_least_the_default_mode")]
pub modes: Vec<LayoutMode>,
}
fn at_least_the_default_mode<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Vec<LayoutMode>, D::Error> {
let mut modes = Vec::<LayoutMode>::deserialize(deserializer)?;
if modes.is_empty() {
modes.push(LayoutMode::default_mode());
}
Ok(modes)
}
impl Default for Layout {
fn default() -> Self {
Self::new("Layout")
}
}
impl Layout {
pub const DEFAULT_NAME: &'static str = "Default";
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
background: None,
members: BTreeMap::new(),
modes: vec![LayoutMode::default_mode()],
}
}
pub fn contains(&self, id: FixtureId) -> bool {
self.members.contains_key(&id)
}
pub fn mode(&self, index: usize) -> &LayoutMode {
self.modes.get(index).unwrap_or(&self.modes[0])
}
pub fn mode_mut(&mut self, index: usize) -> &mut LayoutMode {
let index = if index < self.modes.len() { index } else { 0 };
&mut self.modes[index]
}
pub fn add_mode(&mut self) -> usize {
let name = unique_name("Mode", self.modes.iter().map(|mode| mode.name.as_str()));
self.modes.push(LayoutMode::new(name));
self.modes.len() - 1
}
pub fn remove_mode(&mut self, index: usize) {
if index > 0 && index < self.modes.len() {
self.modes.remove(index);
}
}
pub fn forget(&mut self, id: FixtureId) {
self.members.remove(&id);
for mode in &mut self.modes {
mode.fixture_modes.remove(&id);
}
}
}
#[derive(
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default,
)]
pub enum LayoutRef {
#[default]
Topology,
Layout(usize),
}
impl LayoutRef {
pub fn stack_index(self) -> usize {
match self {
LayoutRef::Topology => 0,
LayoutRef::Layout(index) => index + 1,
}
}
}
#[derive(
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord,
)]
pub struct LayoutSelection {
pub layout: LayoutRef,
pub mode: usize,
}
impl LayoutSelection {
pub fn new(layout: LayoutRef, mode: usize) -> Self {
Self { layout, mode }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_new_layout_has_only_the_default_mode() {
let layout = Layout::new("Stage");
assert_eq!(layout.modes.len(), 1);
assert_eq!(layout.modes[0].name, LayoutMode::DEFAULT_NAME);
assert!(layout.modes[0].fixture_modes.is_empty());
}
#[test]
fn the_default_mode_cannot_be_removed() {
let mut layout = Layout::new("Stage");
layout.add_mode();
layout.remove_mode(0);
assert_eq!(layout.modes.len(), 2);
layout.remove_mode(1);
assert_eq!(layout.modes.len(), 1);
}
#[test]
fn an_out_of_range_mode_falls_back_to_the_default() {
let layout = Layout::new("Stage");
assert_eq!(layout.mode(7).name, LayoutMode::DEFAULT_NAME);
}
#[test]
fn a_layout_saved_without_modes_gets_its_default_mode_back() {
let layout: Layout = serde_json::from_str(
r#"{"name": "Stage", "background": null, "members": {}, "modes": []}"#,
)
.expect("deserialize");
assert_eq!(layout.modes, [LayoutMode::default_mode()]);
assert_eq!(layout.mode(3).name, LayoutMode::DEFAULT_NAME);
}
#[test]
fn forgetting_a_fixture_clears_its_mode_assignments() {
let mut layout = Layout::new("Stage");
let id = FixtureId::new();
layout.members.insert(id, LayoutPosition::default());
let mode = layout.add_mode();
layout.mode_mut(mode).fixture_modes.insert(id, 1);
layout.forget(id);
assert!(!layout.contains(id));
assert!(layout.mode(mode).fixture_modes.is_empty());
}
#[test]
fn the_stack_index_is_the_position_in_the_layout_stack() {
let mut patch = crate::patching::Patch::default();
patch.add_layout();
patch.add_layout();
let refs = patch.layout_refs();
assert_eq!(refs.len(), 3);
for (index, layout) in refs.into_iter().enumerate() {
assert_eq!(layout.stack_index(), index, "{layout:?}");
}
}
}