gled 2.29.0

gled is an application for creating animations and effects on artnet or dmx installations
pub mod color;
pub mod effect;
pub mod effect_state;
pub(crate) mod grid;
pub mod instance;

use super::{AssetTrait, animation::Animation, palette::Palette};
use crate::{
    audio::sound_data::SoundData,
    pipeline::{
        gpu::{Gpu, OutputContext},
        group::{GroupIndices, Groups},
    },
    storage::{AssetId, collections::Collections},
    ui::pills::show_pills,
};
use effect::Effect;
use egui::{Color32, Vec2};
use serde::{Deserialize, Serialize};
use wgpu::{CommandEncoder, Queue};

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
pub struct Scene {
    pub effects: Vec<Effect>,
}

impl Scene {
    pub fn group_indices(&self) -> GroupIndices {
        self.effects
            .iter()
            .map(|effect| effect.group_index)
            .collect()
    }

    pub fn effect(&mut self, index: usize) -> Option<&mut Effect> {
        self.effects.get_mut(index)
    }

    pub fn effects(&self) -> &[Effect] {
        &self.effects
    }

    /// Add an effect. Its GPU pipeline is built lazily by `ensure_pipelines`
    /// on whichever thread renders this scene.
    pub fn add_effect(&mut self, effect: Effect) -> usize {
        self.effects.push(effect);

        self.effects.len() - 1
    }

    pub fn remove_effect(&mut self, index: usize) -> Option<Effect> {
        let mut effect = None;

        if self.effects.len() > index {
            effect = Some(self.effects.remove(index));
        }

        effect
    }

    /// Mark the pipelines of all effects using the given animation as outdated,
    /// or of all effects for `None`. Call after an animation was edited; the
    /// next `ensure_*_pipelines` rebuilds them.
    pub fn invalidate_pipelines(&mut self, animation: Option<AssetId<Animation>>) {
        for effect in self.effects.iter_mut() {
            if let Some(animation) = animation
                && effect.animation != Some(animation)
            {
                continue;
            }

            effect.invalidate_pipeline();
        }
    }

    /// Build missing thumbnail pipelines on the UI device and rebuild outdated ones.
    pub fn ensure_matrix_pipelines(&mut self, gpu: &Gpu, collections: &Collections) {
        for effect in self.effects.iter_mut() {
            effect.ensure_matrix_pipeline(gpu, collections);
        }
    }

    /// Build missing output pipelines on the output device and rebuild outdated ones.
    pub fn ensure_output_pipelines(&mut self, context: &OutputContext, collections: &Collections) {
        for effect in self.effects.iter_mut() {
            effect.ensure_output_pipeline(context, collections);
        }
    }

    pub fn prepare(
        &mut self,
        queue: &Queue,
        palette: Option<Palette>,
        groups: &Groups,
        main_opacity: f32,
        collections: &Collections,
        sound_data: &SoundData,
    ) {
        for effect in self.effects.iter_mut() {
            effect.prepare(
                queue,
                palette.clone(),
                groups,
                main_opacity,
                collections,
                sound_data,
            );
        }
    }

    #[cfg_attr(feature = "profiling", profiling::function)]
    pub fn render(&mut self, encoder: &mut CommandEncoder, render_matrix: bool, send_output: bool) {
        for effect in self.effects.iter_mut() {
            effect.render(encoder, render_matrix, send_output);
        }
    }
}

impl AssetTrait for Scene {
    const DIR_NAME: &'static str = "scenes";
    const NAME: &'static str = "Scene";
    const SHOW_NAME_IF_SELECTED: bool = true;

    fn show(&self, ui: &mut egui::Ui, rect: egui::Rect) {
        show_pills(
            ui,
            rect.right_top() + Vec2::new(0.0, 1.0),
            self.group_indices()
                .into_iter()
                .map(|index| (index.to_string(), Color32::GOLD))
                .collect(),
        );
    }
}