gled-ui 2.34.0

gled's egui interface, shared by the desktop app and the browser
Documentation
//! A rule's MIDI messages, for when a controller needs them adjusted by
//! hand: the control's message, its touch note, and each light's message
//! and values.

use egui::{DragValue, Frame, Grid, Ui};
use gled_model::asset::midi_controller::{MidiOutputBindingKind, MidiTrigger};

use super::intent::Intent;
use super::list_state::LearnRequest;
use super::rule_card::Cards;
use super::rules::{Members, Rule, feedback_role, message_name};

pub(crate) fn midi_details(ui: &mut Ui, cards: &mut Cards, rule: &Rule) {
    let control = &cards.controls[rule.control];
    Frame::NONE
        .fill(ui.visuals().extreme_bg_color)
        .inner_margin(6.0)
        .show(ui, |ui| {
            ui.horizontal(|ui| {
                ui.label(format!(
                    "{} sends {}",
                    control.name,
                    describe(&control.trigger)
                ));
                if ui
                    .small_button("Learn again")
                    .on_hover_text("Then move the control")
                    .clicked()
                {
                    cards.state.learn_request = Some(LearnRequest::Control(rule.control));
                }
            });
            if let Some(press) = rule.press {
                let touch = cards.mapping.input_bindings[press].touch.clone();
                touch_row(ui, cards, rule, &touch);
            }
            Grid::new(("midi feedback", rule.control, rule.mode))
                .num_columns(5)
                .show(ui, |ui| {
                    for &output in &rule.feedback {
                        feedback_row(ui, cards, rule, output);
                        ui.end_row();
                    }
                });
            for index in rule.press.iter().chain(&rule.release) {
                ui.weak(&cards.mapping.input_bindings[*index].name);
            }
        });
}

/// "Note On, note 24, channel 1".
fn describe(trigger: &MidiTrigger) -> String {
    let channel = (trigger.status & 0x0F) + 1;
    match trigger.status & 0xF0 {
        0x80 | 0x90 => format!("note {} on channel {channel}", trigger.data1),
        0xB0 => format!("controller (CC) {} on channel {channel}", trigger.data1),
        0xE0 => format!("pitch bend on channel {channel}"),
        _ => format!(
            "{} (status {}, data {})",
            message_name(trigger),
            trigger.status,
            trigger.data1
        ),
    }
}

fn touch_row(ui: &mut Ui, cards: &mut Cards, rule: &Rule, touch: &Option<MidiTrigger>) {
    ui.horizontal(|ui| {
        let mut sensitive = touch.is_some();
        let mut edited = touch.clone();
        let mut changed = ui
            .checkbox(&mut sensitive, "Touch note")
            .on_hover_text(
                "The note a motor fader sends while a finger rests on it; \
                 gled outlines what it changes meanwhile",
            )
            .changed();
        if changed {
            edited = sensitive.then_some(MidiTrigger {
                status: 0x90,
                data1: 0,
                match_data1: true,
            });
        }
        if let Some(touch) = edited.as_mut() {
            ui.label("note");
            changed |= ui
                .add(DragValue::new(&mut touch.data1).range(0..=127))
                .changed();
            let mut channel = (touch.status & 0x0F) + 1;
            ui.label("channel");
            if ui.add(DragValue::new(&mut channel).range(1..=16)).changed() {
                touch.status = 0x90 | (channel - 1);
                changed = true;
            }
        }
        if changed {
            cards.intents.push(Intent::SetTouch {
                members: Members::single(rule.clone()),
                touch: edited,
            });
        }
    });
}

fn feedback_row(ui: &mut Ui, cards: &mut Cards, rule: &Rule, output: usize) {
    let kind = &cards.mapping.output_bindings[output].kind;
    let role = feedback_role(cards.controls, rule, kind);
    ui.label(role.name());
    let mut edited = kind.clone();
    let mut changed = false;
    match &mut edited {
        MidiOutputBindingKind::Value(value) => {
            changed |= status_data(ui, &mut value.status, &mut value.data1);
            ui.horizontal(|ui| {
                ui.label("off");
                changed |= ui
                    .add(DragValue::new(&mut value.min).range(0..=127))
                    .changed();
                ui.label("full");
                changed |= ui
                    .add(DragValue::new(&mut value.max).range(0..=127))
                    .changed();
                ui.label("on")
                    .on_hover_text("The value sent for \"on\", such as a lit button's color");
                changed |= ui
                    .add(DragValue::new(&mut value.active_value).range(0..=127))
                    .changed();
            });
        }
        MidiOutputBindingKind::SceneColorValue(color) => {
            changed |= status_data(ui, &mut color.status, &mut color.data1);
            ui.label("");
        }
        MidiOutputBindingKind::Meter(meter) => {
            let mut strip = meter.strip + 1;
            ui.horizontal(|ui| {
                ui.label("strip");
                if ui.add(DragValue::new(&mut strip).range(1..=8)).changed() {
                    meter.strip = strip - 1;
                    changed = true;
                }
            });
            ui.label("");
        }
    }
    if changed {
        cards.intents.push(Intent::SetFeedbackKind {
            output,
            kind: edited,
        });
    }
}

/// The message type, channel and number of an output.
fn status_data(ui: &mut Ui, status: &mut u8, data1: &mut u8) -> bool {
    let mut changed = false;
    ui.horizontal(|ui| {
        let mut channel = (*status & 0x0F) + 1;
        let kind = *status & 0xF0;
        let label = match kind {
            0x90 => "note",
            0xB0 => "CC",
            0xE0 => "pitch bend",
            _ => "data",
        };
        ui.label("channel");
        if ui.add(DragValue::new(&mut channel).range(1..=16)).changed() {
            *status = kind | (channel - 1);
            changed = true;
        }
        if kind != 0xE0 {
            ui.label(label);
            changed |= ui.add(DragValue::new(data1).range(0..=127)).changed();
        } else {
            ui.label(label);
        }
    });
    changed
}