gled-ui 2.31.2

gled's egui interface, shared by the desktop app and the browser
Documentation
use crate::settings::ClientSettings;
use crate::{
    app::{client_timing::ClientTiming, timing::BeatState},
    pipeline::{renderer_callback::RendererCallback, thumbnails::ThumbnailTextures, ui_gpu},
    ui::{
        asset::CollectionsChangeButton,
        asset_tree::{AssetTree, TREE_WIDTH, TreeSelection},
        scene_effect_editor::{
            SceneEffectEditorState, scene_effect_list_ui, selected_effect_editor_ui,
        },
        window_common::{gled_window_frame, raise, tool_window_builder},
    },
    wgpu_render_state,
};
use egui::{
    Color32, Context, Id, Margin, ScrollArea, Stroke, Vec2, ViewportId,
    scroll_area::ScrollBarVisibility::AlwaysVisible,
};
use gled_gpu::{
    effect_state::EffectPipelines,
    scene_render::{
        EvalContext, ScenePaint, ensure_scene_matrix_pipelines, prepare_scene, render_scene,
        scene_effect_keys, set_scene_beat,
    },
};
use gled_model::{
    asset::{Asset, animation::Animation, scene::Scene},
    asset_id::AssetId,
    collection::Collections,
    patching::{Patch, layout::LayoutSelection},
};
use gled_protocol::state::SoundSnapshot;
use wgpu::CommandEncoderDescriptor;

#[derive(Debug, Default)]
pub(crate) struct ScenesWindow {
    open: bool,
    /// Set while the window still has to be brought forward.
    raise: bool,
    dirty: bool,
    tree: AssetTree<Scene>,
    effect_editor: SceneEffectEditorState,
    /// Thumbnail pipelines of the edited scene's effects.
    pipelines: EffectPipelines,
    /// egui textures of those thumbnails.
    textures: ThumbnailTextures,
}

impl ScenesWindow {
    #[cfg_attr(feature = "profiling", profiling::function)]
    pub(crate) fn update(
        &mut self,
        ctx: &Context,
        timing: &ClientTiming,
        collections: &mut Collections,
        settings: &mut ClientSettings,
        sound_data: &SoundSnapshot,
    ) {
        if !self.open {
            return;
        }
        if !self.open {
            return;
        }

        ctx.show_viewport_immediate(
            ViewportId(Id::new("scenes window")),
            tool_window_builder("Scenes")
                .with_inner_size(Vec2::new(950.0, 500.0))
                .with_min_inner_size(Vec2::new(950.0, 500.0)),
            |ctx, _viewport_class| {
                raise(ctx, &mut self.raise);
                ctx.input(|input| {
                    if input.viewport().close_requested()
                        || crate::ui::window_common::embedded_close_clicked("Scenes")
                    {
                        self.open = false;
                    }
                });

                gled_window_frame(ctx, "Scenes", |ui| {
                    egui::Panel::left("scenes tree")
                        .exact_size(TREE_WIDTH)
                        .resizable(false)
                        .show(ui, |ui| {
                            if self
                                .tree
                                .show(ui, ui.make_persistent_id("scenes_tree"), collections)
                            {
                                self.dirty = false;
                                self.effect_editor.reset();
                            }
                        });

                    egui::Panel::right("scene editor")
                        .exact_size(300.0)
                        .resizable(false)
                        .show(ui, |ui| {
                            let TreeSelection::Asset(scene) = self.tree.selected() else {
                                return;
                            };

                            ScrollArea::vertical()
                                .id_salt("scene_editor_scroll")
                                .scroll_bar_visibility(AlwaysVisible)
                                .show(ui, |ui| {
                                    self.dirty |= selected_effect_editor_ui(
                                        ui,
                                        &mut scene.data,
                                        scene.id.id,
                                        &self.textures,
                                        &mut self.effect_editor,
                                        true,
                                        &Patch::default(),
                                        1.0,
                                        collections,
                                        sound_data,
                                    );
                                });
                        });

                    egui::CentralPanel::default().show(ui, |ui| {
                        self.tree.common_settings(ui, &mut self.dirty, collections);

                        ui.add_space(4.0);

                        if let TreeSelection::Asset(asset) = self.tree.selected() {
                            // Render the edited scene itself, so the thumbnails
                            // shown below sample the textures rendered here.
                            let owner = asset.id.id;
                            let scene = &asset.data;
                            self.pipelines.retain(scene_effect_keys(owner, scene));
                            ensure_scene_matrix_pipelines(
                                owner,
                                scene,
                                &mut self.pipelines,
                                &ui_gpu(),
                                collections,
                            );

                            egui::Frame::NONE
                                .inner_margin(Margin::from(6.0))
                                .stroke(Stroke::new(1.0, Color32::DARK_GRAY))
                                .show(ui, |ui| {
                                    ui.label("Preview Palette");
                                    ui.vertical_centered_justified(|ui| {
                                        if settings
                                            .preview_palette_mut()
                                            .collections_change_button(ui, collections)
                                        {
                                        }
                                    });
                                });

                            ui.add_space(4.0);

                            {
                                set_scene_beat(
                                    owner,
                                    scene,
                                    &mut self.pipelines,
                                    &BeatState {
                                        beat_progression: timing.beat_progression(),
                                        beats_per_minute: timing.beats_per_minute(),
                                        framerate: timing.framerate().unwrap_or_default(),
                                    },
                                );

                                let preview_palette = settings
                                    .preview_palette()
                                    .and_then(|id| Asset::get(id, collections));
                                let wgpu_render_state = wgpu_render_state();
                                let device = wgpu_render_state.device;
                                let queue = &wgpu_render_state.queue;
                                prepare_scene(
                                    owner,
                                    scene,
                                    &mut self.pipelines,
                                    queue,
                                    ScenePaint {
                                        palette: preview_palette
                                            .as_ref()
                                            .map(|palette| &palette.data),
                                        selection: LayoutSelection::default(),
                                        opacity: 1.0,
                                    },
                                    EvalContext {
                                        // Only thumbnails render here, which need no lamps.
                                        patch: &Patch::default(),
                                        collections,
                                        sound: sound_data,
                                    },
                                );
                                let mut encoder =
                                    device.create_command_encoder(&CommandEncoderDescriptor {
                                        label: Some("Render animations for scene editor"),
                                    });
                                render_scene(
                                    owner,
                                    scene,
                                    &self.pipelines,
                                    &mut encoder,
                                    true,
                                    false,
                                );
                                RendererCallback::add(encoder.finish());
                                self.textures.sync(&self.pipelines);
                            }

                            ScrollArea::vertical()
                                .id_salt("effects_scroll")
                                .auto_shrink([false, false])
                                .scroll_bar_visibility(AlwaysVisible)
                                .show(ui, |ui| {
                                    ui.set_max_width(ui.available_width() - 30.0);
                                    if let TreeSelection::Asset(scene) = self.tree.selected() {
                                        scene_effect_list_ui(
                                            ui,
                                            &scene.data,
                                            scene.id.id,
                                            &self.textures,
                                            &mut self.effect_editor,
                                            true,
                                            None,
                                            None,
                                            collections,
                                            sound_data,
                                        );
                                    }
                                });
                        }
                    });
                });
            },
        );
    }

    pub(crate) fn open(&mut self) {
        self.open = true;
        self.raise = true;
    }

    /// Rebuild the previews of effects using `animation` (all for `None`) on
    /// the next frame, after the animation's code changed.
    pub(crate) fn invalidate_pipelines(&mut self, animation: Option<AssetId<Animation>>) {
        self.pipelines.invalidate(animation);
    }
}