gled-ui 2.34.0

gled's egui interface, shared by the desktop app and the browser
Documentation
//! What the controller window calls each action: its group in the palette,
//! its label there, the sentence it reads as, which controls it suits, and
//! words it is found by.

use gled_model::asset::midi_controller::MidiInputActionKind;
use strum::IntoEnumIterator;

use super::controls::ControlKind;

/// A heading of the action palette.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ActionGroup {
    Global,
    Navigation,
    Scene,
    Animation,
}

impl ActionGroup {
    pub(crate) fn label(self) -> &'static str {
        match self {
            Self::Global => "Global",
            Self::Navigation => "Navigation",
            Self::Scene => "Scene",
            Self::Animation => "Animation",
        }
    }
}

/// Which controls an action suits, for the palette's suggestions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Fit {
    Press,
    Level,
    Endless,
}

impl Fit {
    fn suits(self, kind: ControlKind) -> bool {
        match self {
            Self::Press => matches!(kind, ControlKind::Button | ControlKind::Pad),
            Self::Level => matches!(kind, ControlKind::Fader | ControlKind::Knob),
            Self::Endless => kind == ControlKind::Encoder,
        }
    }
}

/// An action as the palette and the rule sentences show it.
#[derive(Debug, Clone, Copy)]
pub(crate) struct ActionInfo {
    pub(crate) kind: MidiInputActionKind,
    pub(crate) group: ActionGroup,
    pub(crate) label: &'static str,
    /// The sentence after the control's name: `{@…}` is the action word,
    /// `{target}`, `{effect}`, `{setting}`, `{animation}`, `{mode}` and
    /// `{encoder}` the action's settings.
    pub(crate) phrase: &'static str,
    pub(crate) description: &'static str,
    /// More words the palette's search finds it by.
    pub(crate) keywords: &'static str,
    fit: Fit,
}

impl ActionInfo {
    pub(crate) fn suits(&self, kind: ControlKind) -> bool {
        self.fit.suits(kind)
    }

    /// Whether the search text `query` finds this action.
    pub(crate) fn matches(&self, query: &str) -> bool {
        let query = query.trim().to_lowercase();
        query.split_whitespace().all(|word| {
            self.label.to_lowercase().contains(word)
                || self.keywords.contains(word)
                || self.kind.to_string().to_lowercase().contains(word)
                || self.group.label().to_lowercase().contains(word)
        })
    }
}

/// How `kind` reads in the palette and in rule sentences.
pub(crate) fn action_info(kind: MidiInputActionKind) -> ActionInfo {
    use ActionGroup::{Animation, Global, Navigation, Scene};
    use Fit::{Endless, Level, Press};
    use MidiInputActionKind as K;
    let (group, label, phrase, fit, description, keywords) = match kind {
        K::Tap => (
            Global,
            "Tap tempo",
            "{@taps the tempo}",
            Press,
            "Tap a few times to set the BPM",
            "bpm beat",
        ),
        K::SetMainDimmer => (
            Global,
            "Main dimmer",
            "{@sets the main dimmer}",
            Level,
            "Fader position = master brightness",
            "master brightness",
        ),
        K::SetMainDimmerRelative => (
            Global,
            "Main dimmer up / down",
            "{@turns the main dimmer up/down} · {encoder}",
            Endless,
            "An endless encoder nudges the master brightness",
            "master brightness",
        ),
        K::SetBlackout => (
            Global,
            "Blackout while held (flash blackout)",
            "{@switches blackout on}",
            Press,
            "Blackout on press, back to normal on release",
            "flash hold momentary kill",
        ),
        K::ToggleBlackout => (
            Global,
            "Toggle blackout",
            "{@toggles blackout}",
            Press,
            "Each press switches blackout on or off",
            "kill",
        ),
        K::SetSpeedAdd => (
            Global,
            "Speed offset",
            "{@sets the speed offset}",
            Level,
            "Adds to the animation speed",
            "tempo",
        ),
        K::SetSpeedMultiply => (
            Global,
            "Speed multiplier",
            "{@sets the speed multiplier}",
            Level,
            "Multiplies the animation speed",
            "tempo",
        ),
        K::SetSpeedRelative => (
            Global,
            "BPM up / down",
            "{@nudges the BPM} · {encoder}",
            Endless,
            "An endless encoder changes the tempo",
            "tempo speed",
        ),
        K::SelectSceneDistributed => (
            Navigation,
            "Sweep through all scenes (knob)",
            "{@sweeps through all scenes}",
            Level,
            "The knob's position picks a scene: 0 = first … 127 = last",
            "select pick",
        ),
        K::SelectSceneRelative => (
            Navigation,
            "Next / previous scene",
            "{@steps through the scenes} · {encoder}",
            Endless,
            "",
            "select",
        ),
        K::SelectSceneRow => (
            Navigation,
            "Next / previous row",
            "{@steps through the rows} · {encoder}",
            Endless,
            "",
            "select",
        ),
        K::SelectSceneColumn => (
            Navigation,
            "Next / previous column",
            "{@steps through the columns} · {encoder}",
            Endless,
            "",
            "select",
        ),
        K::SelectScene => (
            Navigation,
            "Jump to a scene (button)",
            "{@selects} {target}",
            Press,
            "Makes one particular scene the selected one",
            "select pick",
        ),
        K::SelectMode => (
            Navigation,
            "Switch controller mode",
            "{@switches the controller to mode} {mode}",
            Press,
            "Changes which rules work",
            "mode page layer",
        ),
        K::SelectSceneEffect => (
            Navigation,
            "Jump to an animation (button)",
            "{@selects} {animation}",
            Press,
            "Makes one particular animation the selected one",
            "select effect",
        ),
        K::SelectSceneEffectRelative => (
            Navigation,
            "Next / previous animation",
            "{@steps through the animations} · {encoder}",
            Endless,
            "",
            "select effect",
        ),
        K::SelectSceneEffectDistributed => (
            Navigation,
            "Sweep through all animations (knob)",
            "{@sweeps through all animations}",
            Level,
            "The knob's position picks an animation",
            "select effect",
        ),
        K::ToggleSceneActive => (
            Scene,
            "Toggle scene on / off",
            "{@toggles} {target} {@on/off}",
            Press,
            "Each press switches the scene on or off",
            "activate",
        ),
        K::SetSceneActive => (
            Scene,
            "Scene on while held",
            "{@switches on} {target}",
            Press,
            "On while pressed, off on release",
            "hold momentary activate",
        ),
        K::FlashScene => (
            Scene,
            "Flash scene",
            "{@flashes} {target}",
            Press,
            "The scene shows while the button is held",
            "hold momentary",
        ),
        K::SetSceneOpacity => (
            Scene,
            "Scene opacity",
            "{@sets the opacity of} {target}",
            Level,
            "",
            "brightness",
        ),
        K::SetSceneInputDimmer => (
            Scene,
            "Scene input dimmer",
            "{@sets the input dimmer of} {target}",
            Level,
            "",
            "",
        ),
        K::SetSceneBeatOffset => (
            Scene,
            "Scene beat offset",
            "{@sets the beat offset of} {target}",
            Level,
            "",
            "",
        ),
        K::SetSceneIgnoreMainDimmer => (
            Scene,
            "Scene ignores main dimmer",
            "{@sets “ignore main dimmer” of} {target}",
            Press,
            "",
            "",
        ),
        K::SetSceneSetOffsetOnFlash => (
            Scene,
            "Scene: set offset on flash",
            "{@sets “offset on flash” of} {target}",
            Press,
            "",
            "",
        ),
        K::SetSceneEffectSettingF32 => (
            Animation,
            "Animation setting 1–6",
            "{@sets} {setting} of {effect} in {target}",
            Level,
            "The animation's own sliders",
            "effect parameter",
        ),
        K::SetSceneEffectOpacity => (
            Animation,
            "Animation opacity",
            "{@sets the opacity of} {effect} in {target}",
            Level,
            "",
            "effect",
        ),
        K::SetSceneEffectColorShift => (
            Animation,
            "Animation color shift",
            "{@sets the color shift of} {effect} in {target}",
            Level,
            "",
            "effect hue",
        ),
        K::SetSceneEffectBeatProgression => (
            Animation,
            "Animation beat progression",
            "{@sets the beat progression of} {effect} in {target}",
            Level,
            "",
            "effect",
        ),
        K::SetSceneEffectBeatOffset => (
            Animation,
            "Animation beat offset",
            "{@sets the beat offset of} {effect} in {target}",
            Level,
            "",
            "effect",
        ),
        K::SetSceneEffectSpeedExponent => (
            Animation,
            "Animation speed",
            "{@sets the speed of} {effect} in {target}",
            Level,
            "",
            "effect tempo",
        ),
        K::SetSceneEffectLayout => (
            Animation,
            "Animation layout",
            "{@sets the layout of} {effect} in {target}",
            Level,
            "0 = the tile's layout, 1 = the topology, 2 and up the layouts",
            "effect",
        ),
    };
    ActionInfo {
        kind,
        group,
        label,
        phrase,
        description,
        keywords,
        fit,
    }
}

/// All actions in palette order.
pub(crate) fn actions() -> impl Iterator<Item = ActionInfo> {
    MidiInputActionKind::iter().map(action_info)
}

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

    #[test]
    fn the_search_finds_actions_by_what_people_call_them() {
        let found = |query: &str| -> Vec<&'static str> {
            actions()
                .filter(|info| info.matches(query))
                .map(|info| info.label)
                .collect()
        };
        assert!(found("flash").contains(&"Flash scene"));
        assert!(found("flash").contains(&"Blackout while held (flash blackout)"));
        assert!(found("animation speed").contains(&"Animation speed"));
        // gled's own name for it still finds it.
        assert!(found("speed exponent").contains(&"Animation speed"));
    }

    #[test]
    fn every_action_suits_some_control() {
        for info in actions() {
            assert!(
                [
                    ControlKind::Button,
                    ControlKind::Fader,
                    ControlKind::Encoder
                ]
                .iter()
                .any(|kind| info.suits(*kind)),
                "{:?}",
                info.kind
            );
        }
    }
}