mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Implement anchor and handle point rendering with the Path Tool (#353)
* Implement Path Tool * Draw a red rectangle where the first point on the shape is * Correctly render anchors, handles, and connecting lines * Fix drain() which can panic * Refactor frontend messages to work as return values not callbacks * Reduce the number of unnecessary frontend updates * Fix stack overflow by using a loop * Group Document Render calls and put them at the end * Speed hacks for dirtification * Add performance * Bunch folder changed updates * Add triggers to redraw overlays to movement_handler * Polish the pixel-perfect rendering of vector manipulators * Restore scrollbars that were disabled * Cleanup * WIP Add shape outline rendering * Fix compiling * Add outlines to selected shapes * Fix outlines rendering over handles and anchors * Fix dirtification * Add a comment * Address code review feedback * Formatting * Small tweaks Co-authored-by: Oliver Davies <oliver@psyfer.io> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
@@ -9,6 +9,7 @@ use crate::{
|
||||
communication::{message::Message, MessageHandler},
|
||||
Color,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@@ -126,7 +127,7 @@ fn default_tool_options() -> HashMap<ToolType, ToolOptions> {
|
||||
}
|
||||
|
||||
#[repr(usize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ToolType {
|
||||
Select,
|
||||
Crop,
|
||||
|
||||
@@ -6,14 +6,16 @@ use crate::{
|
||||
document::DocumentMessageHandler,
|
||||
tool::{tool_options::ToolOptions, DocumentToolData, ToolFsmState, ToolType},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[impl_message(Message, Tool)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ToolMessage {
|
||||
SelectTool(ToolType),
|
||||
ActivateTool(ToolType),
|
||||
SelectPrimaryColor(Color),
|
||||
SelectSecondaryColor(Color),
|
||||
SelectedLayersChanged,
|
||||
SwapColors,
|
||||
ResetColors,
|
||||
NoOp,
|
||||
@@ -63,7 +65,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
||||
|
||||
update_working_colors(document_data, responses);
|
||||
}
|
||||
SelectTool(new_tool) => {
|
||||
ActivateTool(new_tool) => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
let document_data = &self.tool_state.document_tool_data;
|
||||
let old_tool = tool_data.active_tool_type;
|
||||
@@ -75,12 +77,13 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
||||
|
||||
// Get the Abort state of a tool's FSM
|
||||
let reset_message = |tool| match tool {
|
||||
ToolType::Ellipse => Some(EllipseMessage::Abort.into()),
|
||||
ToolType::Rectangle => Some(RectangleMessage::Abort.into()),
|
||||
ToolType::Shape => Some(ShapeMessage::Abort.into()),
|
||||
ToolType::Line => Some(LineMessage::Abort.into()),
|
||||
ToolType::Pen => Some(PenMessage::Abort.into()),
|
||||
ToolType::Select => Some(SelectMessage::Abort.into()),
|
||||
ToolType::Path => Some(PathMessage::Abort.into()),
|
||||
ToolType::Pen => Some(PenMessage::Abort.into()),
|
||||
ToolType::Line => Some(LineMessage::Abort.into()),
|
||||
ToolType::Rectangle => Some(RectangleMessage::Abort.into()),
|
||||
ToolType::Ellipse => Some(EllipseMessage::Abort.into()),
|
||||
ToolType::Shape => Some(ShapeMessage::Abort.into()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -100,8 +103,9 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
||||
}
|
||||
|
||||
// Special cases for specific tools
|
||||
if new_tool == ToolType::Select {
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
// TODO: Refactor to avoid doing this here
|
||||
if new_tool == ToolType::Select || new_tool == ToolType::Path {
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
|
||||
// Store the new active tool
|
||||
@@ -112,6 +116,13 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
||||
let tool_options = self.tool_state.document_tool_data.tool_options.get(&new_tool).map(|tool_options| *tool_options);
|
||||
responses.push_back(FrontendMessage::SetActiveTool { tool_name, tool_options }.into());
|
||||
}
|
||||
SelectedLayersChanged => {
|
||||
match self.tool_state.tool_data.active_tool_type {
|
||||
ToolType::Select => responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into()),
|
||||
ToolType::Path => responses.push_back(PathMessage::RedrawOverlay.into()),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
SwapColors => {
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
|
||||
@@ -146,7 +157,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
||||
}
|
||||
}
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut list = actions!(ToolMessageDiscriminant; ResetColors, SwapColors, SelectTool, SetToolOptions);
|
||||
let mut list = actions!(ToolMessageDiscriminant; ResetColors, SwapColors, ActivateTool, SetToolOptions);
|
||||
list.extend(self.tool_state.tool_data.active_tool().actions());
|
||||
|
||||
list
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::message_prelude::*;
|
||||
use crate::tool::ToolActionHandlerData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Crop;
|
||||
|
||||
#[impl_message(Message, ToolMessage, Crop)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum CropMessage {
|
||||
MouseMove,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||
use glam::DAffine2;
|
||||
use graphene::{layers::style, Operation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::resize::*;
|
||||
|
||||
@@ -14,7 +15,7 @@ pub struct Ellipse {
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Ellipse)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum EllipseMessage {
|
||||
DragStart,
|
||||
DragStop,
|
||||
|
||||
@@ -4,12 +4,13 @@ use crate::tool::{ToolActionHandlerData, ToolMessage};
|
||||
use glam::DVec2;
|
||||
use graphene::layers::LayerDataType;
|
||||
use graphene::Quad;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Eyedropper;
|
||||
|
||||
#[impl_message(Message, ToolMessage, Eyedropper)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum EyedropperMessage {
|
||||
LeftMouseDown,
|
||||
RightMouseDown,
|
||||
|
||||
@@ -3,12 +3,13 @@ use crate::message_prelude::*;
|
||||
use crate::tool::ToolActionHandlerData;
|
||||
use glam::DVec2;
|
||||
use graphene::{Operation, Quad};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Fill;
|
||||
|
||||
#[impl_message(Message, ToolMessage, Fill)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum FillMessage {
|
||||
MouseDown,
|
||||
}
|
||||
@@ -21,7 +22,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Fill {
|
||||
|
||||
if let Some(path) = data.0.document.intersects_quad_root(quad).last() {
|
||||
responses.push_back(
|
||||
Operation::FillLayer {
|
||||
Operation::SetLayerFill {
|
||||
path: path.to_vec(),
|
||||
color: data.1.primary_color,
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolOptions, Too
|
||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::{layers::style, Operation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Line {
|
||||
@@ -13,7 +14,7 @@ pub struct Line {
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Line)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum LineMessage {
|
||||
DragStart,
|
||||
DragStop,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::message_prelude::*;
|
||||
use crate::tool::ToolActionHandlerData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Navigate;
|
||||
|
||||
#[impl_message(Message, ToolMessage, Navigate)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum NavigateMessage {
|
||||
MouseMove,
|
||||
}
|
||||
|
||||
@@ -1,18 +1,306 @@
|
||||
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;
|
||||
pub struct Path {
|
||||
fsm_state: PathToolFsmState,
|
||||
data: PathToolData,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Path)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum PathMessage {
|
||||
MouseMove,
|
||||
RedrawOverlay,
|
||||
Abort,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Path {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
todo!("{}::handle_input {:?} {:?} {:?} ", module_path!(), action, data, responses);
|
||||
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;),
|
||||
}
|
||||
}
|
||||
advertise_actions!();
|
||||
}
|
||||
|
||||
#[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<Vec<LayerId>>,
|
||||
handle_marker_pool: Vec<Vec<LayerId>>,
|
||||
anchor_handle_line_pool: Vec<Vec<LayerId>>,
|
||||
shape_outline_pool: Vec<Vec<LayerId>>,
|
||||
}
|
||||
|
||||
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<Message>,
|
||||
) -> 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 {
|
||||
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<VectorManipulatorShape>) -> (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<F>(pool: &mut Vec<Vec<LayerId>>, total: usize, add_overlay_function: F, responses: &mut VecDeque<Message>)
|
||||
where
|
||||
F: Fn(&mut VecDeque<Message>) -> Vec<LayerId>,
|
||||
{
|
||||
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<Message>) -> Vec<LayerId> {
|
||||
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<Message>) -> Vec<LayerId> {
|
||||
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<Message>) -> Vec<LayerId> {
|
||||
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<Message>) -> Vec<LayerId> {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolOptions, Too
|
||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||
use glam::DAffine2;
|
||||
use graphene::{layers::style, Operation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Pen {
|
||||
@@ -11,7 +12,7 @@ pub struct Pen {
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Pen)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum PenMessage {
|
||||
Undo,
|
||||
DragStart,
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||
use glam::DAffine2;
|
||||
use graphene::{layers::style, Operation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::resize::*;
|
||||
|
||||
@@ -14,7 +15,7 @@ pub struct Rectangle {
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Rectangle)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum RectangleMessage {
|
||||
DragStart,
|
||||
DragStop,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use graphene::color::Color;
|
||||
use graphene::layers::style;
|
||||
use graphene::layers::style::Fill;
|
||||
use graphene::layers::style::Stroke;
|
||||
@@ -8,6 +7,7 @@ use graphene::Quad;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::consts::COLOR_ACCENT;
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
@@ -24,7 +24,7 @@ pub struct Select {
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Select)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum SelectMessage {
|
||||
DragStart { add_to_selection: Key },
|
||||
DragStop,
|
||||
@@ -70,7 +70,7 @@ struct SelectToolData {
|
||||
drag_current: ViewportPosition,
|
||||
layers_dragging: Vec<Vec<LayerId>>, // Paths and offsets
|
||||
drag_box_id: Option<Vec<LayerId>>,
|
||||
bounding_box_id: Option<Vec<LayerId>>,
|
||||
bounding_box_path: Option<Vec<LayerId>>,
|
||||
}
|
||||
|
||||
impl SelectToolData {
|
||||
@@ -89,13 +89,13 @@ impl SelectToolData {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_boundnig_box(responses: &mut Vec<Message>) -> Vec<LayerId> {
|
||||
fn add_bounding_box(responses: &mut Vec<Message>) -> Vec<LayerId> {
|
||||
let path = vec![generate_uuid()];
|
||||
responses.push(
|
||||
Operation::AddBoundingBox {
|
||||
Operation::AddOverlayRect {
|
||||
path: path.clone(),
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(Some(Stroke::new(Color::from_rgb8(0x00, 0xA8, 0xFF), 1.0)), Some(Fill::none())),
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
@@ -125,11 +125,11 @@ impl Fsm for SelectToolFsmState {
|
||||
match (self, event) {
|
||||
(_, 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_path.take()) {
|
||||
(None, Some(path)) => Operation::DeleteLayer { path }.into(),
|
||||
(Some([pos1, pos2]), path) => {
|
||||
let path = path.unwrap_or_else(|| add_boundnig_box(&mut buffer));
|
||||
data.bounding_box_id = Some(path.clone());
|
||||
let path = path.unwrap_or_else(|| add_bounding_box(&mut buffer));
|
||||
data.bounding_box_path = Some(path.clone());
|
||||
let transform = transform_from_box(pos1, pos2);
|
||||
Operation::SetLayerTransformInViewport { path, transform }.into()
|
||||
}
|
||||
@@ -163,7 +163,7 @@ impl Fsm for SelectToolFsmState {
|
||||
if !input.keyboard.get(add_to_selection as usize) {
|
||||
buffer.push(DocumentMessage::DeselectAllLayers.into());
|
||||
}
|
||||
data.drag_box_id = Some(add_boundnig_box(&mut buffer));
|
||||
data.drag_box_id = Some(add_bounding_box(&mut buffer));
|
||||
DrawingBox
|
||||
};
|
||||
buffer.into_iter().rev().for_each(|message| responses.push_front(message));
|
||||
@@ -220,7 +220,7 @@ impl Fsm for SelectToolFsmState {
|
||||
(_, Abort) => {
|
||||
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.bounding_box_id);
|
||||
delete(&mut data.bounding_box_path);
|
||||
Ready
|
||||
}
|
||||
(_, Align(axis, aggregate)) => {
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::tool::{DocumentToolData, Fsm, ShapeType, ToolActionHandlerData, ToolO
|
||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||
use glam::DAffine2;
|
||||
use graphene::{layers::style, Operation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::resize::*;
|
||||
|
||||
@@ -14,7 +15,7 @@ pub struct Shape {
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Shape)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum ShapeMessage {
|
||||
DragStart,
|
||||
DragStop,
|
||||
@@ -82,7 +83,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
};
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddShape {
|
||||
Operation::AddNgon {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
|
||||
Reference in New Issue
Block a user