//! This shows the implementation of undo and redo by moving a simple sprite. //! //! - A or ← : move left //! - W or ↑ : move up //! - D or → : move right //! - S or ↓ : move down //! - Z : Undo //! - X : Redo use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Duration; use bevy::app::{App, Startup, Update}; use bevy::DefaultPlugins; use bevy::math::{Vec2, Vec3}; use bevy::prelude::{Camera2dBundle, Color, Commands, Component, EventWriter, In, KeyCode, LinearRgba, Local, Query, Res, Sprite, Transform, With}; use bevy::sprite::SpriteBundle; use bevy::time::Time; use bevy::utils::default; use bevy_egui::{egui, EguiContexts, EguiPlugin}; use bevy_egui::egui::{Color32, RichText}; use bevy_flurx::actions; use bevy_flurx::prelude::*; #[derive(Component)] struct MrShape; #[derive(Eq, PartialEq, Clone)] struct MoveAct(usize, String); type StartAndEndPos = (Vec3, Vec3); fn main() { App::new() .add_plugins(( DefaultPlugins, EguiPlugin, FlurxPlugin )) .init_resource::>() .add_record_events::() .add_systems(Startup, ( spawn_camera, spawn_mr_shape, spawn_undo_redo_reactor, spawn_move_reactor, )) .add_systems(Update, show_record) .run(); } fn spawn_camera( mut commands: Commands ) { commands.spawn(Camera2dBundle::default()); } fn spawn_mr_shape( mut commands: Commands ) { commands.spawn(( MrShape, SpriteBundle { sprite: Sprite { custom_size: Some(Vec2::new(50., 50.)), color: Color::WHITE, ..default() }, ..default() } )); } fn spawn_undo_redo_reactor( mut commands: Commands ) { commands.spawn(Reactor::schedule(|task| async move { loop { let either = task.will(Update, wait::either( wait::input::just_pressed().with(KeyCode::KeyZ), wait::input::just_pressed().with(KeyCode::KeyX), )).await; if either.is_left() { let _ = task.will(Update, record::undo::once::()).await; } else { let _ = task.will(Update, record::redo::once::()).await; } } })); } fn show_record( mut context: EguiContexts, mut undo: EventWriter>, mut redo: EventWriter>, record: Res>, ) { egui::SidePanel::right("record") .min_width(200.) .show(context.ctx_mut(), |ui| { ui .vertical(|ui| { let size = egui::Vec2::new(ui.available_width(), 30.); for act in record.acts() { let button = egui::Button::new(RichText::new(&act.1).color(Color32::BLACK)) .fill(Color32::GREEN) .min_size(size); if ui.add(button).clicked() { undo.send(RequestUndo::To(act.clone())); } } for act in record.redo_acts().rev() { let button = egui::Button::new(RichText::new(&act.1).color(Color32::WHITE)) .fill(Color32::RED) .min_size(size); if ui.add(button).clicked() { redo.send(RequestRedo::To(act.clone())); } } }); }); } fn spawn_move_reactor( mut commands: Commands ) { commands.spawn(Reactor::schedule(|task| async move { loop { task.will(Update, wait::any().with(actions![ wait::input::any_just_released().with(vec![KeyCode::KeyA, KeyCode::ArrowLeft]), wait::input::any_just_released().with(vec![KeyCode::KeyW, KeyCode::ArrowUp]), wait::input::any_just_released().with(vec![KeyCode::KeyS, KeyCode::ArrowDown]), wait::input::any_just_released().with(vec![KeyCode::KeyD, KeyCode::ArrowRight]), ]) .pipe(return_start_and_end_pos()) .pipe(move_action::<1000>()) .pipe(push_move_track()), ) .await; } })); } fn return_start_and_end_pos() -> ActionSeed { once::run(|In(key_index): In, m: Query<&Transform, With>| { let start = m.single().translation; let output = |end: Vec3| { (start, start + end * 100.) }; match key_index { 0 => output(Vec3::NEG_X), 1 => output(Vec3::Y), 2 => output(Vec3::NEG_Y), _ => output(Vec3::X), } }) } fn move_action() -> ActionSeed { wait::output(|In((start, end)): In, mut m: Query<&mut Transform, With>, mut tick: Local, time: Res