Skip to content

Commit bc2c235

Browse files
rewin123ayamaev-sealice-i-cecile
authored
Undo and redo logic with generic atomic actions and auto component change undo/redo (#2)
* Add undo crate * Update and rename READMY.md to README.md * Add example * Updated to bevy 0.14 * fmt * clippy * Some comments * Docs and make lint happy * fmt * clippy * remove default plugins from doc tests * typo * clippy * Typo --------- Co-authored-by: a.yamaev <[email protected]> Co-authored-by: Alice Cecile <[email protected]>
1 parent bbe8f66 commit bc2c235

File tree

3 files changed

+1754
-7
lines changed

3 files changed

+1754
-7
lines changed

crates/bevy_undo/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
[package]
22
name = "bevy_undo"
3+
description = "Subcrate for the editor crate. Contains undo functionality."
34
version = "0.1.0"
45
edition = "2021"
56

7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
69
[dependencies]
10+
bevy = "0.14.0"
11+
pretty-type-name = "1.0.1"
712

813
[lints]
914
workspace = true
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
//! # Undo/Redo Example for Bevy
2+
//!
3+
//! This example demonstrates how to use the undo/redo functionality in a simple Bevy application.
4+
//! It creates a cube that can be moved left and right, with the ability to undo and redo these movements.
5+
//!
6+
//! ## Features
7+
//!
8+
//! - A movable cube controlled by keyboard input
9+
//! - Undo/redo functionality for cube movements
10+
//! - Visual display of the undo history
11+
//!
12+
//! ## Controls
13+
//!
14+
//! - `A`: Move the cube left
15+
//! - `D`: Move the cube right
16+
//! - `Ctrl + Z`: Undo the last movement
17+
//! - `Ctrl + Shift + Z`: Redo the last undone movement
18+
//!
19+
//! ## Code Overview
20+
//!
21+
//! The example consists of several key components:
22+
//!
23+
//! 1. `setup`: Initializes the scene with a cube, camera, and UI text.
24+
//! 2. `move_cube`: Handles the cube movement based on keyboard input.
25+
//! 3. `send_undo_event`: Listens for undo/redo key combinations and sends appropriate events.
26+
//! 4. `write_undo_text`: Updates the UI text to display the current undo history.
27+
//!
28+
//! ## Important Notes
29+
//!
30+
//! - The `UndoMarker` component is added to the cube to enable undo/redo functionality for it.
31+
//! - `OneFrameUndoIgnore` is used to prevent the initial Transform component addition from being recorded in the undo history.
32+
//! - The `auto_reflected_undo::<Transform>()` call sets up automatic undo/redo tracking for the Transform component.
33+
//!
34+
//! ## Running the Example
35+
//!
36+
//! To run this example, ensure you have Bevy and the undo plugin added to your project's dependencies.
37+
//! Then, you can run it using `cargo run --example undo_demo` (assuming you've named this file `examples/undo_demo.rs`).
38+
39+
use bevy::prelude::*;
40+
use bevy_undo::*;
41+
42+
fn main() {
43+
App::new()
44+
.add_plugins(DefaultPlugins)
45+
.add_plugins(UndoPlugin)
46+
.auto_reflected_undo::<Transform>()
47+
.add_systems(Startup, setup)
48+
.add_systems(Update, (move_cube, send_undo_event, write_undo_text))
49+
.run();
50+
}
51+
52+
#[derive(Component)]
53+
struct Controller;
54+
55+
fn setup(
56+
mut cmd: Commands,
57+
mut meshes: ResMut<Assets<Mesh>>,
58+
mut materials: ResMut<Assets<StandardMaterial>>,
59+
) {
60+
cmd.spawn(PbrBundle {
61+
mesh: meshes.add(Mesh::from(Cuboid::from_length(2.0))),
62+
material: materials.add(StandardMaterial {
63+
base_color: Color::srgb(0.3, 0.5, 0.3),
64+
..default()
65+
}),
66+
..default()
67+
})
68+
.insert(Controller)
69+
.insert(UndoMarker) //Only entities with this marker will be able to undo
70+
.insert(OneFrameUndoIgnore::default()); // To prevent adding "Transform add" change in change chain
71+
72+
cmd.spawn(Camera3dBundle {
73+
transform: Transform::from_xyz(0.0, 5.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
74+
..default()
75+
});
76+
77+
cmd.spawn(NodeBundle {
78+
style: Style {
79+
width: Val::Percent(100.0),
80+
height: Val::Percent(100.0),
81+
justify_content: JustifyContent::Start,
82+
align_items: AlignItems::Start,
83+
..default()
84+
},
85+
..default()
86+
})
87+
.with_children(|parent| {
88+
parent.spawn(TextBundle {
89+
text: Text {
90+
sections: vec![],
91+
..default()
92+
},
93+
..default()
94+
});
95+
});
96+
}
97+
98+
fn move_cube(
99+
inputs: Res<ButtonInput<KeyCode>>,
100+
mut query: Query<&mut Transform, With<Controller>>,
101+
time: Res<Time>,
102+
) {
103+
let speed = 10.0;
104+
if inputs.pressed(KeyCode::KeyA) {
105+
for mut transform in &mut query {
106+
transform.translation += Vec3::new(-1.0, 0.0, 0.0) * time.delta_seconds() * speed;
107+
}
108+
}
109+
110+
if inputs.pressed(KeyCode::KeyD) {
111+
for mut transform in &mut query {
112+
transform.translation += Vec3::new(1.0, 0.0, 0.0) * time.delta_seconds() * speed;
113+
}
114+
}
115+
}
116+
117+
fn send_undo_event(mut events: EventWriter<UndoRedo>, inputs: Res<ButtonInput<KeyCode>>) {
118+
if inputs.just_pressed(KeyCode::KeyZ)
119+
&& inputs.pressed(KeyCode::ControlLeft)
120+
&& !inputs.pressed(KeyCode::ShiftLeft)
121+
{
122+
events.send(UndoRedo::Undo);
123+
}
124+
125+
if inputs.just_pressed(KeyCode::KeyZ)
126+
&& inputs.pressed(KeyCode::ControlLeft)
127+
&& inputs.pressed(KeyCode::ShiftLeft)
128+
{
129+
events.send(UndoRedo::Redo);
130+
}
131+
}
132+
133+
fn write_undo_text(
134+
mut query: Query<&mut Text>,
135+
change_chain: Res<ChangeChain>, //Change chain in UndoPlugin
136+
) {
137+
for mut text in &mut query {
138+
text.sections.clear();
139+
text.sections.push(TextSection::new(
140+
"Registered changes\n",
141+
TextStyle::default(),
142+
));
143+
for change in change_chain.changes.iter() {
144+
text.sections.push(TextSection::new(
145+
format!("{}\n", change.debug_text()),
146+
TextStyle::default(),
147+
));
148+
}
149+
}
150+
}

0 commit comments

Comments
 (0)