gled-ui 2.34.0

gled's egui interface, shared by the desktop app and the browser
Documentation
use egui::{ComboBox, Ui};
use gled_model::asset::midi_controller::MidiController;
use gled_protocol::midi::TestCommand;
use std::collections::BTreeSet;

use super::MidiControllerTestState;
use crate::midi_learn::MidiPortDiagnostics;

fn is_selectable_output_port(name: &str) -> bool {
    !name.trim().is_empty() && !gled_midi::is_own_port(name)
}

/// The MIDI outputs of the engine's machine, which scans while this shows.
fn available_output_ports() -> Vec<String> {
    crate::ui::frame_requests::devices()
        .midi_outputs
        .into_iter()
        .filter(|name| is_selectable_output_port(name))
        .collect()
}

fn test_device_port_options(diagnostics: &[MidiPortDiagnostics]) -> Vec<String> {
    let mut ports = BTreeSet::new();

    for diag in diagnostics {
        if diag.output_connected && is_selectable_output_port(&diag.port_name) {
            ports.insert(diag.port_name.clone());
        }
    }
    for port in available_output_ports() {
        ports.insert(port);
    }

    ports.into_iter().collect()
}

/// A port's name without the system's client prefix and address:
/// "iCON P1-X1:iCON P1-X1 MIDI 1 129:0" → "iCON P1-X1 MIDI 1".
pub(super) fn short_port_name(port: &str) -> String {
    let key = gled_model::asset::midi_controller::normalize_controller_key(port);
    match key.split_once(':') {
        Some((_, name)) if !name.trim().is_empty() => name.trim().to_owned(),
        _ => key,
    }
}

/// Picks the device test lights go to; returns the port picked now.
pub(super) fn render_test_device_box(
    ui: &mut Ui,
    test_state: &mut MidiControllerTestState,
    diagnostics: &[MidiPortDiagnostics],
    controller_id: gled_model::asset_id::AssetId<MidiController>,
    test_command_sender: &kanal::Sender<TestCommand>,
) -> Option<String> {
    let mut picked = None;
    let selected = test_state
        .selected_output_port
        .as_deref()
        .map_or_else(|| "nothing".to_owned(), short_port_name);
    ComboBox::new(format!("{controller_id}_test_output_port"), "")
        .width(260.0)
        .selected_text(selected)
        .show_ui(ui, |ui| {
            if ui
                .selectable_label(test_state.selected_output_port.is_none(), "nothing")
                .clicked()
            {
                test_state.selected_output_port = None;
                let _ = test_command_sender.send(TestCommand::UnsetTestDevice);
            }
            for port_name in test_device_port_options(diagnostics) {
                let current =
                    test_state.selected_output_port.as_deref() == Some(port_name.as_str());
                if ui
                    .selectable_label(current, short_port_name(&port_name))
                    .on_hover_text(&port_name)
                    .clicked()
                    && !current
                {
                    test_state.selected_output_port = Some(port_name.clone());
                    let _ = test_command_sender.send(TestCommand::SetTestDevice {
                        port_name: port_name.clone(),
                    });
                    picked = Some(port_name);
                }
            }
        });
    picked
}

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

    #[test]
    fn ports_show_without_client_and_address() {
        assert_eq!(
            short_port_name("iCON P1-X1:iCON P1-X1 MIDI 1 129:0"),
            "iCON P1-X1 MIDI 1"
        );
        assert_eq!(short_port_name("APC40 [2]"), "APC40");
    }
}