1pub mod io;
2pub mod scene_instance_path;
3
4use io::{ArtnetConfig, ArtnetControlConfig, ChannelOverwrites, OscConfig};
5
6use super::{
7 AssetTrait, midi_controller::MidiController, output_device::routing::OutputRoutings,
8 scene::instance::SceneInstance,
9};
10use crate::{
11 asset::{
12 group::{Group, Groups},
13 palette::Palette,
14 project::scene_instance_path::SceneInstanceUnion,
15 scene::{Scene, grid::GridLocation},
16 svg::Svg,
17 },
18 asset_id::AssetId,
19 collection::Collections,
20 input::{
21 event::{GamepadEvent, InputEvent},
22 key_code::KeyCode,
23 },
24 sound::{
25 AudioDeviceId,
26 trigger_ref::{CollectSoundTriggers, SoundTriggerSet},
27 },
28};
29use serde::{Deserialize, Serialize};
30use std::{
31 collections::{BTreeSet, HashMap},
32 sync::Arc,
33};
34
35#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
36#[serde(rename_all = "lowercase")]
37pub enum GridHighlight {
38 Row,
39 Column,
40 None,
41}
42
43#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
44#[serde(default)]
45pub struct Project {
46 pub palette: Option<Palette>,
47 pub groups: Groups,
48 #[serde(default = "Project::default_grid_width")]
49 pub grid_width: usize,
50 #[serde(default = "Project::default_grid_height")]
51 pub grid_height: usize,
52 #[serde(default = "Project::default_grid_highlight")]
53 pub grid_highlight: GridHighlight,
54 pub scenes_instances_grid: HashMap<GridLocation, SceneInstance>,
55 pub svg: Option<Svg>,
56 pub channel_overwrites: ChannelOverwrites,
57 pub output_routings: Arc<OutputRoutings>,
58 pub artnet_config: ArtnetConfig,
59 pub tap_input_events: BTreeSet<InputEvent>,
60 pub blackout_input_events: BTreeSet<InputEvent>,
61 pub blackout_hold_input_events: BTreeSet<InputEvent>,
62 pub half_input_events: BTreeSet<InputEvent>,
63 pub double_input_events: BTreeSet<InputEvent>,
64 pub midi_active_mappings: HashMap<String, Option<AssetId<MidiController>>>,
65 pub main_dimmer: f32,
66 pub audio_input_device: Option<AudioDeviceId>,
67 pub artnet_control_config: ArtnetControlConfig,
68 pub osc_config: OscConfig,
69}
70
71impl Default for Project {
72 fn default() -> Self {
73 Self {
74 palette: None,
75 groups: Groups::default(),
76 grid_width: Self::DEFAULT_GRID_WIDTH,
77 grid_height: Self::DEFAULT_GRID_HEIGHT,
78 grid_highlight: Self::DEFAULT_GRID_HIGHLIGHT,
79 scenes_instances_grid: HashMap::new(),
80 svg: Default::default(),
81 channel_overwrites: Default::default(),
82 output_routings: Default::default(),
83 artnet_config: Default::default(),
84 tap_input_events: std::iter::once(InputEvent::Key(KeyCode::T))
85 .chain(std::iter::once(InputEvent::Gamepad(GamepadEvent::Mode(0))))
86 .collect(),
87 blackout_input_events: std::iter::once(InputEvent::Key(KeyCode::B)).collect(),
88 blackout_hold_input_events: std::iter::once(InputEvent::Key(KeyCode::N))
89 .chain(std::iter::once(InputEvent::Gamepad(GamepadEvent::Start(0))))
90 .collect(),
91 half_input_events: std::iter::once(InputEvent::Key(KeyCode::Minus)).collect(),
92 double_input_events: std::iter::once(InputEvent::Key(KeyCode::Plus)).collect(),
93 midi_active_mappings: HashMap::new(),
94 main_dimmer: 1.0,
95 audio_input_device: None,
96 artnet_control_config: ArtnetControlConfig::default(),
97 osc_config: OscConfig::default(),
98 }
99 }
100}
101
102impl Project {
103 pub(crate) const MIN_GRID_WIDTH: usize = 2;
104 pub(crate) const MIN_GRID_HEIGHT: usize = 2;
105 pub(crate) const DEFAULT_GRID_WIDTH: usize = 8;
106 pub(crate) const DEFAULT_GRID_HEIGHT: usize = 6;
107 pub(crate) const DEFAULT_GRID_HIGHLIGHT: GridHighlight = GridHighlight::Row;
108
109 fn default_grid_width() -> usize {
110 Self::DEFAULT_GRID_WIDTH
111 }
112
113 fn default_grid_height() -> usize {
114 Self::DEFAULT_GRID_HEIGHT
115 }
116
117 fn default_grid_highlight() -> GridHighlight {
118 Self::DEFAULT_GRID_HIGHLIGHT
119 }
120
121 pub fn grid_width(&self) -> usize {
122 self.grid_width.max(Self::MIN_GRID_WIDTH)
123 }
124
125 pub fn grid_height(&self) -> usize {
126 self.grid_height.max(Self::MIN_GRID_HEIGHT)
127 }
128
129 pub fn quick_row_index(&self) -> usize {
130 self.grid_height() - 1
131 }
132
133 pub(crate) fn quick_col_index(&self) -> usize {
134 self.grid_width() - 1
135 }
136
137 pub fn set_grid_size(&mut self, width: usize, height: usize) {
138 self.grid_width = width.max(Self::MIN_GRID_WIDTH);
139 self.grid_height = height.max(Self::MIN_GRID_HEIGHT);
140
141 let max_width = self.grid_width();
142 let max_height = self.grid_height();
143 self.scenes_instances_grid
144 .retain(|location, _| location.col < max_width && location.row < max_height);
145 }
146
147 pub fn next_empty_grid_location(&self, start: GridLocation) -> GridLocation {
148 let grid_height = self.grid_height();
149 let grid_width = self.grid_width();
150 for row in 0..grid_height {
151 let row = (start.row + row) % grid_height;
152 for col in 0..grid_width {
153 let col = (start.col + col) % grid_width;
154 let location = GridLocation { row, col };
155 if !self.scenes_instances_grid.contains_key(&location) {
156 return location;
157 }
158 }
159 }
160 start
161 }
162
163 pub fn get_scenes_instance(&mut self, pos: &GridLocation) -> Option<&mut SceneInstance> {
164 self.scenes_instances_grid.get_mut(pos)
165 }
166 pub fn scenes_instances_grid_len(&self) -> usize {
167 self.scenes_instances_grid.len()
168 }
169
170 pub fn location_by_location_or_quick_index(
171 &mut self,
172 index_or_grid: SceneInstanceUnion,
173 ) -> Option<GridLocation> {
174 match index_or_grid {
175 SceneInstanceUnion::Selected => None,
176 SceneInstanceUnion::Grid(location) => Some(location),
177 SceneInstanceUnion::Quick(quick_scene_instance_index) => match self.grid_highlight {
178 GridHighlight::Row => {
179 if quick_scene_instance_index.index >= self.grid_width() {
180 return None;
181 }
182 Some(GridLocation {
183 row: self.quick_row_index(),
184 col: quick_scene_instance_index.index,
185 })
186 }
187 GridHighlight::Column => {
188 if quick_scene_instance_index.index >= self.grid_height() {
189 return None;
190 }
191 Some(GridLocation {
192 row: quick_scene_instance_index.index,
193 col: self.quick_col_index(),
194 })
195 }
196 GridHighlight::None => None,
197 },
198 }
199 }
200
201 pub fn all_scene_instance_locations(&self) -> impl Iterator<Item = &GridLocation> {
202 self.scenes_instances_grid.keys()
203 }
204
205 pub fn remove_scene_instance(&mut self, pos: GridLocation) -> Option<SceneInstance> {
207 self.scenes_instances_grid.remove(&pos)
208 }
209
210 pub fn add_scene(
211 &mut self,
212 pos: GridLocation,
213 scene_id: AssetId<Scene>,
214 collections: &Collections,
215 ) {
216 let scene_instance = SceneInstance::from_scene_id(scene_id, collections);
217 self.add_scene_instance(pos, scene_instance);
218 }
219
220 pub fn add_scene_instance(&mut self, pos: GridLocation, scene_instance: SceneInstance) {
221 if pos.col >= self.grid_width() || pos.row >= self.grid_height() {
222 return;
223 }
224 let scene_instances = &mut self.scenes_instances_grid;
225 scene_instances.insert(pos, scene_instance);
226 }
227
228 pub fn remove_nonexistant_groups(&mut self, available: &[Group]) {
230 self.groups.remove_nonexistant_groups(available);
231 for scene_instance in self.scenes_instances_grid.values_mut() {
232 scene_instance.remove_nonexistant_groups(available);
233 }
234 }
235
236 pub fn grid_location_from_continuous_index(
237 &self,
238 index: usize,
239 start: &GridLocation,
240 ) -> GridLocation {
241 let grid_width = self.grid_width();
242 let grid_height = self.grid_height();
243 let span = grid_width * grid_height;
244 let index = (index + start.col + start.row * grid_width) % span;
245 let row = index / grid_width;
246 let col = index % grid_width;
247 GridLocation { row, col }
248 }
249}
250
251impl AssetTrait for Project {
252 const DIR_NAME: &'static str = "projects";
253 const NAME: &'static str = "Project";
254 const SHOW_NAME_IF_SELECTED: bool = true;
255}
256
257impl CollectSoundTriggers for Project {
258 fn collect_sound_triggers(&self, triggers: &mut SoundTriggerSet) {
259 for scene_instance in self.scenes_instances_grid.values() {
260 scene_instance.collect_sound_triggers(triggers);
261 }
262 }
263}