gled-ui 2.32.1

gled's egui interface, shared by the desktop app and the browser
Documentation
//! What the windows need from the engine during a frame, collected on the
//! user interface's thread and sent once per frame when it changed: the
//! output routing the pointer is over, and whether a device list is shown.
//! The device lists themselves come back in the engine's status. Also what
//! the windows show of this interface's own MIDI ports.

use gled_midi::client::Availability;
use gled_protocol::state::{ClientInput, Devices};
use std::{
    cell::{Cell, RefCell},
    collections::BTreeSet,
};

use uuid::Uuid;

use gled_model::asset::output_device::routing::OutputRouting;

thread_local! {
    static HOVERED: RefCell<Option<OutputRouting>> = const { RefCell::new(None) };
    static WATCH_DEVICES: Cell<bool> = const { Cell::new(false) };
    static WATCH_MIDI: Cell<bool> = const { Cell::new(false) };
    static WATCHED_TRIGGERS: RefCell<BTreeSet<Uuid>> = const { RefCell::new(BTreeSet::new()) };
    static DEVICES: RefCell<Devices> = RefCell::new(Devices::default());
    static LOCAL_MIDI: RefCell<LocalMidiStatus> = RefCell::new(LocalMidiStatus::default());
    static MIDI_ACCESS_REQUESTED: Cell<bool> = const { Cell::new(false) };
}

/// This interface's own MIDI ports, when it hosts them: connected to a
/// remote engine, on the desktop or in the browser.
#[derive(Debug, Clone, Default)]
pub(crate) struct LocalMidiStatus {
    /// `None` while the interface hosts no ports, as beside its own engine.
    pub(crate) availability: Option<Availability>,
    pub(crate) ports: BTreeSet<String>,
}

/// This interface's own MIDI ports.
pub(crate) fn local_midi() -> LocalMidiStatus {
    LOCAL_MIDI.with(|status| status.borrow().clone())
}

/// Asks the browser for its MIDI devices after this frame.
pub(crate) fn request_midi_access() {
    MIDI_ACCESS_REQUESTED.with(|requested| requested.set(true));
}

/// Notes the routing a window's pointer is over this frame, if any.
pub(crate) fn hover_output_routing(routing: Option<OutputRouting>) {
    if let Some(routing) = routing {
        HOVERED.with(|hovered| *hovered.borrow_mut() = Some(routing));
    }
}

/// The engine's device lists. Calling this keeps the engine scanning.
pub(crate) fn devices() -> Devices {
    WATCH_DEVICES.with(|watch| watch.set(true));
    DEVICES.with(|devices| devices.borrow().clone())
}

/// Keeps the engine streaming MIDI traffic and learn input this frame.
pub(crate) fn watch_midi() {
    WATCH_MIDI.with(|watch| watch.set(true));
}

/// Keeps the engine sending this sound trigger's spectrum this frame.
pub(crate) fn watch_sound_trigger(id: Uuid) {
    WATCHED_TRIGGERS.with(|watched| watched.borrow_mut().insert(id));
}

/// What the windows asked for during a frame, last sent.
#[derive(Debug, Default)]
pub(crate) struct FrameRequests {
    hovered: Option<OutputRouting>,
    watch_devices: bool,
    watch_midi: bool,
    midi_learning: bool,
    watched_triggers: BTreeSet<Uuid>,
}

impl FrameRequests {
    /// Call before the windows draw: hands them the newest device lists.
    pub(crate) fn begin(&self, devices: &Devices, local_midi: LocalMidiStatus) {
        DEVICES.with(|current| *current.borrow_mut() = devices.clone());
        LOCAL_MIDI.with(|current| *current.borrow_mut() = local_midi);
    }

    /// Whether a window asked for the browser's MIDI devices this frame.
    pub(crate) fn take_midi_access_request(&self) -> bool {
        MIDI_ACCESS_REQUESTED.with(|requested| requested.replace(false))
    }

    /// Whether a window shows the MIDI monitor, and whether one waits for
    /// MIDI learn input, as last sent.
    pub(crate) fn midi_watching(&self) -> (bool, bool) {
        (self.watch_midi, self.midi_learning)
    }

    /// Call after the windows drew: sends what changed. `midi_learning` is
    /// whether the interface waits for MIDI learn input.
    pub(crate) fn end(&mut self, midi_learning: bool) {
        let hovered = HOVERED.with(|hovered| hovered.borrow_mut().take());
        if hovered != self.hovered {
            self.hovered = hovered.clone();
            ClientInput::HoverOutputRouting(hovered).enqueue();
        }
        let watch = WATCH_DEVICES.with(|watch| watch.replace(false));
        if watch != self.watch_devices {
            self.watch_devices = watch;
            ClientInput::WatchDevices(watch).enqueue();
        }
        let watch = WATCH_MIDI.with(|watch| watch.replace(false));
        if watch != self.watch_midi {
            self.watch_midi = watch;
            ClientInput::WatchMidi(watch).enqueue();
        }
        let triggers = WATCHED_TRIGGERS.with(|watched| std::mem::take(&mut *watched.borrow_mut()));
        if triggers != self.watched_triggers {
            self.watched_triggers = triggers.clone();
            ClientInput::WatchSoundTriggers(triggers).enqueue();
        }
        if midi_learning != self.midi_learning {
            self.midi_learning = midi_learning;
            ClientInput::MidiLearning(midi_learning).enqueue();
        }
    }
}