|
// Source: https://github.com/bevyengine/bevy/blob/main/examples/window/screenshot.rs |
|
use bevy::prelude::*; |
|
use bevy::render::view::screenshot::ScreenshotManager; |
|
use bevy::time::common_conditions::on_timer; |
|
use bevy::utils::Duration; |
|
use bevy::window::PrimaryWindow; |
|
// timestamps: |
|
use chrono::{DateTime, Utc}; |
|
// GIF recordings: |
|
use engiffen::{engiffen, load_images, Quantizer}; |
|
use std::fs; |
|
use std::fs::File; |
|
|
|
// PLUGIN BEGIN |
|
|
|
pub struct WindowDrawPlugin; |
|
|
|
impl Plugin for WindowDrawPlugin { |
|
fn build(&self, app: &mut App) { |
|
app.add_state::<GifRecordingState>() |
|
.add_systems( |
|
Update, |
|
( |
|
record_gif_on_f11, |
|
record_gif_generate_temp_frames |
|
.run_if(on_timer(Duration::from_secs_f64(0.1))) |
|
.run_if(in_state(GifRecordingState::Recording)), |
|
take_screenshot_on_f12, |
|
), |
|
) |
|
.add_systems( |
|
OnExit(GifRecordingState::Recording), |
|
record_gif_convert_pngs_to_gif, |
|
); |
|
} |
|
} |
|
|
|
// PLUGIN END |
|
|
|
// RESOURCES |
|
|
|
// STATES |
|
#[derive(States, Debug, Clone, Copy, Eq, PartialEq, Hash, Default)] |
|
pub enum GifRecordingState { |
|
#[default] |
|
Available, |
|
Processing, |
|
Recording, |
|
} |
|
|
|
// COMPONENTS |
|
|
|
// HELPERS |
|
|
|
// SYSTEMS |
|
fn record_gif_on_f11( |
|
input: Res<Input<KeyCode>>, |
|
state: Res<State<GifRecordingState>>, |
|
mut next_state: ResMut<NextState<GifRecordingState>>, |
|
) { |
|
if input.just_pressed(KeyCode::F11) { |
|
next_state.set(match state.get() { |
|
GifRecordingState::Available => GifRecordingState::Recording, |
|
GifRecordingState::Recording => GifRecordingState::Processing, |
|
GifRecordingState::Processing => GifRecordingState::Processing, |
|
}); |
|
} |
|
} |
|
|
|
fn record_gif_generate_temp_frames( |
|
main_window: Query<Entity, With<PrimaryWindow>>, |
|
mut screenshot_manager: ResMut<ScreenshotManager>, |
|
mut counter: Local<u32>, |
|
) { |
|
let path = format!("./screenshots/temp/temp_{}.png", *counter); |
|
*counter += 1; |
|
screenshot_manager |
|
.save_screenshot_to_disk(main_window.single(), path) |
|
.unwrap(); |
|
} |
|
|
|
fn record_gif_convert_pngs_to_gif(mut next_state: ResMut<NextState<GifRecordingState>>) { |
|
convert_pngs_to_gif(); |
|
next_state.set(GifRecordingState::Available); |
|
} |
|
|
|
fn convert_pngs_to_gif() { |
|
let mut frames_paths = Vec::new(); |
|
for file in fs::read_dir("./screenshots/temp/").unwrap() { |
|
frames_paths.push(file.unwrap().path()) |
|
} |
|
frames_paths.sort(); |
|
let now: DateTime<Utc> = Utc::now(); |
|
|
|
let images = load_images(&frames_paths); |
|
if let Ok(mut output) = File::create(format!("./screenshots/{}.gif", now)) { |
|
// encode an animated gif at 10 frames per second |
|
let gif = engiffen(&images, 10, Quantizer::Naive).expect("GIF creation failure!"); |
|
gif.write(&mut output).expect("Error writing to file"); |
|
info!("GIF animation saved: {:?}", output); |
|
|
|
// Remove PNG files after creating the GIF |
|
for png_path in &frames_paths { |
|
if let Err(err) = fs::remove_file(png_path) { |
|
eprintln!("Error removing PNG file {}: {}", png_path.display(), err); |
|
// Handle the error as needed |
|
} |
|
} |
|
} else { |
|
eprintln!("Error creating file"); |
|
// Handle the error as needed |
|
} |
|
} |
|
|
|
fn take_screenshot_on_f12( |
|
input: Res<Input<KeyCode>>, |
|
main_window: Query<Entity, With<PrimaryWindow>>, |
|
mut screenshot_manager: ResMut<ScreenshotManager>, |
|
) { |
|
if input.just_pressed(KeyCode::F12) { |
|
info!("Screenshot taken."); |
|
let now: DateTime<Utc> = Utc::now(); |
|
let path = format!("./screenshots/{}.png", now); |
|
screenshot_manager |
|
.save_screenshot_to_disk(main_window.single(), path) |
|
.unwrap(); |
|
} |
|
} |