use crate::consts::COLOR_ACCENT; use crate::consts::VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE; use crate::document::DocumentMessageHandler; use crate::document::VectorManipulatorSegment; use crate::document::VectorManipulatorShape; use crate::input::InputPreprocessor; use crate::message_prelude::*; use crate::tool::ToolActionHandlerData; use crate::tool::{DocumentToolData, Fsm}; use glam::{DAffine2, DVec2}; use graphene::color::Color; use graphene::layers::style; use graphene::layers::style::Fill; use graphene::layers::style::Stroke; use graphene::Operation; use kurbo::BezPath; use serde::{Deserialize, Serialize}; #[derive(Default)] pub struct Path { fsm_state: PathToolFsmState, data: PathToolData, } #[impl_message(Message, ToolMessage, Path)] #[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] pub enum PathMessage { RedrawOverlay, Abort, } impl<'a> MessageHandler> for Path { fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses); } fn actions(&self) -> ActionList { use PathToolFsmState::*; match self.fsm_state { Ready => actions!(PathMessageDiscriminant;), } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum PathToolFsmState { Ready, } impl Default for PathToolFsmState { fn default() -> Self { PathToolFsmState::Ready } } #[derive(Clone, Debug, Default)] struct PathToolData { anchor_marker_pool: Vec>, handle_marker_pool: Vec>, anchor_handle_line_pool: Vec>, shape_outline_pool: Vec>, } impl PathToolData {} impl Fsm for PathToolFsmState { type ToolData = PathToolData; fn transition( self, event: ToolMessage, document: &DocumentMessageHandler, _tool_data: &DocumentToolData, data: &mut Self::ToolData, _input: &InputPreprocessor, responses: &mut VecDeque, ) -> Self { if let ToolMessage::Path(event) = event { use PathMessage::*; use PathToolFsmState::*; match (self, event) { (_, RedrawOverlay) => { let (mut anchor_i, mut handle_i, mut line_i, mut shape_i) = (0, 0, 0, 0); let shapes_to_draw = document.selected_layers_vector_points(); // Grow the overlay pools by the shortfall, if any let (total_anchors, total_handles, total_anchor_handle_lines) = calculate_total_overlays_per_type(&shapes_to_draw); let total_shapes = shapes_to_draw.len(); grow_overlay_pool_entries(&mut data.shape_outline_pool, total_shapes, add_shape_outline, responses); grow_overlay_pool_entries(&mut data.anchor_handle_line_pool, total_anchor_handle_lines, add_anchor_handle_line, responses); grow_overlay_pool_entries(&mut data.anchor_marker_pool, total_anchors, add_anchor_marker, responses); grow_overlay_pool_entries(&mut data.handle_marker_pool, total_handles, add_handle_marker, responses); // Helps push values that end in approximately half, plus or minus some floating point imprecision, towards the same side of the round() function const BIAS: f64 = 0.0001; // Draw the overlays for each shape for shape_to_draw in &shapes_to_draw { let shape_layer_path = &data.shape_outline_pool[shape_i]; responses.push_back( Operation::SetShapePathInViewport { path: shape_layer_path.clone(), bez_path: shape_to_draw.path.clone(), transform: shape_to_draw.transform.to_cols_array(), } .into(), ); responses.push_back( Operation::SetLayerVisibility { path: shape_layer_path.clone(), visible: true, } .into(), ); shape_i += 1; for segment in &shape_to_draw.segments { // TODO: We draw each anchor point twice because segment has it on both ends, fix this let (anchors, handles, anchor_handle_lines) = match segment { VectorManipulatorSegment::Line(a1, a2) => (vec![*a1, *a2], vec![], vec![]), VectorManipulatorSegment::Quad(a1, h1, a2) => (vec![*a1, *a2], vec![*h1], vec![(*h1, *a1)]), VectorManipulatorSegment::Cubic(a1, h1, h2, a2) => (vec![*a1, *a2], vec![*h1, *h2], vec![(*h1, *a1), (*h2, *a2)]), }; // Draw the line connecting the anchor with handle for cubic and quadratic bezier segments for anchor_handle_line in anchor_handle_lines { let marker = data.anchor_handle_line_pool[line_i].clone(); let line_vector = anchor_handle_line.0 - anchor_handle_line.1; let scale = DVec2::splat(line_vector.length()); let angle = -line_vector.angle_between(DVec2::X); let translation = (anchor_handle_line.1 + BIAS).round() + DVec2::splat(0.5); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); responses.push_back(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()); responses.push_back(Operation::SetLayerVisibility { path: marker, visible: true }.into()); line_i += 1; } // Draw the draggable square points on the end of every line segment or bezier curve segment for anchor in anchors { let marker = data.anchor_marker_pool[anchor_i].clone(); let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE); let angle = 0.; let translation = (anchor - (scale / 2.) + BIAS).round(); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); responses.push_back(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()); responses.push_back(Operation::SetLayerVisibility { path: marker, visible: true }.into()); anchor_i += 1; } // Draw the draggable handle for cubic and quadratic bezier segments for handle in handles { let marker = data.handle_marker_pool[handle_i].clone(); let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE); let angle = 0.; let translation = (handle - (scale / 2.) + BIAS).round(); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); responses.push_back(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()); responses.push_back(Operation::SetLayerVisibility { path: marker, visible: true }.into()); handle_i += 1; } } } // Hide the remaining pooled overlays for i in anchor_i..data.anchor_marker_pool.len() { let marker = data.anchor_marker_pool[i].clone(); responses.push_back(Operation::SetLayerVisibility { path: marker, visible: false }.into()); } for i in handle_i..data.handle_marker_pool.len() { let marker = data.handle_marker_pool[i].clone(); responses.push_back(Operation::SetLayerVisibility { path: marker, visible: false }.into()); } for i in line_i..data.anchor_handle_line_pool.len() { let line = data.anchor_handle_line_pool[i].clone(); responses.push_back(Operation::SetLayerVisibility { path: line, visible: false }.into()); } for i in shape_i..data.shape_outline_pool.len() { let shape_i = data.shape_outline_pool[i].clone(); responses.push_back(Operation::SetLayerVisibility { path: shape_i, visible: false }.into()); } self } (_, Abort) => { // Destory the overlay layer pools while let Some(layer) = data.anchor_marker_pool.pop() { responses.push_back(Operation::DeleteLayer { path: layer }.into()); } while let Some(layer) = data.handle_marker_pool.pop() { responses.push_back(Operation::DeleteLayer { path: layer }.into()); } while let Some(layer) = data.anchor_handle_line_pool.pop() { responses.push_back(Operation::DeleteLayer { path: layer }.into()); } while let Some(layer) = data.shape_outline_pool.pop() { responses.push_back(Operation::DeleteLayer { path: layer }.into()); } Ready } } } else { self } } } fn calculate_total_overlays_per_type(shapes_to_draw: &Vec) -> (usize, usize, usize) { let (mut total_anchors, mut total_handles, mut total_anchor_handle_lines) = (0, 0, 0); for shape_to_draw in shapes_to_draw { for segment in &shape_to_draw.segments { let (anchors, handles, anchor_handle_lines) = match segment { VectorManipulatorSegment::Line(_, _) => (2, 0, 0), VectorManipulatorSegment::Quad(_, _, _) => (2, 1, 1), VectorManipulatorSegment::Cubic(_, _, _, _) => (2, 2, 2), }; total_anchors += anchors; total_handles += handles; total_anchor_handle_lines += anchor_handle_lines; } } (total_anchors, total_handles, total_anchor_handle_lines) } fn grow_overlay_pool_entries(pool: &mut Vec>, total: usize, add_overlay_function: F, responses: &mut VecDeque) where F: Fn(&mut VecDeque) -> Vec, { if pool.len() < total { let additional = total - pool.len(); pool.reserve(additional); for _ in 0..additional { let marker = add_overlay_function(responses); pool.push(marker); } } } fn add_anchor_marker(responses: &mut VecDeque) -> Vec { let layer_path = vec![generate_uuid()]; responses.push_back( Operation::AddOverlayRect { path: layer_path.clone(), transform: DAffine2::IDENTITY.to_cols_array(), style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Some(Fill::new(Color::WHITE))), } .into(), ); layer_path } fn add_handle_marker(responses: &mut VecDeque) -> Vec { let layer_path = vec![generate_uuid()]; responses.push_back( Operation::AddOverlayEllipse { path: layer_path.clone(), transform: DAffine2::IDENTITY.to_cols_array(), style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Some(Fill::new(Color::WHITE))), } .into(), ); layer_path } fn add_anchor_handle_line(responses: &mut VecDeque) -> Vec { let layer_path = vec![generate_uuid()]; responses.push_back( Operation::AddOverlayLine { path: layer_path.clone(), transform: DAffine2::IDENTITY.to_cols_array(), style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())), } .into(), ); layer_path } fn add_shape_outline(responses: &mut VecDeque) -> Vec { let layer_path = vec![generate_uuid()]; responses.push_back( Operation::AddOverlayShape { path: layer_path.clone(), bez_path: BezPath::default(), style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())), } .into(), ); layer_path }