souprune 0.5.1

A game framework designed specifically for Deltarune / Undertale fangames.
Documentation
//! # image_overlay.rs
//!
//! # image_overlay.rs 文件
//!
//! ## Module Overview
//!
//! ## 模块概述
//!
//! Provides a debug tool to overlay an image on the screen (toggled via F4), useful for comparing in-game visuals with reference assets.
//!
//! 提供一个在屏幕上覆盖图像的调试工具(通过 F4 切换),用于将游戏内视觉效果与参考资产进行对比。

#[cfg(feature = "debug")]
pub mod debug_image_overlay {
    use bevy::prelude::*;
    use std::fs;
    use std::path::Path;
    use std::time::SystemTime;

    /// Resource to control image overlay visibility.
    ///
    /// 控制图像覆盖可见性的调试资源。
    #[derive(Resource, Default)]
    pub struct ImageOverlaySettings {
        pub show_overlay: bool,
    }

    /// Component for the overlay image entity.
    ///
    /// 覆盖图像实体的组件。
    #[derive(Component)]
    pub struct DebugImageOverlay;

    /// Set up the image overlay debug systems.
    ///
    /// 设置图像覆盖调试系统。
    pub fn setup_image_overlay_debug(app: &mut App) {
        app.init_resource::<ImageOverlaySettings>().add_systems(
            Update,
            (toggle_image_overlay_system, maintain_overlay_system),
        );
    }

    /// Toggle the image overlay with the F4 key (debug only).
    ///
    /// F4 键切换图像覆盖的系统(仅调试模式)。
    fn toggle_image_overlay_system(
        keyboard: Res<ButtonInput<KeyCode>>,
        mut settings: ResMut<ImageOverlaySettings>,
        mut commands: Commands,
        asset_server: Res<AssetServer>,
        overlay_query: Query<Entity, With<DebugImageOverlay>>,
        window_query: Query<&Window>,
    ) {
        if keyboard.just_pressed(KeyCode::F4) {
            settings.show_overlay = !settings.show_overlay;

            if settings.show_overlay {
                // Remove any existing overlay entity before spawning a new one.
                //
                // 若已存在覆盖层实体,则在生成新实体前先移除。
                for entity in overlay_query.iter() {
                    commands.entity(entity).despawn();
                }

                // Look up the most recently modified image in the debug folder.
                //
                // 在 debug 文件夹中查找最近修改的图像。
                if let Some(latest_image_path) = find_latest_debug_image() {
                    info!("Loading debug overlay image: {}", latest_image_path);

                    // Load the selected image asset.
                    //
                    // 加载所选的图像资源。
                    let image_handle: Handle<Image> = asset_server.load(&latest_image_path);

                    // Query the current window size for correct scaling.
                    //
                    // 查询当前窗口尺寸以便正确缩放。
                    if let Ok(window) = window_query.single() {
                        let window_width = window.width();
                        let window_height = window.height();

                        // Spawn the overlay node with a semi-transparent background.
                        //
                        // 创建带半透明背景的覆盖层节点。
                        commands
                            .spawn((
                                Name::new("DebugImageOverlay"),
                                DebugImageOverlay,
                                Node {
                                    position_type: PositionType::Absolute,
                                    width: Val::Percent(100.0),
                                    height: Val::Percent(100.0),
                                    top: Val::Px(0.0),
                                    left: Val::Px(0.0),
                                    justify_content: JustifyContent::Center,
                                    align_items: AlignItems::Center,
                                    ..default()
                                },
                                BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.3)),
                                ZIndex(1000),
                            ))
                            .with_children(|parent| {
                                parent.spawn((
                                    ImageNode {
                                        image: image_handle,
                                        // Render the image as semi-transparent.
                                        //
                                        // 以半透明方式渲染图像。
                                        color: Color::srgba(1.0, 1.0, 1.0, 0.7),
                                        ..default()
                                    },
                                    Node {
                                        // Scale the image to fit the window while preserving aspect ratio.
                                        //
                                        // 缩放图像以适配窗口并保持纵横比。
                                        width: Val::Percent(100.0),
                                        height: Val::Percent(100.0),
                                        max_width: Val::Px(window_width),
                                        max_height: Val::Px(window_height),
                                        ..default()
                                    },
                                ));
                            });

                        info!("Debug image overlay: ON");
                    }
                }
            } else {
                // Remove the overlay entity.
                //
                // 移除覆盖层实体。
                for entity in overlay_query.iter() {
                    commands.entity(entity).despawn();
                }
                info!("Debug image overlay: OFF");
            }
        }
    }

    /// Maintain the overlay entity and remove it when needed.
    ///
    /// 维护覆盖层的系统(如需要则移除)。
    fn maintain_overlay_system(
        settings: Res<ImageOverlaySettings>,
        mut commands: Commands,
        overlay_query: Query<Entity, With<DebugImageOverlay>>,
    ) {
        if !settings.show_overlay {
            for entity in overlay_query.iter() {
                commands.entity(entity).despawn();
            }
        }
    }

    /// Find the most recently modified image in the debug folder.
    ///
    /// 查找 debug 文件夹中最近修改的图像。
    fn find_latest_debug_image() -> Option<String> {
        // Check multiple potential debug folder locations.
        //
        // 检查多个可能的 debug 文件夹位置。
        let possible_paths = ["crates/souprune/assets/debug", "assets/debug"];
        let extensions = ["png", "jpg", "jpeg", "gif", "bmp", "tiff"];

        let mut latest_file: Option<(String, SystemTime)> = None;
        let mut found_debug_folder = false;

        for debug_path in &possible_paths {
            if !Path::new(debug_path).exists() {
                continue;
            }

            found_debug_folder = true;

            if let Ok(entries) = fs::read_dir(debug_path) {
                for entry in entries.flatten() {
                    if let Ok(file_type) = entry.file_type()
                        && file_type.is_file()
                        && let Some(file_name) = entry.file_name().to_str()
                    {
                        // Check whether the file uses a supported image extension.
                        //
                        // 判断文件是否使用受支持的图像扩展名。
                        if let Some(extension) = file_name.split('.').next_back()
                            && extensions.contains(&extension.to_lowercase().as_str())
                            && let Ok(metadata) = entry.metadata()
                            && let Ok(modified) = metadata.modified()
                        {
                            let relative_path = format!("debug/{}", file_name);

                            if latest_file.is_none() || latest_file.as_ref().unwrap().1 < modified {
                                latest_file = Some((relative_path, modified));
                            }
                        }
                    }
                }
                // Once files are found in this path we can stop probing others.
                //
                // 在该路径找到文件后即可停止继续检查其他路径。
                if latest_file.is_some() {
                    break;
                }
            }
        }

        if !found_debug_folder {
            warn!("Debug folder not found in any of the expected locations");
            return None;
        }

        if let Some((path, _)) = latest_file {
            info!("Selected latest debug image: {}", path);
            Some(path)
        } else {
            warn!("No image files found in debug folder");
            None
        }
    }
}