gled-ui 2.31.0

gled's egui interface, shared by the desktop app and the browser
Documentation
use crate::ui::layout_selection::selection_label;
use crate::ui::pills::show_pills;
use egui::{
    Button, Color32, CornerRadius, Margin, Rect, Response, Sense, Shape, Ui, Vec2, Widget, pos2,
};
use epaint::RectShape;
use gled_model::{asset::scene::effect::Effect, collection::Collections, patching::Patch};
use gled_protocol::state::SoundSnapshot;

impl std::fmt::Debug for EffectWidget<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("EffectWidget")
            .field("effect", &self.effect)
            .finish_non_exhaustive()
    }
}

pub(crate) struct EffectWidget<'a> {
    pub(crate) selectable: Option<(&'a mut usize, usize)>,
    pub(crate) show_group: bool,
    pub(crate) effect: &'a Effect,
    /// The rendered effect, once its pipeline exists.
    pub(crate) thumbnail: Option<egui::TextureId>,
    /// The patch whose layouts the effect may pick, for its badge.
    pub(crate) patch: Option<&'a Patch>,
    /// used for showing the dimmer
    pub(crate) beat_progression: Option<f32>,
    pub(crate) collections: &'a Collections,
    pub(crate) sound_data: &'a SoundSnapshot,
}

impl Widget for EffectWidget<'_> {
    fn ui(self, ui: &mut Ui) -> Response {
        let frame = if let Some((selected_effect, index)) = &self.selectable {
            egui::Frame::NONE
                .fill(if *selected_effect == index {
                    Color32::GOLD
                } else {
                    Color32::TRANSPARENT
                })
                .inner_margin(Margin::from(10.0))
                .corner_radius(CornerRadius::from(4.0))
        } else {
            egui::Frame::NONE
                .inner_margin(Margin::from(10.0))
                .corner_radius(CornerRadius::from(4.0))
        };

        let response = frame
            .show(ui, |ui| {
                let mut bg_rect = ui.available_rect_before_wrap() + Margin::same(5);
                ui.painter()
                    .rect_filled(bg_rect, CornerRadius::ZERO, Color32::BLACK);
                if let Some(beat_progression) = self.beat_progression {
                    let dimmer = self.effect.opacity.value(
                        beat_progression,
                        self.collections,
                        self.sound_data,
                    );
                    bg_rect.min.y += (bg_rect.height() * (1.0 - dimmer)).round().max(0.0);
                    ui.painter()
                        .rect_filled(bg_rect, CornerRadius::ZERO, Color32::DARK_GREEN);
                }

                let rect = ui.available_rect_before_wrap();
                ui.allocate_rect(rect, Sense::hover());
                ui.painter().add(Shape::Rect(RectShape::filled(
                    rect,
                    CornerRadius::default(),
                    Color32::BLACK,
                )));

                ui.painter().add(Shape::Rect({
                    let mut shape =
                        RectShape::filled(rect, CornerRadius::default(), Color32::WHITE);
                    if let Some(texture_id) = self.thumbnail {
                        shape = shape.with_texture(
                            texture_id,
                            Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
                        );
                    }
                    shape
                }));
                // only an effect that overrides its tile earns a badge
                if self.show_group
                    && let (Some(patch), Some(selection)) = (self.patch, self.effect.selection)
                {
                    show_pills(
                        ui,
                        rect.right_top() + Vec2::new(0.0, 5.0),
                        vec![(selection_label(patch, selection), Color32::GOLD)],
                    );
                }
            })
            .response;

        if let Some((selected_effect, index)) = self.selectable {
            let res = ui.put(response.rect, Button::new("").fill(Color32::TRANSPARENT));
            if res.clicked() {
                *selected_effect = index;
            }
        }

        response
    }
}