1use crate::naming::unique_name;
2use crate::patching::fixture::{FixtureId, LayoutPosition};
3use emath::{Pos2, Rect, Vec2};
4use serde::{Deserialize, Deserializer, Serialize};
5use std::collections::BTreeMap;
6use std::path::PathBuf;
7
8pub const SCENE_SIZE: f32 = 2000.0;
12
13pub fn scene_bounds() -> Rect {
16 Rect::from_min_size(Pos2::ZERO, Vec2::splat(SCENE_SIZE))
17}
18
19#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
26pub struct LayoutMode {
27 pub name: String,
28 pub fixture_modes: BTreeMap<FixtureId, usize>,
29}
30
31impl LayoutMode {
32 pub const DEFAULT_NAME: &'static str = "Default";
33
34 pub fn default_mode() -> Self {
35 Self {
36 name: Self::DEFAULT_NAME.to_string(),
37 fixture_modes: BTreeMap::new(),
38 }
39 }
40
41 pub fn new(name: impl Into<String>) -> Self {
42 Self {
43 name: name.into(),
44 fixture_modes: BTreeMap::new(),
45 }
46 }
47}
48
49#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
54pub struct Layout {
55 pub name: String,
56 pub background: Option<PathBuf>,
59 pub members: BTreeMap<FixtureId, LayoutPosition>,
60 #[serde(deserialize_with = "at_least_the_default_mode")]
62 pub modes: Vec<LayoutMode>,
63}
64
65fn at_least_the_default_mode<'de, D: Deserializer<'de>>(
68 deserializer: D,
69) -> Result<Vec<LayoutMode>, D::Error> {
70 let mut modes = Vec::<LayoutMode>::deserialize(deserializer)?;
71 if modes.is_empty() {
72 modes.push(LayoutMode::default_mode());
73 }
74 Ok(modes)
75}
76
77impl Default for Layout {
78 fn default() -> Self {
79 Self::new("Layout")
80 }
81}
82
83impl Layout {
84 pub const DEFAULT_NAME: &'static str = "Default";
87
88 pub fn new(name: impl Into<String>) -> Self {
89 Self {
90 name: name.into(),
91 background: None,
92 members: BTreeMap::new(),
93 modes: vec![LayoutMode::default_mode()],
94 }
95 }
96
97 pub fn contains(&self, id: FixtureId) -> bool {
98 self.members.contains_key(&id)
99 }
100
101 pub fn mode(&self, index: usize) -> &LayoutMode {
103 self.modes.get(index).unwrap_or(&self.modes[0])
104 }
105
106 pub fn mode_mut(&mut self, index: usize) -> &mut LayoutMode {
107 let index = if index < self.modes.len() { index } else { 0 };
108 &mut self.modes[index]
109 }
110
111 pub fn add_mode(&mut self) -> usize {
113 let name = unique_name("Mode", self.modes.iter().map(|mode| mode.name.as_str()));
114 self.modes.push(LayoutMode::new(name));
115 self.modes.len() - 1
116 }
117
118 pub fn remove_mode(&mut self, index: usize) {
120 if index > 0 && index < self.modes.len() {
121 self.modes.remove(index);
122 }
123 }
124
125 pub fn forget(&mut self, id: FixtureId) {
126 self.members.remove(&id);
127 for mode in &mut self.modes {
128 mode.fixture_modes.remove(&id);
129 }
130 }
131}
132
133#[derive(
136 Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default,
137)]
138pub enum LayoutRef {
139 #[default]
140 Topology,
141 Layout(usize),
142}
143
144impl LayoutRef {
145 pub fn stack_index(self) -> usize {
149 match self {
150 LayoutRef::Topology => 0,
151 LayoutRef::Layout(index) => index + 1,
152 }
153 }
154}
155
156#[derive(
158 Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord,
159)]
160pub struct LayoutSelection {
161 pub layout: LayoutRef,
162 pub mode: usize,
163}
164
165impl LayoutSelection {
166 pub fn new(layout: LayoutRef, mode: usize) -> Self {
167 Self { layout, mode }
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn a_new_layout_has_only_the_default_mode() {
177 let layout = Layout::new("Stage");
178 assert_eq!(layout.modes.len(), 1);
179 assert_eq!(layout.modes[0].name, LayoutMode::DEFAULT_NAME);
180 assert!(layout.modes[0].fixture_modes.is_empty());
181 }
182
183 #[test]
184 fn the_default_mode_cannot_be_removed() {
185 let mut layout = Layout::new("Stage");
186 layout.add_mode();
187 layout.remove_mode(0);
188 assert_eq!(layout.modes.len(), 2);
189 layout.remove_mode(1);
190 assert_eq!(layout.modes.len(), 1);
191 }
192
193 #[test]
194 fn an_out_of_range_mode_falls_back_to_the_default() {
195 let layout = Layout::new("Stage");
196 assert_eq!(layout.mode(7).name, LayoutMode::DEFAULT_NAME);
197 }
198
199 #[test]
200 fn a_layout_saved_without_modes_gets_its_default_mode_back() {
201 let layout: Layout = serde_json::from_str(
202 r#"{"name": "Stage", "background": null, "members": {}, "modes": []}"#,
203 )
204 .expect("deserialize");
205 assert_eq!(layout.modes, [LayoutMode::default_mode()]);
206 assert_eq!(layout.mode(3).name, LayoutMode::DEFAULT_NAME);
207 }
208
209 #[test]
210 fn forgetting_a_fixture_clears_its_mode_assignments() {
211 let mut layout = Layout::new("Stage");
212 let id = FixtureId::new();
213 layout.members.insert(id, LayoutPosition::default());
214 let mode = layout.add_mode();
215 layout.mode_mut(mode).fixture_modes.insert(id, 1);
216
217 layout.forget(id);
218
219 assert!(!layout.contains(id));
220 assert!(layout.mode(mode).fixture_modes.is_empty());
221 }
222
223 #[test]
224 fn the_stack_index_is_the_position_in_the_layout_stack() {
225 let mut patch = crate::patching::Patch::default();
226 patch.add_layout();
227 patch.add_layout();
228
229 let refs = patch.layout_refs();
230
231 assert_eq!(refs.len(), 3);
232 for (index, layout) in refs.into_iter().enumerate() {
233 assert_eq!(layout.stack_index(), index, "{layout:?}");
234 }
235 }
236}