nightshade 0.8.2

A cross-platform data-oriented game engine.
Documentation
use crate::mosaic::behavior::{TileBehavior, TileBehaviorWithWorld};
use crate::mosaic::modal::{ModalData, ModalKind, ModalResult, Modals};
use crate::mosaic::widget::{Pane, Widget, WidgetContext};
use crate::prelude::*;

use super::Mosaic;

impl<W: Widget<C, M>, C, M> Mosaic<W, C, M> {
    pub fn show(&mut self, world: &mut World, ui_context: &egui::Context, app: &mut C) {
        self.prepare(world);
        let mut tree = self.tile_tree.take();

        egui::CentralPanel::default()
            .frame(egui::Frame::NONE)
            .show(ui_context, |ui| {
                Self::render_tree(
                    &mut tree,
                    &mut self.behavior,
                    world,
                    app,
                    &mut self.modals,
                    (&mut self.pending_messages, &mut self.incoming_messages),
                    ui,
                );
            });

        self.render_modals(ui_context);
        self.process_pending_removals(world, app);
        self.finish(tree);
    }

    pub fn show_inside(&mut self, world: &mut World, ui: &mut egui::Ui, app: &mut C) {
        self.prepare(world);
        let ui_context = ui.ctx().clone();
        let mut tree = self.tile_tree.take();

        Self::render_tree(
            &mut tree,
            &mut self.behavior,
            world,
            app,
            &mut self.modals,
            (&mut self.pending_messages, &mut self.incoming_messages),
            ui,
        );

        self.render_modals(&ui_context);
        self.process_pending_removals(world, app);
        self.finish(tree);
    }

    fn render_tree(
        tree: &mut Option<egui_tiles::Tree<Pane<W>>>,
        behavior: &mut TileBehavior<W, C, M>,
        world: &mut World,
        app: &mut C,
        modals: &mut Modals,
        messages: (
            &mut Vec<M>,
            &mut std::collections::HashMap<egui_tiles::TileId, Vec<M>>,
        ),
        ui: &mut egui::Ui,
    ) {
        let (pending, incoming) = messages;
        if let Some(tree) = tree {
            {
                let mut behavior_with_world = TileBehaviorWithWorld {
                    behavior: &mut *behavior,
                    world: &mut *world,
                    app: &mut *app,
                    modals: &mut *modals,
                    messages: &mut *pending,
                    incoming_messages: &mut *incoming,
                };
                tree.ui(&mut behavior_with_world, ui);
            }
            Self::process_pending_add(behavior, tree, modals, app, world, pending, incoming);
        }
    }

    pub(super) fn prepare(&mut self, world: &mut World) {
        self.behavior.viewport_rects.clear();

        self.behavior.viewport_textures.clear();
        let source_textures = self
            .viewport_texture_overrides
            .as_ref()
            .unwrap_or(&world.resources.user_interface.viewport_textures);
        self.behavior
            .viewport_textures
            .extend_from_slice(source_textures);

        self.behavior.config = self.config.clone();
        self.behavior.window_index = self.window_index;
        self.behavior.is_active_window = self.is_active_window;

        self.behavior.cached_cameras.clear();
        self.behavior
            .cached_cameras
            .extend(world.query_entities(CAMERA));
        self.behavior.cached_cameras.sort_by_key(|entity| entity.id);

        if let Some(tree) = &self.tile_tree {
            for (_tile_id, tile) in tree.tiles.iter() {
                if let egui_tiles::Tile::Pane(pane) = tile
                    && let Some(camera_entity) =
                        pane.widget.required_camera(&self.behavior.cached_cameras)
                    && !world
                        .resources
                        .user_interface
                        .required_cameras
                        .contains(&camera_entity)
                {
                    world
                        .resources
                        .user_interface
                        .required_cameras
                        .push(camera_entity);
                    world
                        .resources
                        .user_interface
                        .required_camera_sizes
                        .push((0, 0));
                }
            }
        }
    }

    pub(super) fn finish(&mut self, tree: Option<egui_tiles::Tree<Pane<W>>>) {
        self.tile_tree = tree;
        self.incoming_messages.clear();

        if self.behavior.layout_modified {
            self.layout_modified = true;
            self.behavior.layout_modified = false;
        }
    }

    fn process_pending_add(
        behavior: &mut TileBehavior<W, C, M>,
        tree: &mut egui_tiles::Tree<Pane<W>>,
        modals: &mut Modals,
        app: &mut C,
        world: &mut World,
        messages: &mut Vec<M>,
        incoming_messages: &mut std::collections::HashMap<egui_tiles::TileId, Vec<M>>,
    ) {
        if let Some((tile_id, catalog_index)) = behavior.pending_add_widget.take() {
            let catalog = W::catalog();
            if let Some(entry) = catalog.get(catalog_index) {
                let mut new_widget = (entry.create)();
                let new_pane = Pane::new(new_widget.clone());
                let new_tile_id = tree.tiles.insert_pane(new_pane);
                Self::add_to_container(&mut tree.tiles, tile_id, new_tile_id);

                let mut context = WidgetContext {
                    world,
                    selected_viewport_tile: &mut behavior.selected_viewport_tile,
                    viewport_textures: &behavior.viewport_textures,
                    current_tile_id: new_tile_id,
                    modals,
                    app,
                    window_index: behavior.window_index,
                    is_active_window: behavior.is_active_window,
                    cached_cameras: &behavior.cached_cameras,
                    messages,
                    incoming_messages,
                };
                new_widget.on_add(&mut context);

                if let Some(egui_tiles::Tile::Pane(pane)) = tree.tiles.get_mut(new_tile_id) {
                    pane.widget = new_widget;
                }
            }
        }
    }

    fn process_pending_removals(&mut self, world: &mut World, app: &mut C) {
        let removals = std::mem::take(&mut self.behavior.pending_removals);
        for (tile_id, mut widget) in removals {
            let mut context = WidgetContext {
                world: &mut *world,
                selected_viewport_tile: &mut self.behavior.selected_viewport_tile,
                viewport_textures: &self.behavior.viewport_textures,
                current_tile_id: tile_id,
                modals: &mut self.modals,
                app: &mut *app,
                window_index: self.behavior.window_index,
                is_active_window: self.behavior.is_active_window,
                cached_cameras: &self.behavior.cached_cameras,
                messages: &mut self.pending_messages,
                incoming_messages: &mut self.incoming_messages,
            };
            widget.on_remove(&mut context);
        }
    }

    pub(super) fn render_modals(&mut self, ui_context: &egui::Context) {
        self.modals.activate_next_pending();

        if let Some(mut modal_data) = self.modals.active.take() {
            let mut should_close = false;
            let mut confirmed = false;

            egui::Area::new(egui::Id::new("mosaic_modal_backdrop"))
                .fixed_pos(egui::pos2(0.0, 0.0))
                .order(egui::Order::Middle)
                .show(ui_context, |ui| {
                    let screen = ui.ctx().content_rect();
                    ui.painter()
                        .rect_filled(screen, 0.0, egui::Color32::from_black_alpha(128));
                    ui.allocate_rect(screen, egui::Sense::click());
                });

            egui::Window::new(&modal_data.title)
                .collapsible(false)
                .resizable(false)
                .order(egui::Order::Foreground)
                .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0))
                .show(ui_context, |ui| {
                    ui.add_space(8.0);
                    ui.label(&modal_data.body);
                    ui.add_space(8.0);

                    match &mut modal_data.kind {
                        ModalKind::Confirm => {}
                        ModalKind::TextInput { current_text } => {
                            let response = ui.text_edit_singleline(current_text);
                            if response.lost_focus()
                                && ui.input(|input| input.key_pressed(egui::Key::Enter))
                            {
                                confirmed = true;
                                should_close = true;
                            }
                            ui.add_space(8.0);
                        }
                    }

                    ui.horizontal(|ui| {
                        if ui.button(&modal_data.confirm_text).clicked() {
                            confirmed = true;
                            should_close = true;
                        }
                        if ui.button(&modal_data.cancel_text).clicked() {
                            should_close = true;
                        }
                    });

                    if ui.input(|input| input.key_pressed(egui::Key::Escape)) {
                        should_close = true;
                    }
                });

            if should_close {
                let ModalData { id, kind, .. } = modal_data;
                let result = if confirmed {
                    match kind {
                        ModalKind::Confirm => ModalResult::Confirmed,
                        ModalKind::TextInput { current_text } => {
                            ModalResult::TextInput(current_text)
                        }
                    }
                } else {
                    ModalResult::Cancelled
                };
                self.modals.store_result(id, result);
            } else {
                self.modals.active = Some(modal_data);
            }
        }
    }
}