Improve history states (#932)

* Add some more history states

* Fix undo whilst drawing

* Paste image history

* Toggle output and preview history

* Code review nits

* Remove extra '{'

* Fix typo

* Fix about.toml

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2023-01-01 22:02:44 +00:00
committed by Keavon Chambers
parent 6e142627a3
commit 2bcc3d3baf
16 changed files with 282 additions and 190 deletions

View File

@@ -100,8 +100,8 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
// Send the DocumentIsDirty message to the active tool's sub-tool message handler
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
// Send Properties to the frontend
tool_data.tools.get(&tool_type).unwrap().register_properties(responses, LayoutTarget::ToolOptions);
// Send tool options to the frontend
responses.push_back(ToolMessage::RefreshToolOptions.into());
// Notify the frontend about the new active tool to be displayed
tool_data.register_properties(responses, LayoutTarget::ToolShelf);

View File

@@ -310,7 +310,7 @@ impl SelectedGradient {
};
// Clear the gradient if layer deleted
let Ok(layer) = document.document_legacy.layer(&inner_gradient.path) else{
let Ok(layer) = document.document_legacy.layer(&inner_gradient.path) else {
responses.push_back(ToolMessage::RefreshToolOptions.into());
*gradient = None;
return;
@@ -320,7 +320,7 @@ impl SelectedGradient {
inner_gradient.transform = gradient_space_transform(&inner_gradient.path, layer, document, font_cache);
// Clear if no longer a gradient
let Some(gradient) = layer.style().ok().and_then(|style|style.fill().as_gradient()) else{
let Some(gradient) = layer.style().ok().and_then(|style|style.fill().as_gradient()) else {
responses.push_back(ToolMessage::RefreshToolOptions.into());
*gradient = None;
return;
@@ -462,7 +462,7 @@ impl Fsm for GradientToolFsmState {
self
}
(GradientToolFsmState::Ready, GradientToolMessage::DeleteStop) => {
let Some(selected_gradient) = &mut tool_data.selected_gradient else{
let Some(selected_gradient) = &mut tool_data.selected_gradient else {
return self;
};

View File

@@ -473,11 +473,15 @@ impl Fsm for SelectToolFsmState {
// If the user clicks on new shape, make that layer their new selection.
// Otherwise enter the box select mode
let state = if tool_data.pivot.is_over(input.mouse.position) {
responses.push_back(DocumentMessage::StartTransaction.into());
tool_data.snap_manager.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
tool_data.snap_manager.add_all_document_handles(document, &[], &[], &[]);
DraggingPivot
} else if let Some(selected_edges) = dragging_bounds {
responses.push_back(DocumentMessage::StartTransaction.into());
let snap_x = selected_edges.2 || selected_edges.3;
let snap_y = selected_edges.0 || selected_edges.1;
@@ -498,6 +502,8 @@ impl Fsm for SelectToolFsmState {
ResizingBounds
} else if rotating_bounds {
responses.push_back(DocumentMessage::StartTransaction.into());
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
let selected = selected.iter().collect::<Vec<_>>();
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.center_of_transformation, &selected, responses, &document.document_legacy);
@@ -679,6 +685,12 @@ impl Fsm for SelectToolFsmState {
Ready
}
(ResizingBounds, DragStop) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.push_back(response.into());
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
@@ -688,6 +700,12 @@ impl Fsm for SelectToolFsmState {
Ready
}
(RotatingBounds, DragStop) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.push_back(response.into());
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear();
}
@@ -695,6 +713,12 @@ impl Fsm for SelectToolFsmState {
Ready
}
(DraggingPivot, DragStop) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
responses.push_back(response.into());
tool_data.snap_manager.cleanup(responses);
Ready
@@ -769,6 +793,8 @@ impl Fsm for SelectToolFsmState {
self
}
(_, SetPivot { position }) => {
responses.push_back(DocumentMessage::StartTransaction.into());
let pos: Option<DVec2> = position.into();
tool_data.pivot.set_normalized_position(pos.unwrap(), document, font_cache, responses);

View File

@@ -210,10 +210,18 @@ impl Default for TextToolFsmState {
#[derive(Clone, Debug, Default)]
struct TextToolData {
path: Vec<LayerId>,
layer_path: Vec<LayerId>,
overlays: Vec<Vec<LayerId>>,
}
impl TextToolData {
/// Set the editing state of the currently modifying layer
fn set_editing(&self, editable: bool, responses: &mut VecDeque<Message>) {
let path = self.layer_path.clone();
responses.push_back(DocumentMessage::SetTextboxEditability { path, editable }.into());
}
}
fn transform_from_box(pos1: DVec2, pos2: DVec2) -> [f64; 6] {
DAffine2::from_scale_angle_translation((pos2 - pos1).round(), 0., pos1.round() - DVec2::splat(0.5)).to_cols_array()
}
@@ -293,55 +301,44 @@ impl Fsm for TextToolFsmState {
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
let new_state = if let Some(l) = document
// Check if the user has selected an existing text layer
let new_state = if let Some(clicked_text_layer_path) = document
.document_legacy
.intersects_quad_root(quad, font_cache)
.last()
.filter(|l| document.document_legacy.layer(l).map(|l| l.as_text().is_ok()).unwrap_or(false))
// Editing existing text
{
if state == TextToolFsmState::Editing {
responses.push_back(
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: false,
}
.into(),
);
tool_data.set_editing(false, responses);
}
tool_data.path = l.clone();
tool_data.layer_path = clicked_text_layer_path.clone();
responses.push_back(
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: true,
}
.into(),
);
responses.push_back(
DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![tool_data.path.clone()],
}
.into(),
);
responses.push_back(DocumentMessage::StartTransaction.into());
tool_data.set_editing(true, responses);
let replacement_selected_layers = vec![tool_data.layer_path.clone()];
responses.push_back(DocumentMessage::SetSelectedLayers { replacement_selected_layers }.into());
Editing
}
// Creating new text
// Create new text
else if state == TextToolFsmState::Ready {
responses.push_back(DocumentMessage::StartTransaction.into());
let transform = DAffine2::from_translation(input.mouse.position).to_cols_array();
let font_size = tool_options.font_size;
let font_name = tool_options.font_name.clone();
let font_style = tool_options.font_style.clone();
tool_data.path = document.get_path_for_new_layer();
tool_data.layer_path = document.get_path_for_new_layer();
responses.push_back(
Operation::AddText {
path: tool_data.path.clone(),
path: tool_data.layer_path.clone(),
transform: DAffine2::ZERO.to_cols_array(),
insert_index: -1,
text: r#""#.to_string(),
text: String::new(),
style: style::PathStyle::new(None, Fill::solid(global_tool_data.primary_color)),
size: font_size as f64,
font_name,
@@ -351,37 +348,22 @@ impl Fsm for TextToolFsmState {
);
responses.push_back(
Operation::SetLayerTransformInViewport {
path: tool_data.path.clone(),
path: tool_data.layer_path.clone(),
transform,
}
.into(),
);
responses.push_back(
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: true,
}
.into(),
);
tool_data.set_editing(true, responses);
responses.push_back(
DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![tool_data.path.clone()],
}
.into(),
);
let replacement_selected_layers = vec![tool_data.layer_path.clone()];
responses.push_back(DocumentMessage::SetSelectedLayers { replacement_selected_layers }.into());
Editing
} else {
// Removing old text as editable
responses.push_back(
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: false,
}
.into(),
);
tool_data.set_editing(false, responses);
resize_overlays(&mut tool_data.overlays, responses, 0);
@@ -392,13 +374,7 @@ impl Fsm for TextToolFsmState {
}
(state, Abort) => {
if state == TextToolFsmState::Editing {
responses.push_back(
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: false,
}
.into(),
);
tool_data.set_editing(false, responses);
}
resize_overlays(&mut tool_data.overlays, responses, 0);
@@ -411,21 +387,10 @@ impl Fsm for TextToolFsmState {
Editing
}
(Editing, TextChange { new_text }) => {
responses.push_back(
Operation::SetTextContent {
path: tool_data.path.clone(),
new_text,
}
.into(),
);
let path = tool_data.layer_path.clone();
responses.push_back(Operation::SetTextContent { path, new_text }.into());
responses.push_back(
DocumentMessage::SetTextboxEditability {
path: tool_data.path.clone(),
editable: false,
}
.into(),
);
tool_data.set_editing(false, responses);
resize_overlays(&mut tool_data.overlays, responses, 0);
@@ -433,10 +398,10 @@ impl Fsm for TextToolFsmState {
}
(Editing, UpdateBounds { new_text }) => {
resize_overlays(&mut tool_data.overlays, responses, 1);
let text = document.document_legacy.layer(&tool_data.path).unwrap().as_text().unwrap();
let text = document.document_legacy.layer(&tool_data.layer_path).unwrap().as_text().unwrap();
let quad = text.bounding_box(&new_text, text.load_face(font_cache));
let transformed_quad = document.document_legacy.multiply_transforms(&tool_data.path).unwrap() * quad;
let transformed_quad = document.document_legacy.multiply_transforms(&tool_data.layer_path).unwrap() * quad;
let bounds = transformed_quad.bounding_box();
let operation = Operation::SetLayerTransformInViewport {