#[cfg(test)]
mod bench;
use std::collections::HashSet;
use gled_model::asset::project::Project;
use gled_protocol::action::{TriggerInputs, UiAction};
pub(crate) fn project_edits(
confirmed: &Option<Project>,
edited: &Option<Project>,
patch_applied: bool,
) -> Vec<UiAction> {
match (confirmed, edited) {
(Some(confirmed), Some(edited)) => edits(confirmed, edited, patch_applied),
(Some(_), None) => vec![UiAction::CloseProject],
(None, _) => Vec::new(),
}
}
fn edits(confirmed: &Project, edited: &Project, patch_applied: bool) -> Vec<UiAction> {
let Project {
palette,
patch,
grid_width,
grid_height,
grid_highlight,
scenes_instances_grid,
channel_overwrites,
output_routings,
artnet_config,
tap_input_events,
blackout_input_events,
blackout_hold_input_events,
half_input_events,
double_input_events,
midi_active_mappings,
main_dimmer,
audio_input_device,
artnet_control_config,
osc_config,
} = edited;
let mut actions = Vec::new();
if *palette != confirmed.palette {
actions.push(UiAction::SetProjectPalette(Box::new(palette.clone())));
}
if patch_applied && *patch != confirmed.patch {
actions.push(UiAction::SetProjectPatch(Box::new(patch.clone())));
}
if (*grid_width, *grid_height) != (confirmed.grid_width, confirmed.grid_height) {
actions.push(UiAction::SetGridSize(*grid_width, *grid_height));
}
if *grid_highlight != confirmed.grid_highlight {
actions.push(UiAction::SetGridHighlight(*grid_highlight));
}
if *channel_overwrites != confirmed.channel_overwrites {
actions.push(UiAction::SetProjectChannelOverwrites(
channel_overwrites.clone(),
));
}
if *output_routings != confirmed.output_routings {
actions.push(UiAction::SetProjectOutputRoutings(output_routings.clone()));
}
if *artnet_config != confirmed.artnet_config {
actions.push(UiAction::SetProjectArtnetConfig(Box::new(
artnet_config.clone(),
)));
}
let triggers = TriggerInputs {
tap: tap_input_events.clone(),
blackout: blackout_input_events.clone(),
blackout_hold: blackout_hold_input_events.clone(),
half: half_input_events.clone(),
double: double_input_events.clone(),
};
if triggers != TriggerInputs::of(confirmed) {
actions.push(UiAction::SetProjectTriggerInputs(Box::new(triggers)));
}
if *midi_active_mappings != confirmed.midi_active_mappings {
actions.push(UiAction::SetMidiActiveMappings(
midi_active_mappings.clone(),
));
}
if *main_dimmer != confirmed.main_dimmer {
actions.push(UiAction::SetMainDimmer(*main_dimmer));
}
if *audio_input_device != confirmed.audio_input_device {
actions.push(UiAction::SetAudioDevice(audio_input_device.clone()));
}
if *artnet_control_config != confirmed.artnet_control_config {
actions.push(UiAction::SetProjectArtnetControl(*artnet_control_config));
}
if *osc_config != confirmed.osc_config {
actions.push(UiAction::SetProjectOsc(*osc_config));
}
let locations: HashSet<_> = scenes_instances_grid
.keys()
.chain(confirmed.scenes_instances_grid.keys())
.copied()
.collect();
for location in locations {
let edited = scenes_instances_grid.get(&location);
if edited != confirmed.scenes_instances_grid.get(&location) {
actions.push(UiAction::ReplaceSceneInstance(
location,
edited.cloned().map(Box::new),
));
}
}
actions
}
#[cfg(test)]
mod tests {
use super::*;
use gled_model::{
asset::scene::{grid::GridLocation, instance::SceneInstance},
patching::fixture::{Fixture, LayoutPosition},
};
#[test]
fn unchanged_projects_need_no_actions() {
let project = Some(Project::default());
assert!(project_edits(&project, &project.clone(), true).is_empty());
}
#[test]
fn each_edit_becomes_one_action() {
let confirmed = Project::default();
let mut edited = confirmed.clone();
edited.main_dimmer = 0.5;
edited
.patch
.add_fixture(Fixture::new("bar"), LayoutPosition::default());
edited.blackout_hold_input_events.clear();
let location = GridLocation { row: 1, col: 2 };
edited.scenes_instances_grid.insert(
location,
SceneInstance::from_scene_id(Default::default(), &Default::default()),
);
let actions = project_edits(&Some(confirmed), &Some(edited), true);
assert_eq!(actions.len(), 4, "{actions:?}");
assert!(
actions
.iter()
.any(|action| matches!(action, UiAction::SetMainDimmer(dimmer) if *dimmer == 0.5))
);
assert!(
actions
.iter()
.any(|action| matches!(action, UiAction::SetProjectPatch(_)))
);
assert!(actions.iter().any(|action| matches!(action, UiAction::SetProjectTriggerInputs(inputs) if inputs.blackout_hold.is_empty())));
assert!(actions.iter().any(|action| matches!(action, UiAction::ReplaceSceneInstance(at, Some(_)) if *at == location)));
}
#[test]
fn the_patch_is_compared_only_after_apply() {
let confirmed = Project::default();
let mut edited = confirmed.clone();
edited
.patch
.add_fixture(Fixture::new("bar"), LayoutPosition::default());
let (confirmed, edited) = (Some(confirmed), Some(edited));
assert!(project_edits(&confirmed, &edited, false).is_empty());
assert!(matches!(
project_edits(&confirmed, &edited, true).as_slice(),
[UiAction::SetProjectPatch(_)]
));
}
#[test]
fn closing_the_project_is_an_action() {
let actions = project_edits(&Some(Project::default()), &None, false);
assert!(matches!(actions.as_slice(), [UiAction::CloseProject]));
}
}