gled-ui 2.30.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.

use gled_protocol::state::{ClientInput, Devices};
use std::{
    cell::{Cell, RefCell},
    collections::BTreeSet,
    sync::Arc,
};

use crate::layout::ClientLayout;
use uuid::Uuid;

use gled_model::asset::{group::Group, 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 GROUPS: RefCell<Arc<Vec<Group>>> = RefCell::new(Arc::default());
    static UNIVERSES: RefCell<Arc<BTreeSet<u16>>> = RefCell::new(Arc::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 lamp groups of the project's SVG.
pub(crate) fn layout_groups() -> Arc<Vec<Group>> {
    GROUPS.with(|groups| groups.borrow().clone())
}

/// The output universes the project's SVG uses.
pub(crate) fn layout_universes() -> Arc<BTreeSet<u16>> {
    UNIVERSES.with(|universes| universes.borrow().clone())
}

/// 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 and
    /// the lamp layout.
    pub(crate) fn begin(&self, devices: &Devices, layout: &ClientLayout) {
        DEVICES.with(|current| *current.borrow_mut() = devices.clone());
        GROUPS.with(|groups| *groups.borrow_mut() = layout.groups().clone());
        UNIVERSES.with(|universes| *universes.borrow_mut() = layout.universes().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();
        }
    }
}