gled-ui 2.32.1

gled's egui interface, shared by the desktop app and the browser
Documentation
pub(crate) mod widget;

use crate::app::controller_feedback;
use crate::ui::animation_editor::AnimationUi;
use crate::ui::asset::CollectionsChangeButton;
use crate::ui::layout_selection::inherited_selection_button;
use crate::ui::multiplied_curve::MultipliedCurveUi;
use egui::{Slider, TextureId};
use gled_model::patching::Patch;
use gled_model::{
    asset::{
        Asset, animation::Animation, midi_controller::MidiEffectParameter, scene::effect::Effect,
    },
    collection::Collections,
};
use gled_protocol::state::SoundSnapshot;
use std::sync::Arc;

/// The settings of an effect.
pub(crate) trait EffectUi {
    /// `thumbnail` is the rendered effect for the argument previews;
    /// `animation_overwrite` replaces the effect's animation while its code is
    /// edited.
    fn config_ui(
        &mut self,
        ui: &mut egui::Ui,
        thumbnail: Option<TextureId>,
        animation_overwrite: Option<&Arc<Asset<Animation>>>,
        allow_animation_change: bool,
        patch: &Patch,
        beat_progression: f32,
        collections: &mut Collections,
        sound_data: &SoundSnapshot,
    ) -> bool;
}

impl EffectUi for Effect {
    /// `thumbnail` is the rendered effect for the argument previews;
    /// `animation_overwrite` replaces the effect's animation while its code is
    /// edited.
    fn config_ui(
        &mut self,
        ui: &mut egui::Ui,
        thumbnail: Option<TextureId>,
        animation_overwrite: Option<&Arc<Asset<Animation>>>,
        allow_animation_change: bool,
        patch: &Patch,
        beat_progression: f32,
        collections: &mut Collections,
        sound_data: &SoundSnapshot,
    ) -> bool {
        let mut changed = false;

        if allow_animation_change {
            ui.label("Animation");
            ui.vertical_centered_justified(|ui| {
                // The pipeline follows the new animation id lazily via
                // `Effect::ensure_pipeline` on the thread that renders it.
                changed |= self.animation.collections_change_button(ui, collections);
            });
        }

        ui.label("Progression");
        let response = ui.vertical_centered_justified(|ui| {
            changed |=
                self.beat_progression
                    .change_button(ui, beat_progression, collections, sound_data);
        });
        outline_if_touched(
            ui,
            MidiEffectParameter::BeatProgression,
            response.response.rect,
        );

        let mut beat_progression = beat_progression;
        beat_progression +=
            self.beat_progression_offset
                .value(beat_progression, collections, sound_data);

        ui.label("Colorshift");
        let response = ui.vertical_centered_justified(|ui| {
            changed |=
                self.color_shift
                    .change_button(ui, beat_progression, collections, sound_data);
        });
        outline_if_touched(ui, MidiEffectParameter::ColorShift, response.response.rect);

        ui.label("Opacity");
        let response = ui.vertical_centered_justified(|ui| {
            changed |= self
                .opacity
                .change_button(ui, beat_progression, collections, sound_data);
        });
        outline_if_touched(ui, MidiEffectParameter::Opacity, response.response.rect);

        ui.label("Beat offset");
        let response = ui.vertical_centered_justified(|ui| {
            changed |= self.beat_progression_offset.change_button(
                ui,
                beat_progression,
                collections,
                sound_data,
            );
        });
        outline_if_touched(ui, MidiEffectParameter::BeatOffset, response.response.rect);

        ui.label("Speed");
        ui.horizontal(|ui| {
            ui.spacing_mut().slider_width = ui.available_width() - 60.0;
            changed |= ui
                .add(
                    Slider::new(&mut self.speed_exponent, -8..=8)
                        .step_by(1.0)
                        .custom_formatter(|n, _| {
                            format!(
                                "{} x",
                                if n > 0.0 {
                                    2i32.pow(n as u32).to_string()
                                } else if n > -0.1 {
                                    "1".to_string()
                                } else {
                                    format!("1/{}", 2i32.pow((-n) as u32))
                                }
                            )
                        })
                        .custom_parser(|s| {
                            let s = s.split(' ').next().unwrap_or(s);
                            let s = s.strip_suffix('x').unwrap_or(s);
                            let n = if let Some(n) =
                                s.strip_prefix("1/").and_then(|s| s.parse::<f32>().ok())
                            {
                                1f32 / n
                            } else {
                                s.parse::<f32>().ok()?
                            };

                            Some(n.log2() as f64)
                        }),
                )
                .changed();
        });

        if let Some(animation) = animation_overwrite
            .cloned()
            .or_else(|| self.animation.and_then(|id| Asset::get(id, collections)))
            && let Some(rendered) = thumbnail
        {
            changed |= animation.data.config_ui(
                &mut self.animation_config,
                ui,
                rendered,
                None,
                beat_progression,
                collections,
                sound_data,
            );
        }

        ui.separator();

        ui.label("Layout");
        ui.vertical_centered_justified(|ui| {
            changed |= inherited_selection_button(ui, patch, &mut self.selection);
        });

        changed
    }
}

/// Outlines the slider of `parameter` at `rect` while a controller's fader
/// is touched on it.
pub(crate) fn outline_if_touched(ui: &egui::Ui, parameter: MidiEffectParameter, rect: egui::Rect) {
    if controller_feedback::shown_effect_touched(ui.ctx(), parameter) {
        controller_feedback::outline_touched(ui, rect);
    }
}