Skip to main content

gled_model/asset/
project.rs

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    /// Remove scene instance at path and update path to the next scene instance
206    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    /// Drops groups that are not among `available`, e.g. after a new SVG.
229    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}