gled-ui 2.31.0

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.

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());
}

/// 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) {
        DEVICES.with(|current| *current.borrow_mut() = devices.clone());
    }

    /// 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();
        }
    }
}