gled-ui 2.34.0

gled's egui interface, shared by the desktop app and the browser
Documentation
//! The numbers in actions and feedback sources that pick a scene, an
//! animation or a setting, which a group of controls counts up with
//! ("Select 1–8 select scene n in the selected row").

use gled_model::asset::midi_controller::{
    MidiColorSource, MidiEffectParameter, MidiEffectTarget, MidiInputAction, MidiOutputBindingKind,
    MidiSceneTarget, MidiValueSource,
};

/// A number an action or source carries.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum Slot {
    /// The row of a grid scene, or of the scene in the selected column.
    Row,
    /// The column of a grid scene, or of the scene in the selected row.
    Column,
    /// A quick scene.
    Quick,
    /// An animation of a scene, as an effect target.
    Effect,
    /// An animation picked by a button.
    Animation,
    /// An animation's setting.
    Setting,
}

/// The numbers of `target` with their slots.
fn target_numbers(target: &mut MidiSceneTarget) -> Vec<(Slot, &mut u8)> {
    match target {
        MidiSceneTarget::Selected => Vec::new(),
        MidiSceneTarget::Quick { index } => vec![(Slot::Quick, index)],
        MidiSceneTarget::Grid { row, col } => vec![(Slot::Row, row), (Slot::Column, col)],
        MidiSceneTarget::Row { col } => vec![(Slot::Column, col)],
        MidiSceneTarget::Column { row } => vec![(Slot::Row, row)],
    }
}

fn effect_numbers(effect: &mut MidiEffectTarget) -> Vec<(Slot, &mut u8)> {
    match effect {
        MidiEffectTarget::Selected => Vec::new(),
        MidiEffectTarget::Index { index } => vec![(Slot::Effect, index)],
    }
}

/// The numbers of `action` with their slots, in sentence order.
fn action_numbers_mut(action: &mut MidiInputAction) -> Vec<(Slot, &mut u8)> {
    use MidiInputAction as A;
    match action {
        A::SelectScene { target }
        | A::ToggleSceneActive { target }
        | A::SetSceneActive { target }
        | A::FlashScene { target }
        | A::SetSceneOpacity { target }
        | A::SetSceneInputDimmer { target }
        | A::SetSceneBeatOffset { target }
        | A::SetSceneIgnoreMainDimmer { target }
        | A::SetSceneSetOffsetOnFlash { target } => target_numbers(target),
        A::SetSceneEffectSettingF32 {
            target,
            effect,
            setting_index,
        } => {
            let mut numbers = vec![(Slot::Setting, setting_index)];
            numbers.extend(effect_numbers(effect));
            numbers.extend(target_numbers(target));
            numbers
        }
        A::SetSceneEffectOpacity { target, effect }
        | A::SetSceneEffectColorShift { target, effect }
        | A::SetSceneEffectBeatProgression { target, effect }
        | A::SetSceneEffectBeatOffset { target, effect }
        | A::SetSceneEffectSpeedExponent { target, effect }
        | A::SetSceneEffectLayout { target, effect } => {
            let mut numbers = effect_numbers(effect);
            numbers.extend(target_numbers(target));
            numbers
        }
        A::SelectSceneEffect { index } => vec![(Slot::Animation, index)],
        A::Tap
        | A::SetMainDimmer
        | A::SetMainDimmerRelative { .. }
        | A::SetBlackout
        | A::ToggleBlackout
        | A::SetSpeedAdd
        | A::SetSpeedMultiply
        | A::SetSpeedRelative { .. }
        | A::SelectSceneDistributed
        | A::SelectSceneRelative { .. }
        | A::SelectSceneRow { .. }
        | A::SelectSceneColumn { .. }
        | A::SelectMode { .. }
        | A::SelectSceneEffectRelative { .. }
        | A::SelectSceneEffectDistributed => Vec::new(),
    }
}

/// The numbers of `source` held as bytes, with their slots.
fn source_numbers_mut(source: &mut MidiValueSource) -> Vec<(Slot, &mut u8)> {
    use MidiValueSource as S;
    match source {
        S::SceneOpacity { target }
        | S::SceneOpacityLive { target }
        | S::SceneInputDimmer { target }
        | S::SceneBeatOffset { target }
        | S::SceneIgnoreMainDimmer { target }
        | S::SceneSetOffsetOnFlash { target }
        | S::SceneActive { target }
        | S::SceneFlashed { target }
        | S::SceneSelected { target } => target_numbers(target),
        S::SceneEffectSettingF32 {
            target,
            effect,
            setting_index,
        } => {
            let mut numbers = vec![(Slot::Setting, setting_index)];
            numbers.extend(effect_numbers(effect));
            numbers.extend(target_numbers(target));
            numbers
        }
        S::SceneEffectOpacity { target, effect }
        | S::SceneEffectColorShift { target, effect }
        | S::SceneEffectBeatProgression { target, effect }
        | S::SceneEffectBeatOffset { target, effect }
        | S::SceneEffectSpeedExponent { target, effect }
        | S::SceneEffectLayout { target, effect } => {
            let mut numbers = effect_numbers(effect);
            numbers.extend(target_numbers(target));
            numbers
        }
        S::SelectedSceneEffectLive { effect, .. } => effect_numbers(effect),
        S::EffectSelected { index } => vec![(Slot::Animation, index)],
        S::SelectedSceneOpacity
        | S::SelectedSceneDistributed
        | S::SelectedEffectDistributed
        | S::SelectedSceneOpacityLive
        | S::SelectedSceneInputDimmer
        | S::SelectedSceneBeatOffset
        | S::SelectedSceneIgnoreMainDimmer
        | S::SelectedSceneSetOffsetOnFlash
        | S::MainDimmer
        | S::BeatFlankPulse { .. }
        | S::Blackout { .. }
        | S::ModeActive { .. } => Vec::new(),
    }
}

/// The numbers of `action` with their slots, in sentence order.
pub(crate) fn action_numbers(action: &MidiInputAction) -> Vec<(Slot, u8)> {
    let mut action = action.clone();
    action_numbers_mut(&mut action)
        .into_iter()
        .map(|(slot, number)| (slot, *number))
        .collect()
}

/// Sets the number in `slot` of `action`, if it has one.
pub(crate) fn set_action_number(action: &mut MidiInputAction, slot: Slot, value: u8) {
    for (number_slot, number) in action_numbers_mut(action) {
        if number_slot == slot {
            *number = value;
        }
    }
}

/// The numbers of `source` with their slots, in sentence order; a live
/// animation setting counts as its setting.
pub(crate) fn source_numbers(source: &MidiValueSource) -> Vec<(Slot, u8)> {
    let mut numbers = Vec::new();
    if let MidiValueSource::SelectedSceneEffectLive { parameter, .. } = source
        && let Some(setting) = setting_of(*parameter)
    {
        numbers.push((Slot::Setting, setting));
    }
    let mut source = source.clone();
    numbers.extend(
        source_numbers_mut(&mut source)
            .into_iter()
            .map(|(slot, number)| (slot, *number)),
    );
    numbers
}

/// Sets the number in `slot` of `source`, if it has one.
pub(crate) fn set_source_number(source: &mut MidiValueSource, slot: Slot, value: u8) {
    if let MidiValueSource::SelectedSceneEffectLive { parameter, .. } = source
        && slot == Slot::Setting
        && setting_of(*parameter).is_some()
        && let Some(setting) = MidiEffectParameter::setting(value)
    {
        *parameter = setting;
    }
    for (number_slot, number) in source_numbers_mut(source) {
        if number_slot == slot {
            *number = value;
        }
    }
}

fn setting_of(parameter: MidiEffectParameter) -> Option<u8> {
    (0..6).find(|&index| MidiEffectParameter::setting(index) == Some(parameter))
}

/// The numbers of an output: its source's, or the scene a pad color shows.
pub(crate) fn output_numbers(kind: &MidiOutputBindingKind) -> Vec<(Slot, u8)> {
    match kind {
        MidiOutputBindingKind::SceneColorValue(color) => {
            let MidiColorSource::SceneColor { target } = &color.source;
            let mut target = target.clone();
            target_numbers(&mut target)
                .into_iter()
                .map(|(slot, number)| (slot, *number))
                .collect()
        }
        kind => kind.source().map(source_numbers).unwrap_or_default(),
    }
}

/// Sets the number in `slot` of an output, if it has one.
pub(crate) fn set_output_number(kind: &mut MidiOutputBindingKind, slot: Slot, value: u8) {
    match kind {
        MidiOutputBindingKind::SceneColorValue(color) => {
            let MidiColorSource::SceneColor { target } = &mut color.source;
            for (number_slot, number) in target_numbers(target) {
                if number_slot == slot {
                    *number = value;
                }
            }
        }
        kind => {
            if let Some(source) = kind.source_mut() {
                set_source_number(source, slot, value);
            }
        }
    }
}

/// `action` with its numbers set to 0, to compare what two actions do
/// regardless of which scene or animation they pick.
pub(crate) fn action_shape(action: &MidiInputAction) -> MidiInputAction {
    let mut shape = action.clone();
    for (slot, _) in action_numbers(action) {
        set_action_number(&mut shape, slot, 0);
    }
    shape
}

/// `source` with its numbers set to 0.
pub(crate) fn source_shape(source: &MidiValueSource) -> MidiValueSource {
    let mut shape = source.clone();
    for (slot, _) in source_numbers(source) {
        set_source_number(&mut shape, slot, 0);
    }
    shape
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_setting_names_its_setting_effect_and_scene() {
        let action = MidiInputAction::SetSceneEffectSettingF32 {
            target: MidiSceneTarget::Grid { row: 1, col: 2 },
            effect: MidiEffectTarget::Index { index: 3 },
            setting_index: 4,
        };
        assert_eq!(
            action_numbers(&action),
            [
                (Slot::Setting, 4),
                (Slot::Effect, 3),
                (Slot::Row, 1),
                (Slot::Column, 2)
            ]
        );
    }

    #[test]
    fn actions_on_different_scenes_have_the_same_shape() {
        let flash = |col| MidiInputAction::FlashScene {
            target: MidiSceneTarget::Row { col },
        };
        assert_eq!(action_shape(&flash(1)), action_shape(&flash(5)));
        assert_ne!(
            action_shape(&flash(1)),
            action_shape(&MidiInputAction::FlashScene {
                target: MidiSceneTarget::Column { row: 1 }
            })
        );
    }

    #[test]
    fn a_live_setting_counts_as_its_setting() {
        let mut source = MidiValueSource::SelectedSceneEffectLive {
            effect: MidiEffectTarget::Selected,
            parameter: MidiEffectParameter::Setting1,
        };
        assert_eq!(source_numbers(&source), [(Slot::Setting, 1)]);
        set_source_number(&mut source, Slot::Setting, 3);
        assert_eq!(
            source,
            MidiValueSource::SelectedSceneEffectLive {
                effect: MidiEffectTarget::Selected,
                parameter: MidiEffectParameter::Setting3,
            }
        );
    }
}