nightshade 0.8.0

A cross-platform data-oriented game engine.
Documentation
use crate::prelude::*;

use super::widget::WidgetContext;

#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct ViewportWidget {
    pub camera_index: usize,
}

impl ViewportWidget {
    pub fn title(&self) -> String {
        format!("Viewport {}", self.camera_index + 1)
    }

    pub fn required_camera(&self, cameras: &[Entity]) -> Option<Entity> {
        cameras.get(self.camera_index).copied()
    }

    pub fn ui<C, M>(&mut self, ui: &mut egui::Ui, context: &mut WidgetContext<C, M>) {
        let rect = ui.available_rect_before_wrap();
        let tile_id = context.current_tile_id;

        let cameras = context.cached_cameras;
        if !cameras.is_empty() && self.camera_index >= cameras.len() {
            self.camera_index = cameras.len() - 1;
        }
        let camera_entity = cameras.get(self.camera_index).copied();

        let pixels_per_point = ui.ctx().pixels_per_point();

        let screen_rect = ui.ctx().content_rect();
        let (viewport_width, viewport_height) = if let Some((physical_width, physical_height)) =
            context.world().resources.window.cached_viewport_size
        {
            let scale_x = physical_width as f32 / screen_rect.width();
            let scale_y = physical_height as f32 / screen_rect.height();
            (
                (rect.width() * scale_x).round() as u32,
                (rect.height() * scale_y).round() as u32,
            )
        } else {
            (
                (rect.width() * pixels_per_point).round() as u32,
                (rect.height() * pixels_per_point).round() as u32,
            )
        };

        let texture_index = camera_entity.and_then(|camera| {
            context
                .world()
                .resources
                .user_interface
                .required_cameras
                .iter()
                .position(|&entity| entity == camera)
        });

        if let Some(index) = texture_index {
            context
                .world_mut()
                .resources
                .user_interface
                .required_camera_sizes[index] = (viewport_width, viewport_height);
        }

        let clicked = if let Some(index) = texture_index
            && let Some(texture_id) = context.viewport_textures.get(index)
        {
            let image = egui::Image::new(egui::load::SizedTexture::new(
                *texture_id,
                egui::vec2(rect.width(), rect.height()),
            ))
            .fit_to_exact_size(egui::vec2(rect.width(), rect.height()))
            .sense(egui::Sense::click());

            ui.put(rect, image).clicked()
        } else {
            ui.allocate_rect(rect, egui::Sense::click()).clicked()
        };

        if context.selected_viewport_tile.is_none() {
            *context.selected_viewport_tile = Some(tile_id);
            if let Some(camera) = camera_entity {
                context.world_mut().resources.active_camera = Some(camera);
            }
        }

        if clicked {
            *context.selected_viewport_tile = Some(tile_id);
            if let Some(camera) = camera_entity {
                context.world_mut().resources.active_camera = Some(camera);
            }
        }

        let is_selected = *context.selected_viewport_tile == Some(tile_id);
        if is_selected {
            if context.is_active_window {
                context.world_mut().resources.window.active_viewport_rect = Some(ViewportRect {
                    x: rect.min.x * pixels_per_point,
                    y: rect.min.y * pixels_per_point,
                    width: rect.width() * pixels_per_point,
                    height: rect.height() * pixels_per_point,
                });
                if let Some(camera) = camera_entity {
                    context.world_mut().resources.active_camera = Some(camera);
                }
            }

            ui.painter().rect_stroke(
                rect,
                egui::CornerRadius::ZERO,
                egui::Stroke::new(3.0, egui::Color32::from_rgb(255, 165, 0)),
                egui::StrokeKind::Inside,
            );
        }

        if cameras.len() > 1 {
            let selector_width = 120.0;
            let selector_height = 24.0;
            let margin = 8.0;
            let selector_rect = egui::Rect::from_min_size(
                egui::pos2(rect.min.x + margin, rect.min.y + margin),
                egui::vec2(selector_width, selector_height),
            );

            let mut child_ui = ui.new_child(
                egui::UiBuilder::new()
                    .max_rect(selector_rect)
                    .layout(egui::Layout::left_to_right(egui::Align::Center)),
            );

            let current_camera_name = camera_entity
                .and_then(|entity| context.world().get_name(entity))
                .map(|name| name.0.clone())
                .unwrap_or_else(|| format!("Camera {}", self.camera_index + 1));

            egui::ComboBox::from_id_salt(("camera_selector", tile_id))
                .selected_text(&current_camera_name)
                .width(selector_width - 8.0)
                .show_ui(&mut child_ui, |ui| {
                    for (index, &entity) in cameras.iter().enumerate() {
                        let name = context
                            .world()
                            .get_name(entity)
                            .map(|name| name.0.clone())
                            .unwrap_or_else(|| format!("Camera {}", index + 1));

                        if ui
                            .selectable_label(self.camera_index == index, &name)
                            .clicked()
                        {
                            self.camera_index = index;
                        }
                    }
                });
        }
    }
}