Implement Undo and Redo (#354)
* Implement undo and redo * Create more save points and hook up menu entry * Fix operation ordering * Remove debug statement * Fix folder changed order * Don't store overlays in the history chain * Keep selection
This commit is contained in:
parent
47fbb8d0fa
commit
0ccb181e2c
|
|
@ -19,6 +19,8 @@ use std::collections::VecDeque;
|
||||||
|
|
||||||
use super::movement_handler::{MovementMessage, MovementMessageHandler};
|
use super::movement_handler::{MovementMessage, MovementMessageHandler};
|
||||||
|
|
||||||
|
type DocumentSave = (InternalDocument, HashMap<Vec<LayerId>, LayerData>);
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||||
pub enum FlipAxis {
|
pub enum FlipAxis {
|
||||||
X,
|
X,
|
||||||
|
|
@ -42,7 +44,8 @@ pub enum AlignAggregate {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DocumentMessageHandler {
|
pub struct DocumentMessageHandler {
|
||||||
pub document: InternalDocument,
|
pub document: InternalDocument,
|
||||||
pub document_backup: Option<InternalDocument>,
|
pub document_history: Vec<DocumentSave>,
|
||||||
|
pub document_redo_history: Vec<DocumentSave>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub layer_data: HashMap<Vec<LayerId>, LayerData>,
|
pub layer_data: HashMap<Vec<LayerId>, LayerData>,
|
||||||
movement_handler: MovementMessageHandler,
|
movement_handler: MovementMessageHandler,
|
||||||
|
|
@ -52,7 +55,8 @@ impl Default for DocumentMessageHandler {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
document: InternalDocument::default(),
|
document: InternalDocument::default(),
|
||||||
document_backup: None,
|
document_history: Vec::new(),
|
||||||
|
document_redo_history: Vec::new(),
|
||||||
name: String::from("Untitled Document"),
|
name: String::from("Untitled Document"),
|
||||||
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
||||||
movement_handler: MovementMessageHandler::default(),
|
movement_handler: MovementMessageHandler::default(),
|
||||||
|
|
@ -90,6 +94,10 @@ pub enum DocumentMessage {
|
||||||
SaveDocument,
|
SaveDocument,
|
||||||
RenderDocument,
|
RenderDocument,
|
||||||
Undo,
|
Undo,
|
||||||
|
Redo,
|
||||||
|
DocumentHistoryBackward,
|
||||||
|
DocumentHistoryForward,
|
||||||
|
ClearOverlays,
|
||||||
NudgeSelectedLayers(f64, f64),
|
NudgeSelectedLayers(f64, f64),
|
||||||
AlignSelectedLayers(AlignAxis, AlignAggregate),
|
AlignSelectedLayers(AlignAxis, AlignAggregate),
|
||||||
MoveSelectedLayersTo {
|
MoveSelectedLayersTo {
|
||||||
|
|
@ -195,7 +203,8 @@ impl DocumentMessageHandler {
|
||||||
pub fn with_name(name: String) -> Self {
|
pub fn with_name(name: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
document: InternalDocument::default(),
|
document: InternalDocument::default(),
|
||||||
document_backup: None,
|
document_history: Vec::new(),
|
||||||
|
document_redo_history: Vec::new(),
|
||||||
name,
|
name,
|
||||||
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
||||||
movement_handler: MovementMessageHandler::default(),
|
movement_handler: MovementMessageHandler::default(),
|
||||||
|
|
@ -219,18 +228,42 @@ impl DocumentMessageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn backup(&mut self) {
|
pub fn backup(&mut self) {
|
||||||
self.document_backup = Some(self.document.clone())
|
self.document_redo_history.clear();
|
||||||
|
let new_layer_data = self
|
||||||
|
.layer_data
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(key, value)| (!self.document.layer(key).unwrap().overlay).then(|| (key.clone(), *value)))
|
||||||
|
.collect();
|
||||||
|
self.document_history.push((self.document.clone_without_overlays(), new_layer_data))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rollback(&mut self) -> Result<(), EditorError> {
|
pub fn rollback(&mut self) -> Result<(), EditorError> {
|
||||||
self.backup();
|
self.backup();
|
||||||
self.reset()
|
self.undo()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) -> Result<(), EditorError> {
|
pub fn undo(&mut self) -> Result<(), EditorError> {
|
||||||
match self.document_backup.take() {
|
match self.document_history.pop() {
|
||||||
Some(backup) => {
|
Some((document, layer_data)) => {
|
||||||
self.document = backup;
|
let document = std::mem::replace(&mut self.document, document);
|
||||||
|
let layer_data = std::mem::replace(&mut self.layer_data, layer_data);
|
||||||
|
self.document_redo_history.push((document, layer_data));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
None => Err(EditorError::NoTransactionInProgress),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redo(&mut self) -> Result<(), EditorError> {
|
||||||
|
match self.document_redo_history.pop() {
|
||||||
|
Some((document, layer_data)) => {
|
||||||
|
let document = std::mem::replace(&mut self.document, document);
|
||||||
|
let layer_data = std::mem::replace(&mut self.layer_data, layer_data);
|
||||||
|
let new_layer_data = layer_data
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(key, value)| (!self.document.layer(key).unwrap().overlay).then(|| (key.clone(), *value)))
|
||||||
|
.collect();
|
||||||
|
self.document_history.push((document.clone_without_overlays(), new_layer_data));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
None => Err(EditorError::NoTransactionInProgress),
|
None => Err(EditorError::NoTransactionInProgress),
|
||||||
|
|
@ -284,10 +317,10 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
responses.extend([DocumentMessage::RenderDocument.into(), self.handle_folder_changed(vec![]).unwrap()]);
|
responses.extend([DocumentMessage::RenderDocument.into(), self.handle_folder_changed(vec![]).unwrap()]);
|
||||||
}
|
}
|
||||||
AbortTransaction => {
|
AbortTransaction => {
|
||||||
self.reset().unwrap_or_else(|e| log::warn!("{}", e));
|
self.undo().unwrap_or_else(|e| log::warn!("{}", e));
|
||||||
responses.extend([DocumentMessage::RenderDocument.into(), self.handle_folder_changed(vec![]).unwrap()]);
|
responses.extend([DocumentMessage::RenderDocument.into(), self.handle_folder_changed(vec![]).unwrap()]);
|
||||||
}
|
}
|
||||||
CommitTransaction => self.document_backup = None,
|
CommitTransaction => (),
|
||||||
ExportDocument => {
|
ExportDocument => {
|
||||||
let bbox = self.document.visible_layers_bounding_box().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]);
|
let bbox = self.document.visible_layers_bounding_box().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]);
|
||||||
let size = bbox[1] - bbox[0];
|
let size = bbox[1] - bbox[0];
|
||||||
|
|
@ -325,11 +358,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SetBlendModeForSelectedLayers(blend_mode) => {
|
SetBlendModeForSelectedLayers(blend_mode) => {
|
||||||
|
self.backup();
|
||||||
for path in self.layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) {
|
for path in self.layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) {
|
||||||
responses.push_back(DocumentOperation::SetLayerBlendMode { path, blend_mode }.into());
|
responses.push_back(DocumentOperation::SetLayerBlendMode { path, blend_mode }.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetOpacityForSelectedLayers(opacity) => {
|
SetOpacityForSelectedLayers(opacity) => {
|
||||||
|
self.backup();
|
||||||
let opacity = opacity.clamp(0., 1.);
|
let opacity = opacity.clamp(0., 1.);
|
||||||
|
|
||||||
for path in self.selected_layers().cloned() {
|
for path in self.selected_layers().cloned() {
|
||||||
|
|
@ -345,12 +380,20 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
}
|
}
|
||||||
SelectionChanged => responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into()),
|
SelectionChanged => responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into()),
|
||||||
DeleteSelectedLayers => {
|
DeleteSelectedLayers => {
|
||||||
|
self.backup();
|
||||||
|
responses.push_front(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||||
for path in self.selected_layers().cloned() {
|
for path in self.selected_layers().cloned() {
|
||||||
responses.push_back(DocumentOperation::DeleteLayer { path }.into())
|
responses.push_front(DocumentOperation::DeleteLayer { path }.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClearOverlays => {
|
||||||
|
responses.push_front(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||||
|
for path in self.layer_data.keys().filter(|path| self.document.layer(path).unwrap().overlay).cloned() {
|
||||||
|
responses.push_front(DocumentOperation::DeleteLayer { path }.into())
|
||||||
}
|
}
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
|
||||||
}
|
}
|
||||||
DuplicateSelectedLayers => {
|
DuplicateSelectedLayers => {
|
||||||
|
self.backup();
|
||||||
for path in self.selected_layers_sorted() {
|
for path in self.selected_layers_sorted() {
|
||||||
responses.push_back(DocumentOperation::DuplicateLayer { path }.into())
|
responses.push_back(DocumentOperation::DuplicateLayer { path }.into())
|
||||||
}
|
}
|
||||||
|
|
@ -365,7 +408,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
}
|
}
|
||||||
// TODO: Correctly update layer panel in clear_selection instead of here
|
// TODO: Correctly update layer panel in clear_selection instead of here
|
||||||
responses.extend(self.handle_folder_changed(Vec::new()));
|
responses.extend(self.handle_folder_changed(Vec::new()));
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_front(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||||
}
|
}
|
||||||
SelectAllLayers => {
|
SelectAllLayers => {
|
||||||
let all_layer_paths = self
|
let all_layer_paths = self
|
||||||
|
|
@ -374,16 +417,26 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
.filter(|path| !path.is_empty() && !self.document.layer(path).unwrap().overlay)
|
.filter(|path| !path.is_empty() && !self.document.layer(path).unwrap().overlay)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
responses.push_back(SetSelectedLayers(all_layer_paths).into());
|
responses.push_front(SetSelectedLayers(all_layer_paths).into());
|
||||||
}
|
}
|
||||||
DeselectAllLayers => {
|
DeselectAllLayers => {
|
||||||
responses.push_back(SetSelectedLayers(vec![]).into());
|
responses.push_front(SetSelectedLayers(vec![]).into());
|
||||||
}
|
}
|
||||||
|
DocumentHistoryBackward => self.undo().unwrap_or_else(|e| log::warn!("{}", e)),
|
||||||
|
DocumentHistoryForward => self.redo().unwrap_or_else(|e| log::warn!("{}", e)),
|
||||||
Undo => {
|
Undo => {
|
||||||
// this is a temporary fix and will be addressed by #123
|
responses.push_back(SelectMessage::Abort.into());
|
||||||
if let Some(id) = self.document.root.as_folder().unwrap().list_layers().last() {
|
responses.push_back(DocumentHistoryBackward.into());
|
||||||
responses.push_back(DocumentOperation::DeleteLayer { path: vec![*id] }.into())
|
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||||
}
|
responses.push_back(RenderDocument.into());
|
||||||
|
responses.push_back(FolderChanged(vec![]).into());
|
||||||
|
}
|
||||||
|
Redo => {
|
||||||
|
responses.push_back(SelectMessage::Abort.into());
|
||||||
|
responses.push_back(DocumentHistoryForward.into());
|
||||||
|
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||||
|
responses.push_back(RenderDocument.into());
|
||||||
|
responses.push_back(FolderChanged(vec![]).into());
|
||||||
}
|
}
|
||||||
FolderChanged(path) => responses.extend(self.handle_folder_changed(path)),
|
FolderChanged(path) => responses.extend(self.handle_folder_changed(path)),
|
||||||
DispatchOperation(op) => match self.document.handle_operation(&op) {
|
DispatchOperation(op) => match self.document.handle_operation(&op) {
|
||||||
|
|
@ -446,6 +499,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
}
|
}
|
||||||
|
|
||||||
NudgeSelectedLayers(x, y) => {
|
NudgeSelectedLayers(x, y) => {
|
||||||
|
self.backup();
|
||||||
for path in self.selected_layers().cloned() {
|
for path in self.selected_layers().cloned() {
|
||||||
let operation = DocumentOperation::TransformLayerInViewport {
|
let operation = DocumentOperation::TransformLayerInViewport {
|
||||||
path,
|
path,
|
||||||
|
|
@ -461,6 +515,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
responses.push_back(DocumentsMessage::PasteLayers { path, insert_index }.into());
|
responses.push_back(DocumentsMessage::PasteLayers { path, insert_index }.into());
|
||||||
}
|
}
|
||||||
ReorderSelectedLayers(relative_position) => {
|
ReorderSelectedLayers(relative_position) => {
|
||||||
|
self.backup();
|
||||||
let all_layer_paths = self.all_layers_sorted();
|
let all_layer_paths = self.all_layers_sorted();
|
||||||
let selected_layers = self.selected_layers_sorted();
|
let selected_layers = self.selected_layers_sorted();
|
||||||
if let Some(pivot) = match relative_position.signum() {
|
if let Some(pivot) = match relative_position.signum() {
|
||||||
|
|
@ -491,6 +546,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FlipSelectedLayers(axis) => {
|
FlipSelectedLayers(axis) => {
|
||||||
|
self.backup();
|
||||||
let scale = match axis {
|
let scale = match axis {
|
||||||
FlipAxis::X => DVec2::new(-1., 1.),
|
FlipAxis::X => DVec2::new(-1., 1.),
|
||||||
FlipAxis::Y => DVec2::new(1., -1.),
|
FlipAxis::Y => DVec2::new(1., -1.),
|
||||||
|
|
@ -512,6 +568,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AlignSelectedLayers(axis, aggregate) => {
|
AlignSelectedLayers(axis, aggregate) => {
|
||||||
|
self.backup();
|
||||||
let (paths, boxes): (Vec<_>, Vec<_>) = self.selected_layers().filter_map(|path| self.document.viewport_bounding_box(path).ok()?.map(|b| (path, b))).unzip();
|
let (paths, boxes): (Vec<_>, Vec<_>) = self.selected_layers().filter_map(|path| self.document.viewport_bounding_box(path).ok()?.map(|b| (path, b))).unzip();
|
||||||
|
|
||||||
let axis = match axis {
|
let axis = match axis {
|
||||||
|
|
@ -550,6 +607,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
fn actions(&self) -> ActionList {
|
fn actions(&self) -> ActionList {
|
||||||
let mut common = actions!(DocumentMessageDiscriminant;
|
let mut common = actions!(DocumentMessageDiscriminant;
|
||||||
Undo,
|
Undo,
|
||||||
|
Redo,
|
||||||
SelectAllLayers,
|
SelectAllLayers,
|
||||||
DeselectAllLayers,
|
DeselectAllLayers,
|
||||||
RenderDocument,
|
RenderDocument,
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,7 @@ impl Default for Mapping {
|
||||||
// Editor Actions
|
// Editor Actions
|
||||||
entry! {action=FrontendMessage::OpenDocumentBrowse, key_down=KeyO, modifiers=[KeyControl]},
|
entry! {action=FrontendMessage::OpenDocumentBrowse, key_down=KeyO, modifiers=[KeyControl]},
|
||||||
// Document Actions
|
// Document Actions
|
||||||
|
entry! {action=DocumentMessage::Redo, key_down=KeyZ, modifiers=[KeyControl, KeyShift]},
|
||||||
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
||||||
entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]},
|
entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]},
|
||||||
entry! {action=DocumentMessage::SelectAllLayers, key_down=KeyA, modifiers=[KeyControl]},
|
entry! {action=DocumentMessage::SelectAllLayers, key_down=KeyA, modifiers=[KeyControl]},
|
||||||
|
|
|
||||||
|
|
@ -89,9 +89,9 @@ impl SelectToolData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_boundnig_box(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
fn add_boundnig_box(responses: &mut Vec<Message>) -> Vec<LayerId> {
|
||||||
let path = vec![generate_uuid()];
|
let path = vec![generate_uuid()];
|
||||||
responses.push_back(
|
responses.push(
|
||||||
Operation::AddBoundingBox {
|
Operation::AddBoundingBox {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
transform: DAffine2::ZERO.to_cols_array(),
|
transform: DAffine2::ZERO.to_cols_array(),
|
||||||
|
|
@ -124,22 +124,25 @@ impl Fsm for SelectToolFsmState {
|
||||||
if let ToolMessage::Select(event) = event {
|
if let ToolMessage::Select(event) = event {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(_, UpdateSelectionBoundingBox) => {
|
(_, UpdateSelectionBoundingBox) => {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
let response = match (document.selected_layers_bounding_box(), data.bounding_box_id.take()) {
|
let response = match (document.selected_layers_bounding_box(), data.bounding_box_id.take()) {
|
||||||
(None, Some(path)) => Operation::DeleteLayer { path }.into(),
|
(None, Some(path)) => Operation::DeleteLayer { path }.into(),
|
||||||
(Some([pos1, pos2]), path) => {
|
(Some([pos1, pos2]), path) => {
|
||||||
let path = path.unwrap_or_else(|| add_boundnig_box(responses));
|
let path = path.unwrap_or_else(|| add_boundnig_box(&mut buffer));
|
||||||
data.bounding_box_id = Some(path.clone());
|
data.bounding_box_id = Some(path.clone());
|
||||||
let transform = transform_from_box(pos1, pos2);
|
let transform = transform_from_box(pos1, pos2);
|
||||||
Operation::SetLayerTransformInViewport { path, transform }.into()
|
Operation::SetLayerTransformInViewport { path, transform }.into()
|
||||||
}
|
}
|
||||||
(_, _) => Message::NoOp,
|
(_, _) => Message::NoOp,
|
||||||
};
|
};
|
||||||
responses.push_back(response);
|
responses.push_front(response);
|
||||||
|
buffer.into_iter().rev().for_each(|message| responses.push_front(message));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(Ready, DragStart { add_to_selection }) => {
|
(Ready, DragStart { add_to_selection }) => {
|
||||||
data.drag_start = input.mouse.position;
|
data.drag_start = input.mouse.position;
|
||||||
data.drag_current = input.mouse.position;
|
data.drag_current = input.mouse.position;
|
||||||
|
let mut buffer = Vec::new();
|
||||||
let mut selected: Vec<_> = document.selected_layers().cloned().collect();
|
let mut selected: Vec<_> = document.selected_layers().cloned().collect();
|
||||||
let quad = data.selection_quad();
|
let quad = data.selection_quad();
|
||||||
let intersection = document.document.intersects_quad_root(quad);
|
let intersection = document.document.intersects_quad_root(quad);
|
||||||
|
|
@ -147,25 +150,29 @@ impl Fsm for SelectToolFsmState {
|
||||||
if selected.is_empty() {
|
if selected.is_empty() {
|
||||||
if let Some(layer) = intersection.last() {
|
if let Some(layer) = intersection.last() {
|
||||||
selected.push(layer.clone());
|
selected.push(layer.clone());
|
||||||
responses.push_back(DocumentMessage::SetSelectedLayers(selected.clone()).into());
|
buffer.push(DocumentMessage::SetSelectedLayers(selected.clone()).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the user clicks on a layer that is in their current selection, go into the dragging mode.
|
// If the user clicks on a layer that is in their current selection, go into the dragging mode.
|
||||||
// Otherwise enter the box select mode
|
// Otherwise enter the box select mode
|
||||||
if selected.iter().any(|path| intersection.contains(path)) {
|
let state = if selected.iter().any(|path| intersection.contains(path)) {
|
||||||
|
buffer.push(DocumentMessage::StartTransaction.into());
|
||||||
data.layers_dragging = selected;
|
data.layers_dragging = selected;
|
||||||
Dragging
|
Dragging
|
||||||
} else {
|
} else {
|
||||||
if !input.keyboard.get(add_to_selection as usize) {
|
if !input.keyboard.get(add_to_selection as usize) {
|
||||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
buffer.push(DocumentMessage::DeselectAllLayers.into());
|
||||||
}
|
}
|
||||||
data.drag_box_id = Some(add_boundnig_box(responses));
|
data.drag_box_id = Some(add_boundnig_box(&mut buffer));
|
||||||
DrawingBox
|
DrawingBox
|
||||||
}
|
};
|
||||||
|
buffer.into_iter().rev().for_each(|message| responses.push_front(message));
|
||||||
|
state
|
||||||
}
|
}
|
||||||
(Dragging, MouseMove) => {
|
(Dragging, MouseMove) => {
|
||||||
|
responses.push_front(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||||
for path in data.layers_dragging.iter() {
|
for path in data.layers_dragging.iter() {
|
||||||
responses.push_back(
|
responses.push_front(
|
||||||
Operation::TransformLayerInViewport {
|
Operation::TransformLayerInViewport {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
transform: DAffine2::from_translation(input.mouse.position - data.drag_current).to_cols_array(),
|
transform: DAffine2::from_translation(input.mouse.position - data.drag_current).to_cols_array(),
|
||||||
|
|
@ -173,7 +180,6 @@ impl Fsm for SelectToolFsmState {
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
|
||||||
data.drag_current = input.mouse.position;
|
data.drag_current = input.mouse.position;
|
||||||
Dragging
|
Dragging
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +189,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
let start = data.drag_start + half_pixel_offset;
|
let start = data.drag_start + half_pixel_offset;
|
||||||
let size = data.drag_current - start + half_pixel_offset;
|
let size = data.drag_current - start + half_pixel_offset;
|
||||||
|
|
||||||
responses.push_back(
|
responses.push_front(
|
||||||
Operation::SetLayerTransformInViewport {
|
Operation::SetLayerTransformInViewport {
|
||||||
path: data.drag_box_id.clone().unwrap(),
|
path: data.drag_box_id.clone().unwrap(),
|
||||||
transform: DAffine2::from_scale_angle_translation(size, 0., start).to_cols_array(),
|
transform: DAffine2::from_scale_angle_translation(size, 0., start).to_cols_array(),
|
||||||
|
|
@ -192,21 +198,27 @@ impl Fsm for SelectToolFsmState {
|
||||||
);
|
);
|
||||||
DrawingBox
|
DrawingBox
|
||||||
}
|
}
|
||||||
(Dragging, DragStop) => Ready,
|
(Dragging, DragStop) => {
|
||||||
|
let response = match input.mouse.position.distance(data.drag_start) < 10. * f64::EPSILON {
|
||||||
|
true => DocumentMessage::Undo,
|
||||||
|
false => DocumentMessage::CommitTransaction,
|
||||||
|
};
|
||||||
|
responses.push_front(response.into());
|
||||||
|
Ready
|
||||||
|
}
|
||||||
(DrawingBox, DragStop) => {
|
(DrawingBox, DragStop) => {
|
||||||
let quad = data.selection_quad();
|
let quad = data.selection_quad();
|
||||||
responses.push_back(DocumentMessage::AddSelectedLayers(document.document.intersects_quad_root(quad)).into());
|
responses.push_front(DocumentMessage::AddSelectedLayers(document.document.intersects_quad_root(quad)).into());
|
||||||
responses.push_back(
|
responses.push_front(
|
||||||
Operation::DeleteLayer {
|
Operation::DeleteLayer {
|
||||||
path: data.drag_box_id.take().unwrap(),
|
path: data.drag_box_id.take().unwrap(),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
data.drag_box_id = None;
|
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
(_, Abort) => {
|
(_, Abort) => {
|
||||||
let mut delete = |path: &mut Option<Vec<LayerId>>| path.take().map(|path| responses.push_back(Operation::DeleteLayer { path }.into()));
|
let mut delete = |path: &mut Option<Vec<LayerId>>| path.take().map(|path| responses.push_front(Operation::DeleteLayer { path }.into()));
|
||||||
delete(&mut data.drag_box_id);
|
delete(&mut data.drag_box_id);
|
||||||
delete(&mut data.bounding_box_id);
|
delete(&mut data.bounding_box_id);
|
||||||
Ready
|
Ready
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ const menuEntries: MenuListEntries = [
|
||||||
children: [
|
children: [
|
||||||
[
|
[
|
||||||
{ label: "Undo", shortcut: ["Ctrl", "Z"], action: async () => (await wasm).undo() },
|
{ label: "Undo", shortcut: ["Ctrl", "Z"], action: async () => (await wasm).undo() },
|
||||||
{ label: "Redo", shortcut: ["Ctrl", "⇧", "Z"] },
|
{ label: "Redo", shortcut: ["Ctrl", "⇧", "Z"], action: async () => (await wasm).redo() },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ label: "Cut", shortcut: ["Ctrl", "X"] },
|
{ label: "Cut", shortcut: ["Ctrl", "X"] },
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,12 @@ pub fn undo() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::Undo)).map_err(convert_error)
|
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::Undo)).map_err(convert_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Redo history one step
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn redo() -> Result<(), JsValue> {
|
||||||
|
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::Redo)).map_err(convert_error)
|
||||||
|
}
|
||||||
|
|
||||||
/// Select all layers
|
/// Select all layers
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_all_layers() -> Result<(), JsValue> {
|
pub fn select_all_layers() -> Result<(), JsValue> {
|
||||||
|
|
|
||||||
|
|
@ -272,6 +272,24 @@ impl Document {
|
||||||
self.set_transform_relative_to_scope(layer, None, transform)
|
self.set_transform_relative_to_scope(layer, None, transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_overlays(&mut self, path: &mut Vec<LayerId>) {
|
||||||
|
if self.layer(path).unwrap().overlay {
|
||||||
|
self.delete(path).unwrap()
|
||||||
|
}
|
||||||
|
let ids = self.folder(path).map(|folder| folder.layer_ids.clone()).unwrap_or_default();
|
||||||
|
for id in ids {
|
||||||
|
path.push(id);
|
||||||
|
self.remove_overlays(path);
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone_without_overlays(&self) -> Self {
|
||||||
|
let mut document = self.clone();
|
||||||
|
document.remove_overlays(&mut vec![]);
|
||||||
|
document
|
||||||
|
}
|
||||||
|
|
||||||
/// Mutate the document by applying the `operation` to it. If the operation necessitates a
|
/// Mutate the document by applying the `operation` to it. If the operation necessitates a
|
||||||
/// reaction from the frontend, responses may be returned.
|
/// reaction from the frontend, responses may be returned.
|
||||||
pub fn handle_operation(&mut self, operation: &Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
|
pub fn handle_operation(&mut self, operation: &Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue