gled-ui 2.31.2

gled's egui interface, shared by the desktop app and the browser
Documentation
use egui::{ComboBox, DragValue, Ui};
use gled_model::asset::midi_controller::{
    MidiEffectTarget, MidiEffectTargetKind, MidiEncoder, MidiInputAction, MidiInputActionKind,
    MidiRelativeEncoding, MidiSceneTarget, MidiSceneTargetKind,
};
use strum::IntoEnumIterator;

pub(super) fn input_action_from_kind(
    kind: MidiInputActionKind,
    current: MidiInputAction,
) -> MidiInputAction {
    match kind {
        MidiInputActionKind::Tap => MidiInputAction::Tap,
        MidiInputActionKind::SetMainDimmer => MidiInputAction::SetMainDimmer,
        MidiInputActionKind::SetMainDimmerRelative => MidiInputAction::SetMainDimmerRelative {
            encoder: current_encoder_from_action(&current),
        },
        MidiInputActionKind::SetBlackout => MidiInputAction::SetBlackout,
        MidiInputActionKind::ToggleBlackout => MidiInputAction::ToggleBlackout,
        MidiInputActionKind::SetSpeedAdd => MidiInputAction::SetSpeedAdd,
        MidiInputActionKind::SetSpeedMultiply => MidiInputAction::SetSpeedMultiply,
        MidiInputActionKind::SetSpeedRelative => MidiInputAction::SetSpeedRelative {
            encoder: current_encoder_from_action(&current),
        },
        MidiInputActionKind::SelectSceneDistributed => MidiInputAction::SelectSceneDistributed,
        MidiInputActionKind::SelectSceneRelative => MidiInputAction::SelectSceneRelative {
            encoder: current_encoder_from_action(&current),
        },
        MidiInputActionKind::SelectSceneEffect => MidiInputAction::SelectSceneEffect {
            index: match current {
                MidiInputAction::SelectSceneEffect { index } => index,
                _ => 0,
            },
        },
        MidiInputActionKind::SelectSceneEffectRelative => {
            MidiInputAction::SelectSceneEffectRelative {
                encoder: current_encoder_from_action(&current),
            }
        }
        MidiInputActionKind::SelectSceneEffectDistributed => {
            MidiInputAction::SelectSceneEffectDistributed
        }
        MidiInputActionKind::SelectScene => MidiInputAction::SelectScene {
            target: current_target_from_action(&current),
        },
        MidiInputActionKind::ToggleSceneActive => MidiInputAction::ToggleSceneActive {
            target: current_target_from_action(&current),
        },
        MidiInputActionKind::SetSceneActive => MidiInputAction::SetSceneActive {
            target: current_target_from_action(&current),
        },
        MidiInputActionKind::FlashScene => MidiInputAction::FlashScene {
            target: current_target_from_action(&current),
        },
        MidiInputActionKind::SetSceneOpacity => MidiInputAction::SetSceneOpacity {
            target: current_target_from_action(&current),
        },
        MidiInputActionKind::SetSceneInputDimmer => MidiInputAction::SetSceneInputDimmer {
            target: current_target_from_action(&current),
        },
        MidiInputActionKind::SetSceneBeatOffset => MidiInputAction::SetSceneBeatOffset {
            target: current_target_from_action(&current),
        },
        MidiInputActionKind::SetSceneIgnoreMainDimmer => {
            MidiInputAction::SetSceneIgnoreMainDimmer {
                target: current_target_from_action(&current),
            }
        }
        MidiInputActionKind::SetSceneSetOffsetOnFlash => {
            MidiInputAction::SetSceneSetOffsetOnFlash {
                target: current_target_from_action(&current),
            }
        }
        MidiInputActionKind::SetSceneEffectSettingF32 => {
            let setting_index = match &current {
                MidiInputAction::SetSceneEffectSettingF32 { setting_index, .. } => *setting_index,
                _ => 0,
            };
            MidiInputAction::SetSceneEffectSettingF32 {
                target: current_target_from_action(&current),
                effect: current_effect_from_action(&current),
                setting_index,
            }
        }
        MidiInputActionKind::SetSceneEffectOpacity => {
            let effect = current_effect_from_action(&current);
            MidiInputAction::SetSceneEffectOpacity {
                target: current_target_from_action(&current),
                effect,
            }
        }
        MidiInputActionKind::SetSceneEffectColorShift => {
            let effect = current_effect_from_action(&current);
            MidiInputAction::SetSceneEffectColorShift {
                target: current_target_from_action(&current),
                effect,
            }
        }
        MidiInputActionKind::SetSceneEffectBeatProgression => {
            let effect = current_effect_from_action(&current);
            MidiInputAction::SetSceneEffectBeatProgression {
                target: current_target_from_action(&current),
                effect,
            }
        }
        MidiInputActionKind::SetSceneEffectBeatOffset => {
            let effect = current_effect_from_action(&current);
            MidiInputAction::SetSceneEffectBeatOffset {
                target: current_target_from_action(&current),
                effect,
            }
        }
        MidiInputActionKind::SetSceneEffectSpeedExponent => {
            let effect = current_effect_from_action(&current);
            MidiInputAction::SetSceneEffectSpeedExponent {
                target: current_target_from_action(&current),
                effect,
            }
        }
        MidiInputActionKind::SetSceneEffectLayout => {
            let effect = current_effect_from_action(&current);
            MidiInputAction::SetSceneEffectLayout {
                target: current_target_from_action(&current),
                effect,
            }
        }
    }
}

fn current_target_from_action(action: &MidiInputAction) -> MidiSceneTarget {
    match action {
        MidiInputAction::SelectScene { target }
        | MidiInputAction::ToggleSceneActive { target }
        | MidiInputAction::SetSceneActive { target }
        | MidiInputAction::FlashScene { target }
        | MidiInputAction::SetSceneOpacity { target }
        | MidiInputAction::SetSceneInputDimmer { target }
        | MidiInputAction::SetSceneBeatOffset { target }
        | MidiInputAction::SetSceneIgnoreMainDimmer { target }
        | MidiInputAction::SetSceneSetOffsetOnFlash { target }
        | MidiInputAction::SetSceneEffectSettingF32 { target, .. }
        | MidiInputAction::SetSceneEffectOpacity { target, .. }
        | MidiInputAction::SetSceneEffectColorShift { target, .. }
        | MidiInputAction::SetSceneEffectBeatProgression { target, .. }
        | MidiInputAction::SetSceneEffectBeatOffset { target, .. }
        | MidiInputAction::SetSceneEffectSpeedExponent { target, .. }
        | MidiInputAction::SetSceneEffectLayout { target, .. } => target.clone(),
        _ => MidiSceneTarget::Selected,
    }
}

fn current_effect_from_action(action: &MidiInputAction) -> MidiEffectTarget {
    match action {
        MidiInputAction::SetSceneEffectSettingF32 { effect, .. }
        | MidiInputAction::SetSceneEffectOpacity { effect, .. }
        | MidiInputAction::SetSceneEffectColorShift { effect, .. }
        | MidiInputAction::SetSceneEffectBeatProgression { effect, .. }
        | MidiInputAction::SetSceneEffectBeatOffset { effect, .. }
        | MidiInputAction::SetSceneEffectSpeedExponent { effect, .. }
        | MidiInputAction::SetSceneEffectLayout { effect, .. } => *effect,
        _ => MidiEffectTarget::Selected,
    }
}

fn current_encoder_from_action(action: &MidiInputAction) -> MidiEncoder {
    match action {
        MidiInputAction::SetMainDimmerRelative { encoder }
        | MidiInputAction::SetSpeedRelative { encoder }
        | MidiInputAction::SelectSceneRelative { encoder }
        | MidiInputAction::SelectSceneEffectRelative { encoder } => *encoder,
        _ => MidiEncoder::default(),
    }
}

pub(super) fn scene_target_editor(
    ui: &mut Ui,
    target: &mut MidiSceneTarget,
    dirty: &mut bool,
    id: String,
) {
    let mut kind = MidiSceneTargetKind::from(&*target);
    ComboBox::new(id, "")
        .selected_text(kind.to_string())
        .show_ui(ui, |ui| {
            for candidate in MidiSceneTargetKind::iter() {
                ui.selectable_value(&mut kind, candidate, candidate.to_string());
            }
        });

    let next = scene_target_from_kind(kind, target.clone());
    if *target != next {
        *target = next;
        *dirty = true;
    }

    match target {
        MidiSceneTarget::Quick { index } => {
            ui.horizontal(|ui| {
                ui.label("Quick Scene Index");
                if ui.add(DragValue::new(index).range(0..=7)).changed() {
                    *dirty = true;
                }
            });
        }
        MidiSceneTarget::Grid { row, col } => {
            ui.horizontal(|ui| {
                ui.label("Row");
                if ui.add(DragValue::new(row).range(0..=5)).changed() {
                    *dirty = true;
                }
                ui.label("Col");
                if ui.add(DragValue::new(col).range(0..=7)).changed() {
                    *dirty = true;
                }
            });
        }
        MidiSceneTarget::Selected => {}
    }
}

fn scene_target_from_kind(kind: MidiSceneTargetKind, current: MidiSceneTarget) -> MidiSceneTarget {
    match kind {
        MidiSceneTargetKind::Selected => MidiSceneTarget::Selected,
        MidiSceneTargetKind::Quick => match current {
            MidiSceneTarget::Quick { index } => MidiSceneTarget::Quick { index },
            _ => MidiSceneTarget::Quick { index: 0 },
        },
        MidiSceneTargetKind::Grid => match current {
            MidiSceneTarget::Grid { row, col } => MidiSceneTarget::Grid { row, col },
            _ => MidiSceneTarget::Grid { row: 0, col: 0 },
        },
    }
}

/// Picks the effect a binding works on: the selected one or a fixed index.
pub(super) fn effect_target_editor(
    ui: &mut Ui,
    effect: &mut MidiEffectTarget,
    dirty: &mut bool,
    id: String,
) -> egui::Response {
    ui.horizontal(|ui| {
        let mut kind = MidiEffectTargetKind::from(&*effect);
        ComboBox::new(id, "")
            .selected_text(kind.to_string())
            .show_ui(ui, |ui| {
                for candidate in MidiEffectTargetKind::iter() {
                    ui.selectable_value(&mut kind, candidate, candidate.to_string());
                }
            });
        let next = match (kind, &*effect) {
            (MidiEffectTargetKind::Selected, _) => MidiEffectTarget::Selected,
            (MidiEffectTargetKind::Index, MidiEffectTarget::Index { index }) => {
                MidiEffectTarget::Index { index: *index }
            }
            (MidiEffectTargetKind::Index, MidiEffectTarget::Selected) => {
                MidiEffectTarget::Index { index: 0 }
            }
        };
        if *effect != next {
            *effect = next;
            *dirty = true;
        }
        if let MidiEffectTarget::Index { index } = effect
            && ui.add(DragValue::new(index).range(0..=255)).changed()
        {
            *dirty = true;
        }
    })
    .response
}

/// Picks how an endless encoder reports a turn and how many of its steps
/// make one unit.
pub(super) fn encoder_editor(ui: &mut Ui, encoder: &mut MidiEncoder, dirty: &mut bool, id: String) {
    ui.horizontal(|ui| {
        ui.label("Steps per unit");
        if ui
            .add(DragValue::new(&mut encoder.steps_per_unit).range(1..=64))
            .on_hover_text("Encoder steps for one scene, effect, BPM or 1/127 of the dimmer")
            .changed()
        {
            *dirty = true;
        }
    });
    ui.horizontal(|ui| {
        ui.label("Encoder");
        ComboBox::new(id, "")
            .selected_text(encoder.encoding.to_string())
            .show_ui(ui, |ui| {
                for candidate in MidiRelativeEncoding::iter() {
                    if ui
                        .selectable_value(&mut encoder.encoding, candidate, candidate.to_string())
                        .changed()
                    {
                        *dirty = true;
                    }
                }
            })
            .response
            .on_hover_text(
                "Two's complement: 127 is one step back (APC40). \
                 Sign magnitude: 65 is one step back (Mackie Control, iCON P1-X).",
            );
    });
}