Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Early support for undo in ChangePlugin #257

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
82 changes: 77 additions & 5 deletions rmf_site_editor/src/site/change_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

use crate::site::SiteUpdateSet;
use bevy::prelude::*;
use std::fmt::Debug;
use std::{fmt::Debug, sync::Arc};

use super::{UndoBuffer, UndoEvent};

/// The Change component is used as an event to indicate that the value of a
/// component should change for some entity. Using these events instead of
Expand Down Expand Up @@ -58,28 +60,98 @@ impl<T: Component + Clone + Debug> Default for ChangePlugin<T> {
}
}

/// This is a changelog used for the undo/redo system
struct ChangeLog<T: Component + Clone + Debug>
{
entity: Entity,
from: Option<T>,
to: T
}

#[derive(Resource)]
struct ChangeHistory<T: Component + Clone + Debug> {
pub(crate) revisions: std::collections::HashMap<usize, ChangeLog<T>>
}

impl<T: Component + Clone + Debug> Default for ChangeHistory<T> {
fn default() -> Self {
Self {
revisions: Default::default(),
}
}
}


impl<T: Component + Clone + Debug> Plugin for ChangePlugin<T> {
fn build(&self, app: &mut App) {
app.add_event::<Change<T>>().add_systems(
app.add_event::<Change<T>>()
.init_resource::<ChangeHistory<T>>()
.add_systems(
PreUpdate,
update_changed_values::<T>.in_set(SiteUpdateSet::ProcessChanges),
(update_changed_values::<T>.in_set(SiteUpdateSet::ProcessChanges),
undo_change::<T>.in_set(SiteUpdateSet::ProcessChanges)) // TODO do this on another stage

);
}
}

fn undo_change<T: Component + Clone + Debug>(
mut commands: Commands,
mut values: Query<&mut T>,
change_history: ResMut<ChangeHistory<T>>,
mut undo_cmds: EventReader<UndoEvent>,
) {
for undo in undo_cmds.read() {
let Some(change) = change_history.revisions.get(&undo.action_id) else {
continue;
};

if let Ok(mut component_to_change) = values.get_mut(change.entity) {
if let Some(old_value) = &change.from {
*component_to_change = old_value.clone();
}
else {
commands.entity(change.entity).remove::<T>();
}
}
else {
error!("Undo history corrupted.");
}
}
}

fn update_changed_values<T: Component + Clone + Debug>(
mut commands: Commands,
mut values: Query<&mut T>,
mut changes: EventReader<Change<T>>,
mut undo_buffer: ResMut<UndoBuffer>,
mut change_history: ResMut<ChangeHistory<T>>
) {
for change in changes.read() {
if let Ok(mut new_value) = values.get_mut(change.for_element) {
*new_value = change.to_value.clone();
if let Ok(mut component_to_change) = values.get_mut(change.for_element) {
change_history.revisions.insert(
undo_buffer.get_next_revision(),
ChangeLog {
entity: change.for_element,
to: change.to_value.clone(),
from: Some(component_to_change.clone())
}

);
*component_to_change = change.to_value.clone();
} else {
if change.allow_insert {
commands
.entity(change.for_element)
.insert(change.to_value.clone());
change_history.revisions.insert(
undo_buffer.get_next_revision(),
ChangeLog {
entity: change.for_element,
to: change.to_value.clone(),
from: None
}
);
} else {
error!(
"Unable to change {} data to {:?} for entity {:?} \
Expand Down
4 changes: 4 additions & 0 deletions rmf_site_editor/src/site/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ pub use site_visualizer::*;
pub mod texture;
pub use texture::*;

pub mod undo_plugin;
pub use undo_plugin::*;

pub mod util;
pub use util::*;

Expand Down Expand Up @@ -212,6 +215,7 @@ impl Plugin for SitePlugin {
ChangePlugin::<Scale>::default(),
ChangePlugin::<Distance>::default(),
ChangePlugin::<Texture>::default(),
UndoPlugin::default()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Move to lib.rs in case we want to run in headless mode

))
.add_plugins((
ChangePlugin::<DoorType>::default(),
Expand Down
77 changes: 77 additions & 0 deletions rmf_site_editor/src/site/undo_plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use bevy::{ecs::event, prelude::*};
use bevy_impulse::event_streaming_service;
use std::fmt::Debug;

use crate::{EditMenu, MenuEvent, MenuItem};

#[derive(Event, Debug, Clone, Copy)]
pub struct UndoEvent {
pub action_id: usize
}

#[derive(Resource)]
struct UndoMenu{
menu_entity: Entity
}
///TODO(arjo): Decouple
impl FromWorld for UndoMenu {
fn from_world(world: &mut World) -> Self {
let undo_label = world.spawn(
MenuItem::Text("Undo".into())).id();
let edit_menu = world.resource::<EditMenu>().get();
world.entity_mut(edit_menu).push_children(&[undo_label]);
Self {
menu_entity: undo_label
}
}
}

fn watch_undo_click(
mut reader: EventReader<MenuEvent>,
menu_handle: Res<UndoMenu>,
mut undo_buffer: ResMut<UndoBuffer>,
mut event_writer: EventWriter<UndoEvent>)
{
for menu_click in reader.read() {
if menu_click.clicked() && menu_click.source() == menu_handle.menu_entity {
let Some(undo_item) = undo_buffer.undo_last() else {
continue;
};
event_writer.send(UndoEvent {
action_id: undo_item
});
}
}
}

#[derive(Resource, Default)]
pub struct UndoBuffer {
pub prev_value: usize,
pub undo_stack: Vec<usize>
}

impl UndoBuffer {
pub fn get_next_revision(&mut self) -> usize {
self.prev_value += 1;
self.undo_stack.push(self.prev_value);
self.prev_value
}

pub fn undo_last(&mut self) -> Option<usize> {
self.undo_stack.pop()
}
}

#[derive(Default)]
pub struct UndoPlugin;

impl Plugin for UndoPlugin {
fn build(&self, app: &mut App) {
app
.init_resource::<EditMenu>()
.init_resource::<UndoMenu>()
.init_resource::<UndoBuffer>()
.add_event::<UndoEvent>()
.add_systems(Update,(watch_undo_click));
}
}
24 changes: 24 additions & 0 deletions rmf_site_editor/src/widgets/menu_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,29 @@ impl FromWorld for ViewMenu {
}
}

#[derive(Resource)]
pub struct EditMenu {
/// Map of menu items
menu_item: Entity,
}

impl EditMenu {
pub fn get(&self) -> Entity {
return self.menu_item;
}
}

impl FromWorld for EditMenu {
fn from_world(world: &mut World) -> Self {
let menu_item = world
.spawn(Menu {
text: "Edit".to_string(),
})
.id();
Self { menu_item }
}
}

#[non_exhaustive]
#[derive(Event)]
pub enum MenuEvent {
Expand Down Expand Up @@ -304,6 +327,7 @@ fn top_menu_bar(
true,
);
});

ui.menu_button("View", |ui| {
render_sub_menu(
ui,
Expand Down
Loading