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>,
57 pub members: BTreeMap<FixtureId, LayoutPosition>,
58 #[serde(deserialize_with = "at_least_the_default_mode")]
60 pub modes: Vec<LayoutMode>,
61}
62
63fn at_least_the_default_mode<'de, D: Deserializer<'de>>(
66 deserializer: D,
67) -> Result<Vec<LayoutMode>, D::Error> {
68 let mut modes = Vec::<LayoutMode>::deserialize(deserializer)?;
69 if modes.is_empty() {
70 modes.push(LayoutMode::default_mode());
71 }
72 Ok(modes)
73}
74
75impl Default for Layout {
76 fn default() -> Self {
77 Self::new("Layout")
78 }
79}
80
81impl Layout {
82 pub const DEFAULT_NAME: &'static str = "Default";
85
86 pub fn new(name: impl Into<String>) -> Self {
87 Self {
88 name: name.into(),
89 background: None,
90 members: BTreeMap::new(),
91 modes: vec![LayoutMode::default_mode()],
92 }
93 }
94
95 pub fn contains(&self, id: FixtureId) -> bool {
96 self.members.contains_key(&id)
97 }
98
99 pub fn mode(&self, index: usize) -> &LayoutMode {
101 self.modes.get(index).unwrap_or(&self.modes[0])
102 }
103
104 pub fn mode_mut(&mut self, index: usize) -> &mut LayoutMode {
105 let index = if index < self.modes.len() { index } else { 0 };
106 &mut self.modes[index]
107 }
108
109 pub fn add_mode(&mut self) -> usize {
111 let name = unique_name("Mode", self.modes.iter().map(|mode| mode.name.as_str()));
112 self.modes.push(LayoutMode::new(name));
113 self.modes.len() - 1
114 }
115
116 pub fn remove_mode(&mut self, index: usize) {
118 if index > 0 && index < self.modes.len() {
119 self.modes.remove(index);
120 }
121 }
122
123 pub fn forget(&mut self, id: FixtureId) {
124 self.members.remove(&id);
125 for mode in &mut self.modes {
126 mode.fixture_modes.remove(&id);
127 }
128 }
129}
130
131#[derive(
134 Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default,
135)]
136pub enum LayoutRef {
137 #[default]
138 Topology,
139 Layout(usize),
140}
141
142impl LayoutRef {
143 pub fn stack_index(self) -> usize {
147 match self {
148 LayoutRef::Topology => 0,
149 LayoutRef::Layout(index) => index + 1,
150 }
151 }
152}
153
154#[derive(
156 Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord,
157)]
158pub struct LayoutSelection {
159 pub layout: LayoutRef,
160 pub mode: usize,
161}
162
163impl LayoutSelection {
164 pub fn new(layout: LayoutRef, mode: usize) -> Self {
165 Self { layout, mode }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn a_new_layout_has_only_the_default_mode() {
175 let layout = Layout::new("Stage");
176 assert_eq!(layout.modes.len(), 1);
177 assert_eq!(layout.modes[0].name, LayoutMode::DEFAULT_NAME);
178 assert!(layout.modes[0].fixture_modes.is_empty());
179 }
180
181 #[test]
182 fn the_default_mode_cannot_be_removed() {
183 let mut layout = Layout::new("Stage");
184 layout.add_mode();
185 layout.remove_mode(0);
186 assert_eq!(layout.modes.len(), 2);
187 layout.remove_mode(1);
188 assert_eq!(layout.modes.len(), 1);
189 }
190
191 #[test]
192 fn an_out_of_range_mode_falls_back_to_the_default() {
193 let layout = Layout::new("Stage");
194 assert_eq!(layout.mode(7).name, LayoutMode::DEFAULT_NAME);
195 }
196
197 #[test]
198 fn a_layout_saved_without_modes_gets_its_default_mode_back() {
199 let layout: Layout = serde_json::from_str(
200 r#"{"name": "Stage", "background": null, "members": {}, "modes": []}"#,
201 )
202 .expect("deserialize");
203 assert_eq!(layout.modes, [LayoutMode::default_mode()]);
204 assert_eq!(layout.mode(3).name, LayoutMode::DEFAULT_NAME);
205 }
206
207 #[test]
208 fn forgetting_a_fixture_clears_its_mode_assignments() {
209 let mut layout = Layout::new("Stage");
210 let id = FixtureId::new();
211 layout.members.insert(id, LayoutPosition::default());
212 let mode = layout.add_mode();
213 layout.mode_mut(mode).fixture_modes.insert(id, 1);
214
215 layout.forget(id);
216
217 assert!(!layout.contains(id));
218 assert!(layout.mode(mode).fixture_modes.is_empty());
219 }
220
221 #[test]
222 fn the_stack_index_is_the_position_in_the_layout_stack() {
223 let mut patch = crate::patching::Patch::default();
224 patch.add_layout();
225 patch.add_layout();
226
227 let refs = patch.layout_refs();
228
229 assert_eq!(refs.len(), 3);
230 for (index, layout) in refs.into_iter().enumerate() {
231 assert_eq!(layout.stack_index(), index, "{layout:?}");
232 }
233 }
234}