gled-ui 2.34.0

gled's egui interface, shared by the desktop app and the browser
Documentation
//! What a rule's outputs are to the player: the control's light, its motor
//! fader, its strip's meter, its LED ring or its pad color; and new outputs
//! of these kinds for a control.

use gled_model::asset::midi_controller::{
    MidiColorSource, MidiInputAction, MidiMeterOutput, MidiOutputBindingKind,
    MidiSceneColorValueOutput, MidiSceneTarget, MidiValueOutput, MidiValueSource,
};

use super::build::Rule;
use super::controls::{Control, ControlKind};
use super::sentence::action_target;
use super::sources::{live_source, matching_source};

/// What an output is on the controller.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FeedbackRole {
    /// A button's LED.
    Light,
    /// A fader that moves by itself.
    MotorFader,
    /// The LED meter of a fader's strip.
    Meter,
    /// The LEDs around a knob.
    Ring,
    /// A pad lit in its scene's color.
    PadColor,
    /// Any other message.
    Value,
}

impl FeedbackRole {
    /// The words before what the output shows: "light shows …".
    pub(crate) fn label(self) -> &'static str {
        match self {
            Self::Light => "light shows",
            Self::MotorFader => "motor fader follows",
            Self::Meter => "meter shows",
            Self::Ring => "ring light shows",
            Self::PadColor => "pad shows the color of",
            Self::Value => "sends",
        }
    }

    /// The name in the "add feedback" menu.
    pub(crate) fn name(self) -> &'static str {
        match self {
            Self::Light => "Light",
            Self::MotorFader => "Motor fader",
            Self::Meter => "Level meter",
            Self::Ring => "Ring light",
            Self::PadColor => "Pad color",
            Self::Value => "Value",
        }
    }

    pub(crate) fn description(self) -> &'static str {
        match self {
            Self::Light => "The LED on this control",
            Self::MotorFader => "The fader moves by itself",
            Self::Meter => "The LED meter of this fader's strip",
            Self::Ring => "The LEDs around the knob",
            Self::PadColor => "The scene's color, through a color mapping",
            Self::Value => "Any value, to this control's message",
        }
    }
}

/// What the output `kind` of `rule` is on the controller.
pub(crate) fn feedback_role(
    controls: &[Control],
    rule: &Rule,
    kind: &MidiOutputBindingKind,
) -> FeedbackRole {
    let control = &controls[rule.control];
    match kind {
        MidiOutputBindingKind::Meter(_) => FeedbackRole::Meter,
        MidiOutputBindingKind::SceneColorValue(_) => FeedbackRole::PadColor,
        MidiOutputBindingKind::Value(value) => match value.status & 0xF0 {
            0xE0 => FeedbackRole::MotorFader,
            0xB0 if matches!(control.kind, ControlKind::Knob | ControlKind::Encoder) => {
                FeedbackRole::Ring
            }
            0x80 | 0x90 => FeedbackRole::Light,
            _ => FeedbackRole::Value,
        },
    }
}

/// The feedback a control of `kind` usually has, most usual first.
pub(crate) fn roles_for(kind: ControlKind) -> &'static [FeedbackRole] {
    match kind {
        ControlKind::Button => &[FeedbackRole::Light, FeedbackRole::Value],
        ControlKind::Pad => &[
            FeedbackRole::PadColor,
            FeedbackRole::Light,
            FeedbackRole::Value,
        ],
        ControlKind::Fader => &[
            FeedbackRole::MotorFader,
            FeedbackRole::Meter,
            FeedbackRole::Value,
        ],
        ControlKind::Knob | ControlKind::Encoder => &[FeedbackRole::Ring, FeedbackRole::Value],
    }
}

/// What feedback of `role` shows for `action` by default: a meter the live
/// value, the others the value the action changes.
pub(crate) fn source_for(
    role: FeedbackRole,
    action: Option<&MidiInputAction>,
) -> Option<MidiValueSource> {
    let source = matching_source(action?)?;
    Some(match role {
        FeedbackRole::Meter => live_source(source),
        _ => source,
    })
}

/// A new output of `role` on `control` showing `source`; a pad color shows
/// `target` through the color mapping `mapping_name`.
pub(crate) fn new_feedback(
    control: &Control,
    role: FeedbackRole,
    source: MidiValueSource,
    target: MidiSceneTarget,
    mapping_name: &str,
) -> MidiOutputBindingKind {
    let channel = control.trigger.status & 0x0F;
    let value = |status: u8, data1: u8| {
        MidiOutputBindingKind::Value(MidiValueOutput {
            status,
            data1,
            min: 0,
            max: 127,
            active_value: 127,
            source: source.clone(),
        })
    };
    match role {
        FeedbackRole::Light | FeedbackRole::Value => value(0x90 | channel, control.trigger.data1),
        FeedbackRole::MotorFader => value(0xE0 | channel, 0),
        FeedbackRole::Ring => value(0xB0 | channel, control.trigger.data1),
        FeedbackRole::Meter => MidiOutputBindingKind::Meter(MidiMeterOutput {
            strip: channel & 0x07,
            source: source.clone(),
        }),
        FeedbackRole::PadColor => {
            MidiOutputBindingKind::SceneColorValue(MidiSceneColorValueOutput {
                source: MidiColorSource::SceneColor { target },
                mapping_name: mapping_name.to_owned(),
                status: 0x90 | channel,
                data1: control.trigger.data1,
            })
        }
    }
}

/// The scene a pad color shows for `action`: the action's scene, else the
/// selected one.
pub(crate) fn pad_target(action: Option<&MidiInputAction>) -> MidiSceneTarget {
    action
        .and_then(action_target)
        .cloned()
        .unwrap_or(MidiSceneTarget::Selected)
}