nightshade-editor 0.13.4

An interactive editor for the Nightshade game engine
use crate::app_context::AppContext;
use crate::mosaic::WidgetContext;
use nightshade::prelude::serde::{Deserialize, Serialize};
use nightshade::prelude::*;

use crate::messages::{EditorMessage, SceneAction};

#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(crate = "nightshade::prelude::serde")]
pub struct SceneBrowserWidget {
    #[serde(skip)]
    pub pending_delete: Option<String>,
}

impl SceneBrowserWidget {
    pub(crate) fn render(
        &mut self,
        ui: &mut egui::Ui,
        context: &mut WidgetContext<AppContext, EditorMessage>,
    ) {
        let available_rect = ui.available_rect_before_wrap();
        let tile_id = context.current_tile_id;

        ui.painter()
            .rect_filled(available_rect, 0.0, ui.style().visuals.panel_fill);

        if let Some(scene_name) = self.pending_delete.clone() {
            let escape_pressed = ui.ctx().input(|i| i.key_pressed(egui::Key::Escape));
            let enter_pressed = ui.ctx().input(|i| i.key_pressed(egui::Key::Enter));

            if escape_pressed {
                self.pending_delete = None;
            } else if enter_pressed {
                context.send(EditorMessage::SceneAction(SceneAction::Delete(scene_name)));
                self.pending_delete = None;
            } else {
                let screen_rect = ui
                    .ctx()
                    .input(|i| i.viewport().inner_rect)
                    .unwrap_or(available_rect);
                egui::Area::new(egui::Id::new(("delete_scene_modal_backdrop", tile_id)))
                    .fixed_pos(screen_rect.min)
                    .order(egui::Order::Foreground)
                    .show(ui.ctx(), |ui| {
                        let response =
                            ui.allocate_response(screen_rect.size(), egui::Sense::click());
                        ui.painter().rect_filled(
                            screen_rect,
                            0.0,
                            egui::Color32::from_black_alpha(128),
                        );
                        if response.clicked() {
                            self.pending_delete = None;
                        }
                    });

                egui::Window::new("Delete Scene")
                    .id(egui::Id::new(("delete_scene_dialog", tile_id)))
                    .title_bar(false)
                    .collapsible(false)
                    .resizable(false)
                    .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
                    .order(egui::Order::Foreground)
                    .show(ui.ctx(), |ui| {
                        ui.heading("Delete Scene");
                        ui.add_space(4.0);
                        ui.label(format!("Delete \"{}\"?", scene_name));
                        ui.add_space(8.0);
                        ui.horizontal(|ui| {
                            if ui.button("Cancel").clicked() {
                                self.pending_delete = None;
                            }
                            if ui.button("Delete").clicked() {
                                context.send(EditorMessage::SceneAction(SceneAction::Delete(
                                    scene_name.clone(),
                                )));
                                self.pending_delete = None;
                            }
                        });
                    });
            }
        }

        let has_project = context.app.scene_browser.has_project;
        let scene_count = context.app.scene_browser.scenes.len();

        egui::ScrollArea::vertical()
            .id_salt(("scene_browser_scroll", tile_id))
            .auto_shrink([false, false])
            .show(ui, |ui| {
                ui.set_min_width(available_rect.width());
                ui.add_space(4.0);

                ui.horizontal(|ui| {
                    ui.heading("Scenes");
                    ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                        #[cfg(not(target_arch = "wasm32"))]
                        if ui.button("Export").clicked() {
                            let scene_filters = [nightshade::filesystem::FileFilter {
                                name: "Scene files".to_string(),
                                extensions: vec!["json".to_string(), "bin".to_string()],
                            }];
                            if let Some(path) = nightshade::filesystem::save_file_dialog(
                                &scene_filters,
                                Some("scene.bin"),
                            ) {
                                context.send(EditorMessage::SceneAction(SceneAction::Export(path)));
                            }
                        }
                        #[cfg(not(target_arch = "wasm32"))]
                        if ui
                            .add_enabled(has_project, egui::Button::new("Import"))
                            .clicked()
                        {
                            let scene_filters = [nightshade::filesystem::FileFilter {
                                name: "Scene files".to_string(),
                                extensions: vec!["json".to_string(), "bin".to_string()],
                            }];
                            if let Some(path) = nightshade::filesystem::pick_file(&scene_filters) {
                                context.send(EditorMessage::SceneAction(SceneAction::Import(path)));
                            }
                        }
                        #[cfg(target_arch = "wasm32")]
                        let _ = ui;
                    });
                });

                ui.separator();

                if !has_project {
                    ui.centered_and_justified(|ui| {
                        ui.label("No project loaded");
                    });
                } else if scene_count == 0 {
                    ui.centered_and_justified(|ui| {
                        ui.label("No scenes in project");
                    });
                } else {
                    for scene_index in 0..scene_count {
                        let scene_info = &context.app.scene_browser.scenes[scene_index];
                        let is_active = scene_info.is_active;
                        let entity_count = scene_info.entity_count;
                        let name = scene_info.name.clone();

                        let frame_fill = if is_active {
                            ui.visuals().selection.bg_fill.gamma_multiply(0.5)
                        } else {
                            egui::Color32::TRANSPARENT
                        };

                        egui::Frame::NONE
                            .fill(frame_fill)
                            .inner_margin(egui::Margin::same(8))
                            .show(ui, |ui| {
                                ui.horizontal(|ui| {
                                    let icon = if is_active { "\u{25b6}" } else { "\u{25cb}" };
                                    ui.label(egui::RichText::new(icon).size(14.0));

                                    ui.vertical(|ui| {
                                        let name_text = egui::RichText::new(&name).strong();
                                        if ui.link(name_text).clicked() && !is_active {
                                            context.send(EditorMessage::SceneAction(
                                                SceneAction::SwitchTo(name.clone()),
                                            ));
                                        }

                                        ui.label(
                                            egui::RichText::new(format!(
                                                "{} entities",
                                                entity_count
                                            ))
                                            .small()
                                            .weak(),
                                        );
                                    });

                                    ui.with_layout(
                                        egui::Layout::right_to_left(egui::Align::Center),
                                        |ui| {
                                            let can_delete = scene_count > 1;
                                            if ui
                                                .add_enabled(
                                                    can_delete && !is_active,
                                                    egui::Button::new("\u{1f5d1}").small(),
                                                )
                                                .on_hover_text("Delete scene")
                                                .clicked()
                                            {
                                                self.pending_delete = Some(name.clone());
                                            }
                                        },
                                    );
                                });
                            });

                        ui.add_space(2.0);
                    }
                }
            });
    }
}