Migrate vector data and tools to use nodes (#1065)

* Add rendering to vector nodes

* Add line, shape, rectange and freehand tool

* Fix transforms, strokes and fills

* Migrate spline tool

* Remove blank lines

* Fix test

* Fix fill in properties

* Select layers when filling

* Properties panel transform around pivot

* Fix select tool outlines

* Select tool modifies node graph pivot

* Add the pivot assist to the properties

* Improve setting non existant fill UX

* Cleanup hash function

* Path and pen tools

* Bug fixes

* Disable boolean ops

* Fix default handle smoothing on ellipses

* Fix test and warnings

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2023-03-26 00:03:51 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 5759600f95
commit ac49519fa9
64 changed files with 2639 additions and 1552 deletions
@@ -0,0 +1,25 @@
use crate::messages::portfolio::document::node_graph;
use crate::messages::prelude::*;
use bezier_rs::Subpath;
use document_legacy::{LayerId, Operation};
use glam::DAffine2;
use graphene_core::uuid::ManipulatorGroupId;
use std::collections::VecDeque;
/// Create a new vector layer from a vector of [`bezier_rs::Subpath`].
pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
responses.push_back(DocumentMessage::DeselectAllLayers.into());
let network = node_graph::new_vector_network(subpaths);
responses.push_back(
Operation::AddNodeGraphFrame {
path: layer_path.clone(),
insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(),
network,
}
.into(),
);
responses.add(DocumentMessage::NodeGraphFrameGenerate { layer_path });
}
@@ -1,3 +1,4 @@
pub mod graph_modification_utils;
pub mod overlay_renderer;
pub mod path_outline;
pub mod pivot;
@@ -3,97 +3,106 @@ use crate::consts::VIEWPORT_GRID_ROUNDING_BIAS;
use crate::consts::{COLOR_ACCENT, HIDE_HANDLE_DISTANCE, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT};
use crate::messages::prelude::*;
use bezier_rs::ManipulatorGroup;
use document_legacy::document::Document;
use document_legacy::layers::style::{self, Fill, Stroke};
use document_legacy::{LayerId, Operation};
use graphene_core::raster::color::Color;
use graphene_std::vector::consts::ManipulatorType;
use graphene_std::vector::manipulator_group::ManipulatorGroup;
use graphene_std::vector::manipulator_point::ManipulatorPoint;
use graphene_std::vector::subpath::Subpath;
use graphene_core::uuid::ManipulatorGroupId;
use glam::{DAffine2, DVec2};
use graphene_core::vector::{ManipulatorPointId, SelectedType};
use super::shape_editor::SelectedShapeState;
/// [ManipulatorGroupOverlay]s is the collection of overlays that make up an [ManipulatorGroup] visible in the editor.
type ManipulatorGroupOverlays = [Option<Vec<LayerId>>; 5];
type ManipulatorId = u64;
#[derive(Clone, Debug, Default)]
struct ManipulatorGroupOverlays {
pub anchor: Option<Vec<LayerId>>,
pub in_handle: Option<Vec<LayerId>>,
pub in_line: Option<Vec<LayerId>>,
pub out_handle: Option<Vec<LayerId>>,
pub out_line: Option<Vec<LayerId>>,
}
impl ManipulatorGroupOverlays {
pub fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Option<Vec<LayerId>>> {
[&self.anchor, &self.in_handle, &self.in_line, &self.out_handle, &self.out_line].into_iter()
}
}
type GraphiteManipulatorGroup = ManipulatorGroup<ManipulatorGroupId>;
const POINT_STROKE_WEIGHT: f64 = 2.;
#[derive(Clone, Debug, Default)]
pub struct OverlayRenderer {
shape_overlay_cache: HashMap<LayerId, Vec<LayerId>>,
manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorId), ManipulatorGroupOverlays>,
manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorGroupId), ManipulatorGroupOverlays>,
}
impl OverlayRenderer {
pub fn new() -> Self {
OverlayRenderer {
manipulator_group_overlay_cache: HashMap::new(),
shape_overlay_cache: HashMap::new(),
}
Self::default()
}
pub fn render_subpath_overlays(&mut self, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
pub fn render_subpath_overlays(&mut self, selected_shape_state: &SelectedShapeState, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
let transform = document.generate_transform_relative_to_viewport(&layer_path).ok().unwrap();
if let Ok(layer) = document.layer(&layer_path) {
let layer_id = layer_path.last().unwrap();
self.layer_overlay_visibility(document, layer_path.clone(), true, responses);
if let Some(shape) = layer.as_subpath() {
if let Some(vector_data) = layer.as_vector_data() {
let outline_cache = self.shape_overlay_cache.get(layer_id);
trace!("Overlay: Outline cache {:?}", &outline_cache);
// Create an outline if we do not have a cached one
if outline_cache.is_none() {
let outline_path = self.create_shape_outline_overlay(shape.clone(), responses);
let outline_path = self.create_shape_outline_overlay(graphene_core::vector::Subpath::from_bezier_crate(&vector_data.subpaths), responses);
self.shape_overlay_cache.insert(*layer_id, outline_path.clone());
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
trace!("Overlay: Creating new outline {:?}", &outline_path);
} else if let Some(outline_path) = outline_cache {
trace!("Overlay: Updating overlays for {:?} owning layer: {:?}", outline_path, layer_id);
Self::modify_outline_overlays(outline_path.clone(), shape.clone(), responses);
Self::modify_outline_overlays(outline_path.clone(), graphene_core::vector::Subpath::from_bezier_crate(&vector_data.subpaths), responses);
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
}
// Create, place, and style the manipulator overlays
for (manipulator_group_id, manipulator_group) in shape.manipulator_groups().enumerate() {
let manipulator_group_cache = self.manipulator_group_overlay_cache.entry((*layer_id, *manipulator_group_id)).or_default();
for manipulator_group in vector_data.manipulator_groups() {
let manipulator_group_cache = self.manipulator_group_overlay_cache.entry((*layer_id, manipulator_group.id)).or_default();
// Only view in and out handles if they are not on top of the anchor
let [in_handle, out_handle] = {
let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() else {
continue;
};
let anchor = manipulator_group.anchor;
let anchor_position = transform.transform_point2(anchor.position);
let filter_position = |handle: &&ManipulatorPoint| transform.transform_point2(handle.position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
let filter_manipulator_point = |manipulator_type| manipulator_group.points[manipulator_type as usize].as_ref().filter(filter_position);
[filter_manipulator_point(ManipulatorType::InHandle), filter_manipulator_point(ManipulatorType::OutHandle)]
let anchor_position = transform.transform_point2(anchor);
let not_under_anchor = |&position: &DVec2| transform.transform_point2(position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
let filter_handle = |manipulator: Option<DVec2>| manipulator.filter(not_under_anchor);
[filter_handle(manipulator_group.in_handle), filter_handle(manipulator_group.out_handle)]
};
// Create anchor
manipulator_group_cache[0] = manipulator_group_cache[0].take().or_else(|| Some(Self::create_anchor_overlay(responses)));
manipulator_group_cache.anchor = manipulator_group_cache.anchor.take().or_else(|| Some(Self::create_anchor_overlay(responses)));
// Create or delete in handle
if in_handle.is_none() {
Self::remove_overlay(manipulator_group_cache[1].take(), responses);
Self::remove_overlay(manipulator_group_cache[3].take(), responses);
Self::remove_overlay(manipulator_group_cache.in_handle.take(), responses);
Self::remove_overlay(manipulator_group_cache.in_line.take(), responses);
} else {
manipulator_group_cache[1] = manipulator_group_cache[1].take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses));
manipulator_group_cache[3] = manipulator_group_cache[3].take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses));
manipulator_group_cache.in_handle = manipulator_group_cache.in_handle.take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses));
manipulator_group_cache.in_line = manipulator_group_cache.in_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses));
}
// Create or delete out handle
if out_handle.is_none() {
Self::remove_overlay(manipulator_group_cache[2].take(), responses);
Self::remove_overlay(manipulator_group_cache[4].take(), responses);
Self::remove_overlay(manipulator_group_cache.out_handle.take(), responses);
Self::remove_overlay(manipulator_group_cache.out_line.take(), responses);
} else {
manipulator_group_cache[2] = manipulator_group_cache[2].take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses));
manipulator_group_cache[4] = manipulator_group_cache[4].take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses));
manipulator_group_cache.out_handle = manipulator_group_cache.out_handle.take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses));
manipulator_group_cache.out_line = manipulator_group_cache.out_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses));
}
// Update placement and style
Self::place_manipulator_group_overlays(manipulator_group, manipulator_group_cache, &transform, responses);
Self::style_overlays(manipulator_group, manipulator_group_cache, responses);
Self::style_overlays(selected_shape_state, &layer_path, manipulator_group, manipulator_group_cache, responses);
}
// TODO Handle removing shapes from cache so we don't memory leak
// Eventually will get replaced with am immediate mode renderer for overlays
@@ -112,11 +121,12 @@ impl OverlayRenderer {
// Remove the ManipulatorGroup overlays
if let Ok(layer) = document.layer(&layer_path) {
if let Some(shape) = layer.as_subpath() {
for (id, _) in shape.manipulator_groups().enumerate() {
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, *id)) {
if let Some(vector_data) = layer.as_vector_data() {
for manipulator_group in vector_data.manipulator_groups() {
let id = manipulator_group.id;
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, id)) {
Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses);
self.manipulator_group_overlay_cache.remove(&(*layer_id, *id));
self.manipulator_group_overlay_cache.remove(&(*layer_id, id));
}
}
}
@@ -133,9 +143,10 @@ impl OverlayRenderer {
// Hide the manipulator group overlays
if let Ok(layer) = document.layer(&layer_path) {
if let Some(shape) = layer.as_subpath() {
for (id, _) in shape.manipulator_groups().enumerate() {
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, *id)) {
if let Some(vector_data) = layer.as_vector_data() {
for manipulator_group in vector_data.manipulator_groups() {
let id = manipulator_group.id;
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, id)) {
Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses);
}
}
@@ -144,7 +155,7 @@ impl OverlayRenderer {
}
/// Create the kurbo shape that matches the selected viewport shape.
fn create_shape_outline_overlay(&self, subpath: Subpath, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
fn create_shape_outline_overlay(&self, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
let layer_path = vec![generate_uuid()];
let operation = Operation::AddShape {
path: layer_path.clone(),
@@ -185,7 +196,7 @@ impl OverlayRenderer {
}
/// Create a single handle overlay and return its layer id if it exists.
fn create_handle_overlay_if_exists(handle: Option<&ManipulatorPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
fn create_handle_overlay_if_exists(handle: Option<DVec2>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
handle.map(|_| Self::create_handle_overlay(responses))
}
@@ -210,7 +221,7 @@ impl OverlayRenderer {
}
/// Create the shape outline overlay and return its layer ID.
fn create_handle_line_overlay_if_exists(handle: Option<&ManipulatorPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
fn create_handle_line_overlay_if_exists(handle: Option<DVec2>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
handle.as_ref().map(|_| Self::create_handle_line_overlay(responses))
}
@@ -219,53 +230,48 @@ impl OverlayRenderer {
responses.push_back(transform_message);
}
fn modify_outline_overlays(outline_path: Vec<LayerId>, subpath: Subpath, responses: &mut VecDeque<Message>) {
fn modify_outline_overlays(outline_path: Vec<LayerId>, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque<Message>) {
let outline_modify_message = Self::overlay_modify_message(outline_path, subpath);
responses.push_back(outline_modify_message);
}
/// Updates the position of the overlays based on the [Subpath] points.
fn place_manipulator_group_overlays(manipulator_group: &ManipulatorGroup, overlays: &mut ManipulatorGroupOverlays, parent_transform: &DAffine2, responses: &mut VecDeque<Message>) {
if let Some(manipulator_point) = &manipulator_group.points[ManipulatorType::Anchor] {
// Helper function to keep things DRY (don't-repeat-yourself)
let mut place_handle_and_line = |handle: &ManipulatorPoint, line_overlay: &mut Vec<LayerId>, marker_source: &mut Option<Vec<LayerId>>| {
let line_vector = parent_transform.transform_point2(manipulator_point.position) - parent_transform.transform_point2(handle.position);
let scale = DVec2::splat(line_vector.length());
let angle = -line_vector.angle_between(DVec2::X);
let translation = (parent_transform.transform_point2(handle.position) + VIEWPORT_GRID_ROUNDING_BIAS).round() + DVec2::splat(0.5);
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
responses.push_back(Self::overlay_transform_message(line_overlay.clone(), transform));
fn place_manipulator_group_overlays(manipulator_group: &GraphiteManipulatorGroup, overlays: &mut ManipulatorGroupOverlays, parent_transform: &DAffine2, responses: &mut VecDeque<Message>) {
let anchor = manipulator_group.anchor;
let mut place_handle_and_line = |handle_position: DVec2, line_overlay: &[LayerId], marker_source: &mut Option<Vec<LayerId>>| {
let line_vector = parent_transform.transform_point2(anchor) - parent_transform.transform_point2(handle_position);
let scale = DVec2::splat(line_vector.length());
let angle = -line_vector.angle_between(DVec2::X);
let translation = (parent_transform.transform_point2(handle_position) + VIEWPORT_GRID_ROUNDING_BIAS).round() + DVec2::splat(0.5);
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
responses.push_back(Self::overlay_transform_message(line_overlay.to_vec(), transform));
let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses));
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
let angle = 0.;
let translation = (parent_transform.transform_point2(handle.position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
responses.push_back(Self::overlay_transform_message(marker_overlay.clone(), transform));
*marker_source = Some(marker_overlay);
};
let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses));
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
let angle = 0.;
let translation = (parent_transform.transform_point2(handle_position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
responses.push_back(Self::overlay_transform_message(marker_overlay.clone(), transform));
*marker_source = Some(marker_overlay);
};
// Place the handle overlays
let [_, h1, h2] = &manipulator_group.points;
let [a, b, c, line1, line2] = overlays;
let markers = [a, b, c];
if let (Some(handle), Some(line_source)) = (h1.as_ref(), line1.as_mut()) {
place_handle_and_line(handle, line_source, markers[handle.manipulator_type as usize]);
}
if let (Some(handle), Some(line_source)) = (h2.as_ref(), line2.as_mut()) {
place_handle_and_line(handle, line_source, markers[handle.manipulator_type as usize]);
}
// Place the handle overlays
if let (Some(handle_position), Some(line_overlay)) = (manipulator_group.in_handle, overlays.in_line.as_mut()) {
place_handle_and_line(handle_position, line_overlay, &mut overlays.in_handle);
}
if let (Some(handle_psoition), Some(line_overlay)) = (manipulator_group.out_handle, overlays.out_line.as_ref()) {
place_handle_and_line(handle_psoition, line_overlay, &mut overlays.out_handle);
}
// Place the anchor point overlay
if let Some(anchor_overlay) = &overlays[ManipulatorType::Anchor as usize] {
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
let angle = 0.;
let translation = (parent_transform.transform_point2(manipulator_point.position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
// Place the anchor point overlay
if let Some(anchor_overlay) = &overlays.anchor {
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
let angle = 0.;
let translation = (parent_transform.transform_point2(anchor) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
let message = Self::overlay_transform_message(anchor_overlay.clone(), transform);
responses.push_back(message);
}
let message = Self::overlay_transform_message(anchor_overlay.clone(), transform);
responses.push_back(message);
}
}
@@ -310,24 +316,27 @@ impl OverlayRenderer {
}
/// Create an update message for an overlay.
fn overlay_modify_message(layer_path: Vec<LayerId>, subpath: Subpath) -> Message {
fn overlay_modify_message(layer_path: Vec<LayerId>, subpath: graphene_core::vector::Subpath) -> Message {
DocumentMessage::Overlays(Operation::SetShapePath { path: layer_path, subpath }.into()).into()
}
/// Sets the overlay style for this point.
fn style_overlays(manipulator_group: &ManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) {
fn style_overlays(state: &SelectedShapeState, layer_path: &[LayerId], manipulator_group: &GraphiteManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) {
// TODO Move the style definitions out of the Subpath, should be looked up from a stylesheet or similar
let selected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT));
let deselected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE));
let selected_shape_state = state.get(layer_path);
// Update if the manipulator points are shown as selected
// Here the index is important, even though overlays[..] has five elements we only care about the first three
for (index, point) in manipulator_group.points.iter().enumerate() {
if let Some(point) = point {
if let Some(overlay) = &overlays[index] {
let style = if point.editor_state.is_selected { selected_style.clone() } else { deselected_style.clone() };
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerStyle { path: overlay.clone(), style }.into()).into());
}
for (index, overlay) in [&overlays.in_handle, &overlays.out_handle, &overlays.anchor].into_iter().enumerate() {
let selected_type = [SelectedType::InHandle, SelectedType::OutHandle, SelectedType::Anchor][index];
if let Some(overlay_path) = overlay {
let selected = selected_shape_state
.filter(|state| state.is_selected(ManipulatorPointId::new(manipulator_group.id, selected_type)))
.is_some();
let style = if selected { selected_style.clone() } else { deselected_style.clone() };
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerStyle { path: overlay_path.clone(), style }.into()).into());
}
}
}
@@ -4,6 +4,7 @@ use crate::messages::prelude::*;
use document_legacy::intersection::Quad;
use document_legacy::layers::layer_info::LayerDataType;
use document_legacy::layers::nodegraph_layer::NodeGraphFrameLayer;
use document_legacy::layers::style::{self, Fill, RenderData, Stroke};
use document_legacy::{LayerId, Operation};
use graphene_std::vector::subpath::Subpath;
@@ -35,6 +36,7 @@ impl PathOutline {
let subpath = match &document_layer.data {
LayerDataType::Shape(layer_shape) => Some(layer_shape.shape.clone()),
LayerDataType::Text(text) => Some(text.to_subpath_nonmut(render_data)),
LayerDataType::NodeGraphFrame(NodeGraphFrameLayer { vector_data: Some(vector_data), .. }) => Some(Subpath::from_bezier_crate(&vector_data.subpaths)),
_ => document_layer.aabb_for_transform(DAffine2::IDENTITY, render_data).map(|[p1, p2]| Subpath::new_rect(p1, p2)),
}?;
@@ -171,8 +171,8 @@ impl Pivot {
let pivot = transform.inverse().transform_point2(position);
// Only update the pivot when computed position is finite. Infinite can happen when scale is 0.
if pivot.is_finite() {
let layer_path = layer_path.to_owned();
responses.push_back(Operation::SetPivot { layer_path, pivot: pivot.into() }.into());
let layer = layer_path.to_owned();
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
}
}
}
@@ -5,7 +5,6 @@ use crate::messages::tool::common_functionality::snapping::SnapManager;
use document_legacy::layers::style::RenderData;
use document_legacy::LayerId;
use document_legacy::Operation;
use glam::{DAffine2, DVec2, Vec2Swizzles};
@@ -54,9 +53,10 @@ impl Resize {
}
Some(
Operation::SetLayerTransformInViewport {
path: path.to_vec(),
transform: DAffine2::from_scale_angle_translation(size, 0., start).to_cols_array(),
GraphOperationMessage::TransformSet {
layer: path.to_vec(),
transform: DAffine2::from_scale_angle_translation(size, 0., start),
transform_in: TransformIn::Viewport,
}
.into(),
)
@@ -1,32 +1,34 @@
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*;
use bezier_rs::TValue;
use document_legacy::{LayerId, Operation};
use graphene_std::vector::consts::ManipulatorType;
use graphene_std::vector::manipulator_group::ManipulatorGroup;
use graphene_std::vector::manipulator_point::ManipulatorPoint;
use graphene_std::vector::subpath::{BezierId, Subpath};
use bezier_rs::{Bezier, TValue};
use document_legacy::LayerId;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::{ManipulatorPointId, SelectedType, VectorData};
use document_legacy::document::Document;
use glam::DVec2;
/// ShapeEditor is the container for all of the layer paths that are represented as [Subpath]s and provides
/// functionality required to query and create the [Subpath] / [ManipulatorGroup]s / [ManipulatorPoint]s.
///
/// Overview:
/// ```text
/// ShapeEditor
/// |
/// selected_layers <- Paths to selected layers that may contain Subpaths
/// / | \
/// Subpath ... Subpath <- Reference from layer paths, one Subpath per layer (for now, will eventually be a CompoundPath)
/// / | \
/// ManipulatorGroup ... ManipulatorGroup <- Subpath contains many ManipulatorGroups
/// ```
#[derive(Clone, Debug, Default)]
pub struct ShapeEditor {
pub struct SelectedLayerState {
selected_points: HashSet<ManipulatorPointId>,
}
impl SelectedLayerState {
pub fn is_selected(&self, point: ManipulatorPointId) -> bool {
self.selected_points.contains(&point)
}
pub fn select_point(&mut self, point: ManipulatorPointId) {
self.selected_points.insert(point);
}
pub fn deselect_point(&mut self, point: ManipulatorPointId) {
self.selected_points.remove(&point);
}
}
pub type SelectedShapeState = HashMap<Vec<LayerId>, SelectedLayerState>;
#[derive(Debug, Default)]
pub struct ShapeState {
// The layers we can select and edit manipulators (anchors and handles) from
selected_layers: Vec<Vec<LayerId>>,
pub selected_shape_state: SelectedShapeState,
}
pub struct SelectedPointsInfo<'a> {
@@ -36,333 +38,299 @@ pub struct SelectedPointsInfo<'a> {
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct ManipulatorPointInfo<'a> {
pub shape_layer_path: &'a [LayerId],
pub manipulator_group_id: u64,
pub manipulator_type: ManipulatorType,
pub point_id: ManipulatorPointId,
}
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
impl ShapeEditor {
impl ShapeState {
/// Select the first point within the selection threshold.
/// Returns a tuple of the points if found and the offset, or `None` otherwise.
pub fn select_point(&self, document: &Document, mouse_position: DVec2, select_threshold: f64, add_to_selection: bool, responses: &mut VecDeque<Message>) -> Option<SelectedPointsInfo> {
if self.selected_layers.is_empty() {
pub fn select_point(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64, add_to_selection: bool) -> Option<SelectedPointsInfo> {
if self.selected_shape_state.is_empty() {
return None;
}
if let Some((shape_layer_path, manipulator_group_id, manipulator_point_index)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) {
trace!("Selecting... manipulator group ID: {}, manipulator point index: {}", manipulator_group_id, manipulator_point_index);
if let Some((shape_layer_path, manipulator_point_id)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) {
trace!("Selecting... manipulator point: {:?}", manipulator_point_id);
// If the point we're selecting has already been selected
// we can assume this point exists.. since we did just click on it hence the unwrap
let is_point_selected = self.shape(document, shape_layer_path).unwrap().manipulator_groups().by_id(manipulator_group_id).unwrap().points[manipulator_point_index]
.as_ref()
.unwrap()
.editor_state
.is_selected;
let vector_data = document.layer(&shape_layer_path).ok()?.as_vector_data()?;
let manipulator_group = vector_data.manipulator_groups().find(|group| group.id == manipulator_point_id.group)?;
let point_position = manipulator_point_id.manipulator_type.get_position(manipulator_group)?;
let point_position = self.shape(document, shape_layer_path).unwrap().manipulator_groups().by_id(manipulator_group_id).unwrap().points[manipulator_point_index]
.as_ref()
.unwrap()
.position;
// The currently selected points (which are then modified to reflect the selection)
let mut points = self
.selected_layers()
.iter()
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
.flat_map(|(path, shape)| {
shape
.manipulator_groups()
.enumerate()
.filter(|(_id, manipulator_group)| manipulator_group.is_anchor_selected())
.flat_map(|(id, manipulator_group)| manipulator_group.selected_points().map(move |point| (id, point.manipulator_type)))
.map(|(anchor, manipulator_point)| ManipulatorPointInfo {
shape_layer_path: path.as_slice(),
manipulator_group_id: *anchor,
manipulator_type: manipulator_point,
})
})
.collect::<Vec<_>>();
let selected_shape_state = self.selected_shape_state.get(&shape_layer_path)?;
let already_selected = selected_shape_state.is_selected(manipulator_point_id);
// Should we select or deselect the point?
let should_select = if is_point_selected { !add_to_selection } else { true };
let new_selected = if already_selected { !add_to_selection } else { true };
// This is selecting the manipulator only for now, next to generalize to points
if should_select {
// If we're replacing the selection, clear all points in other selected shapes
let add = add_to_selection || is_point_selected;
if !add {
points.clear();
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
if new_selected {
let retain_existing_selection = add_to_selection || already_selected;
if !retain_existing_selection {
self.deselect_all();
}
// Add to the selected points
let point_info = ManipulatorPointInfo {
shape_layer_path,
manipulator_group_id,
manipulator_type: ManipulatorType::from_index(manipulator_point_index),
};
points.push(point_info);
responses.push_back(
Operation::SelectManipulatorPoints {
layer_path: shape_layer_path.to_vec(),
point_ids: vec![(point_info.manipulator_group_id, point_info.manipulator_type)],
add,
}
.into(),
);
let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?;
selected_shape_state.select_point(manipulator_point_id);
// Offset to snap the selected point to the cursor
let offset = document
.generate_transform_relative_to_viewport(shape_layer_path)
.generate_transform_relative_to_viewport(&shape_layer_path)
.map(|viewspace| mouse_position - viewspace.transform_point2(point_position))
.unwrap_or_default();
let points = self
.selected_shape_state
.iter()
.flat_map(|(shape_layer_path, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { shape_layer_path, point_id }))
.collect();
return Some(SelectedPointsInfo { points, offset });
} else {
responses.push_back(
Operation::DeselectManipulatorPoints {
layer_path: shape_layer_path.to_vec(),
point_ids: vec![(manipulator_group_id, ManipulatorType::from_index(manipulator_point_index))],
}
.into(),
);
points.retain(|x| {
*x != ManipulatorPointInfo {
shape_layer_path,
manipulator_group_id,
manipulator_type: ManipulatorType::from_index(manipulator_point_index),
}
});
let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?;
selected_shape_state.deselect_point(manipulator_point_id);
return None;
}
}
// Deselect all points if no nearby point
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
self.deselect_all();
None
}
/// A wrapper for `find_nearest_point_indices()` and returns a [ManipulatorPoint].
pub fn find_nearest_point<'a>(&'a self, document: &'a Document, mouse_position: DVec2, select_threshold: f64) -> Option<&'a ManipulatorPoint> {
let (shape_layer_path, manipulator_group_id, manipulator_point_index) = self.find_nearest_point_indices(document, mouse_position, select_threshold)?;
let selected_shape = self.shape(document, shape_layer_path).unwrap();
if let Some(manipulator_group) = selected_shape.manipulator_groups().by_id(manipulator_group_id) {
return manipulator_group.points[manipulator_point_index].as_ref();
}
None
pub fn deselect_all(&mut self) {
self.selected_shape_state.values_mut().for_each(|state| state.selected_points.clear());
}
/// Set the shapes we consider for selection, we will choose draggable manipulators from these shapes.
pub fn set_selected_layers(&mut self, target_layers: Vec<Vec<LayerId>>) {
self.selected_layers = target_layers;
self.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path));
for layer in target_layers {
self.selected_shape_state.entry(layer).or_insert_with(SelectedLayerState::default);
}
}
pub fn selected_layers(&self) -> &Vec<Vec<LayerId>> {
&self.selected_layers
}
pub fn selected_layers_ref(&self) -> Vec<&[LayerId]> {
self.selected_layers.iter().map(|l| l.as_slice()).collect::<Vec<_>>()
pub fn selected_layers(&self) -> impl Iterator<Item = &Vec<LayerId>> {
self.selected_shape_state.keys()
}
/// Clear all of the shapes we can modify.
pub fn clear_selected_layers(&mut self) {
self.selected_layers.clear();
self.selected_shape_state.clear();
}
pub fn has_selected_layers(&self) -> bool {
!self.selected_layers.is_empty()
}
/// Provide the currently selected manipulators by reference.
pub fn selected_manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorGroup> {
self.iter(document).flat_map(|shape| shape.selected_manipulator_groups())
!self.selected_shape_state.is_empty()
}
/// A mutable iterator of all the manipulators, regardless of selection.
pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorGroup> {
self.iter(document).flat_map(|shape| shape.manipulator_groups().iter())
pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a bezier_rs::ManipulatorGroup<ManipulatorGroupId>> {
self.iter(document).flat_map(|shape| shape.manipulator_groups())
}
// Sets the selected points to all points for the corresponding intersection
pub fn select_all_anchors(&self, responses: &mut VecDeque<Message>, itersections: Vec<u64>) {
responses.push_back(Operation::SelectAllAnchors { layer_path: itersections }.into());
pub fn select_all_anchors(&mut self, document: &Document, layer_path: &[LayerId]) {
let Ok(layer) = document.layer(layer_path) else { return };
let Some(vector_data) = layer.as_vector_data() else { return };
let Some(state) = self.selected_shape_state.get_mut(layer_path) else { return };
for manipulator in vector_data.manipulator_groups() {
state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor))
}
}
/// Provide the currently selected points by reference.
pub fn selected_points<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorPoint> {
self.selected_manipulator_groups(document).flat_map(|manipulator_group| manipulator_group.selected_points())
pub fn selected_points<'a>(&'a self) -> impl Iterator<Item = &'a ManipulatorPointId> {
self.selected_shape_state.values().flat_map(|state| &state.selected_points)
}
/// Move the selected points by dragging the mouse.
pub fn move_selected_points(&self, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque<Message>) {
for layer_path in &self.selected_layers {
responses.push_back(
DocumentMessage::MoveSelectedManipulatorPoints {
layer_path: layer_path.clone(),
delta: (delta.x, delta.y),
mirror_distance,
pub fn move_selected_points(&self, document: &Document, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque<Message>) {
for (layer_path, state) in &self.selected_shape_state {
let Ok(layer) = document.layer(&layer_path) else { continue };
let Some(vector_data) = layer.as_vector_data() else { continue };
let transform = document.multiply_transforms(&layer_path).unwrap_or_default();
let delta = transform.inverse().transform_vector2(delta);
for &point in state.selected_points.iter() {
if point.manipulator_type.is_handle() && state.is_selected(ManipulatorPointId::new(point.group, SelectedType::Anchor)) {
continue;
}
.into(),
);
let Some(group) = vector_data.manipulator_from_id(point.group) else { continue };
let mut move_point = |point: ManipulatorPointId| {
let Some(previous_position) = point.manipulator_type.get_position(group) else { return };
let position = previous_position + delta;
responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(),
modification: VectorDataModification::SetManipulatorPosition { point, position },
});
};
move_point(point);
if point.manipulator_type == SelectedType::Anchor {
move_point(ManipulatorPointId::new(point.group, SelectedType::InHandle));
move_point(ManipulatorPointId::new(point.group, SelectedType::OutHandle));
}
if mirror_distance && point.manipulator_type != SelectedType::Anchor && vector_data.mirror_angle.contains(&point.group) {
let Some(mut origional_handle_position) = point.manipulator_type.get_position(group) else { continue };
origional_handle_position += delta;
let point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite());
if state.is_selected(point) {
continue;
}
let position = group.anchor - (origional_handle_position - group.anchor);
responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(),
modification: VectorDataModification::SetManipulatorPosition { point, position },
});
}
}
}
}
/// The opposing handle lengths.
pub fn opposing_handle_lengths(&self, document: &Document) -> HashMap<Vec<LayerId>, HashMap<u64, f64>> {
self.selected_layers()
pub fn opposing_handle_lengths(&self, document: &Document) -> HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>> {
self.selected_shape_state
.iter()
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
.map(|(path, shape)| {
let opposing_handle_lengths = shape
.manipulator_groups()
.enumerate()
.filter_map(|(id, manipulator_group)| {
// We will keep track of the opposing handle length when:
// i) Both handles exist and exactly one is selected.
// ii) The anchor is not selected.
// iii) We have to mirror the angle between handles.
.filter_map(|(path, state)| {
let layer = document.layer(path).ok()?;
let vector_data = layer.as_vector_data()?;
let opposing_handle_lengths = vector_data
.subpaths
.iter()
.flat_map(|subpath| {
subpath.manipulator_groups().iter().filter_map(|manipulator_group| {
// We will keep track of the opposing handle length when:
// i) Both handles exist and exactly one is selected.
// ii) The anchor is not selected.
// iii) We have to mirror the angle between handles.
if !manipulator_group.editor_state.mirror_angle_between_handles {
return None;
}
let in_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle));
let out_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle));
let anchor_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor));
let mut selected_handles = manipulator_group.selected_handles();
let handle = selected_handles.next()?;
// Check that handle is the only selected handle.
if selected_handles.next().is_none() {
let opposing_handle_position = manipulator_group.opposing_handle(handle)?.position;
let anchor = manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
if !anchor.is_selected() {
let opposing_handle_length = opposing_handle_position.distance(anchor.position);
Some((*id, opposing_handle_length))
} else {
None
if anchor_selected {
return None;
}
} else {
None
}
let single_selected_handle = match (in_handle_selected, out_handle_selected) {
(true, false) => SelectedType::InHandle,
(false, true) => SelectedType::OutHandle,
_ => return None,
};
let opposing_handle_position = single_selected_handle.opposite().get_position(manipulator_group)?;
let opposing_handle_length = opposing_handle_position.distance(manipulator_group.anchor);
Some((manipulator_group.id, opposing_handle_length))
})
})
.collect::<HashMap<_, _>>();
(path.clone(), opposing_handle_lengths)
Some((path.clone(), opposing_handle_lengths))
})
.collect::<HashMap<_, _>>()
}
/// Reset the opposing handle lengths.
pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &HashMap<Vec<LayerId>, HashMap<u64, f64>>, responses: &mut VecDeque<Message>) {
self.selected_layers()
.iter()
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
.filter_map(|(path, shape)| opposing_handle_lengths.get(path).map(|layer_opposing_handle_lengths| (path, shape, layer_opposing_handle_lengths)))
.flat_map(|(path, shape, layer_opposing_handle_lengths)| {
shape
.manipulator_groups()
.enumerate()
.map(move |(id, manipulator_group)| (path, layer_opposing_handle_lengths, id, manipulator_group))
})
.for_each(|(path, layer_opposing_handle_lengths, id, manipulator_group)| {
if !manipulator_group.editor_state.mirror_angle_between_handles {
return;
}
pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>>, responses: &mut VecDeque<Message>) {
for (path, state) in &self.selected_shape_state {
let Ok(layer) = document.layer(path) else { continue };
let Some(vector_data) = layer.as_vector_data() else { continue };
let Some(opposing_handle_lengths) = opposing_handle_lengths.get(path) else { continue };
let opposing_handle_length = if let Some(length) = layer_opposing_handle_lengths.get(id) {
length
} else {
return;
};
let mut selected_handles = manipulator_group.selected_handles();
let handle = if let Some(handle) = selected_handles.next() {
handle
} else {
return;
};
// Check that handle is the only selected handle.
if selected_handles.next().is_none() {
let opposing_handle = if let Some(opposing_handle) = manipulator_group.opposing_handle(handle) {
opposing_handle
} else {
return;
};
let anchor = if let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() {
anchor
} else {
return;
};
if anchor.is_selected() {
return;
for subpath in &vector_data.subpaths {
for manipulator_group in subpath.manipulator_groups() {
if !vector_data.mirror_angle.contains(&manipulator_group.id) {
continue;
}
if let Some(offset) = (opposing_handle.position - anchor.position).try_normalize() {
let new_opposing_handle_position = anchor.position + offset * (*opposing_handle_length);
assert!(new_opposing_handle_position.is_finite(), "Opposing handle not finite!");
responses.push_back(
Operation::MoveManipulatorPoint {
layer_path: path.clone(),
id: *id,
manipulator_type: opposing_handle.manipulator_type,
position: new_opposing_handle_position.into(),
}
.into(),
);
let Some(opposing_handle_length) = opposing_handle_lengths.get(&manipulator_group.id) else { continue };
let in_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle));
let out_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle));
let anchor_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor));
if anchor_selected {
continue;
}
let single_selected_handle = match (in_handle_selected, out_handle_selected) {
(true, false) => SelectedType::InHandle,
(false, true) => SelectedType::OutHandle,
_ => continue,
};
let Some(opposing_handle) = single_selected_handle.opposite().get_position(manipulator_group) else { continue };
let Some(offset) = (opposing_handle - manipulator_group.anchor).try_normalize() else { continue };
let point = ManipulatorPointId::new(manipulator_group.id, single_selected_handle.opposite());
let position = manipulator_group.anchor + offset * (*opposing_handle_length);
assert!(position.is_finite(), "Opposing handle not finite!");
responses.add(GraphOperationMessage::Vector {
layer: path.to_vec(),
modification: VectorDataModification::SetManipulatorPosition { point, position },
});
}
});
}
}
}
/// Dissolve the selected points.
pub fn delete_selected_points(&self, responses: &mut VecDeque<Message>) {
responses.push_back(DocumentMessage::DeleteSelectedManipulatorPoints.into());
}
/// Toggle if the handles should mirror angle across the anchor position.
pub fn toggle_handle_mirroring_on_selected(&self, toggle_angle: bool, responses: &mut VecDeque<Message>) {
for layer_path in &self.selected_layers {
responses.push_back(
DocumentMessage::ToggleSelectedHandleMirroring {
layer_path: layer_path.clone(),
toggle_angle,
}
.into(),
);
for (layer, state) in &self.selected_shape_state {
for &point in &state.selected_points {
responses.add(GraphOperationMessage::Vector {
layer: layer.to_vec(),
modification: VectorDataModification::RemoveManipulatorPoint { point },
})
}
}
}
/// Deselect all manipulators from the shapes that the manipulation handler has created.
pub fn deselect_all_points(&self, responses: &mut VecDeque<Message>) {
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
/// Toggle if the handles should mirror angle across the anchor position.
pub fn toggle_handle_mirroring_on_selected(&self, responses: &mut VecDeque<Message>) {
for (layer, state) in &self.selected_shape_state {
for point in &state.selected_points {
responses.add(GraphOperationMessage::Vector {
layer: layer.to_vec(),
modification: VectorDataModification::ToggleManipulatorHandleMirroring { id: point.group },
})
}
}
}
/// Iterate over the shapes.
pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a Subpath> + 'a {
self.selected_layers.iter().flat_map(|layer_id| document.layer(layer_id)).filter_map(|shape| shape.as_subpath())
pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorData> + 'a {
self.selected_shape_state
.keys()
.flat_map(|layer_id| document.layer(layer_id))
.filter_map(|shape| shape.as_vector_data())
}
/// Find a [ManipulatorPoint] that is within the selection threshold and return the layer path, an index to the [ManipulatorGroup], and an enum index for [ManipulatorPoint].
/// Return value is an `Option` of the tuple representing `(layer path, ManipulatorGroup ID, ManipulatorType enum index)`.
fn find_nearest_point_indices(&self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(&[LayerId], u64, usize)> {
if self.selected_layers.is_empty() {
pub fn find_nearest_point_indices(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(Vec<LayerId>, ManipulatorPointId)> {
if self.selected_shape_state.is_empty() {
return None;
}
let select_threshold_squared = select_threshold * select_threshold;
// Find the closest control point among all elements of shapes_to_modify
for layer in self.selected_layers.iter() {
if let Some((manipulator_id, manipulator_point_index, distance_squared)) = self.closest_point_in_layer(document, layer, mouse_position) {
for layer in self.selected_shape_state.keys() {
if let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(document, layer, mouse_position) {
// Choose the first point under the threshold
if distance_squared < select_threshold_squared {
trace!("Selecting... manipulator ID: {}, manipulator point index: {}", manipulator_id, manipulator_point_index);
return Some((layer, manipulator_id, manipulator_point_index));
trace!("Selecting... manipulator point: {:?}", manipulator_point_id);
return Some((layer.clone(), manipulator_point_id));
}
}
}
@@ -373,51 +341,54 @@ impl ShapeEditor {
// TODO Use quadtree or some equivalent spatial acceleration structure to improve this to O(log(n))
/// Find the closest manipulator, manipulator point, and distance so we can select path elements.
/// Brute force comparison to determine which manipulator (handle or anchor) we want to select taking O(n) time.
/// Return value is an `Option` of the tuple representing `(manipulator ID, manipulator point index, distance squared)`.
fn closest_point_in_layer(&self, document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(u64, usize, f64)> {
let mut closest_distance_squared: f64 = f64::MAX; // Not ideal
let mut result: Option<(u64, usize, f64)> = None;
/// Return value is an `Option` of the tuple representing `(ManipulatorPointId, distance squared)`.
fn closest_point_in_layer(document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> {
let mut closest_distance_squared: f64 = f64::MAX;
let mut result = None;
if let Some(shape) = document.layer(layer_path).ok()?.as_subpath() {
let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
for (manipulator_id, manipulator) in shape.manipulator_groups().enumerate() {
let manipulator_point_index = manipulator.closest_point(&viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE);
if let Some(point) = &manipulator.points[manipulator_point_index] {
if point.editor_state.can_be_selected {
let distance_squared = viewspace.transform_point2(point.position).distance_squared(pos);
if distance_squared < closest_distance_squared {
closest_distance_squared = distance_squared;
result = Some((*manipulator_id, manipulator_point_index, distance_squared));
}
}
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?;
let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
for subpath in &vector_data.subpaths {
for manipulator in subpath.manipulator_groups() {
let (selected, distance_squared) = SelectedType::closest_widget(manipulator, viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE);
if distance_squared < closest_distance_squared {
closest_distance_squared = distance_squared;
result = Some((ManipulatorPointId::new(manipulator.id, selected), distance_squared));
}
}
}
result
}
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
///
/// Returns a tuple of [`BezierId`] and `t` as an f64.
fn closest_segment(&self, document: &Document, layer_path: &[LayerId], position: glam::DVec2, tolerance: f64) -> Option<(BezierId, f64)> {
/// Returns a tuple of subpath_index, manipulator_start and `t` as an f64.
fn closest_segment(&self, document: &Document, layer_path: &[LayerId], position: glam::DVec2, tolerance: f64) -> Option<(ManipulatorGroupId, ManipulatorGroupId, Bezier, f64)> {
let transform = document.generate_transform_relative_to_viewport(layer_path).ok()?;
let layer_pos = transform.inverse().transform_point2(position);
let projection_options = bezier_rs::ProjectionOptions { lut_size: 5, ..Default::default() };
let mut result: Option<(BezierId, f64)> = None;
let mut result = None;
let mut closest_distance_squared: f64 = tolerance * tolerance;
for bezier_id in document.layer(layer_path).ok()?.as_subpath()?.bezier_iter() {
let bezier = bezier_id.internal;
let t = bezier.project(layer_pos, projection_options);
let layerspace = bezier.evaluate(TValue::Parametric(t));
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?;
let screenspace = transform.transform_point2(layerspace);
let distance_squared = screenspace.distance_squared(position);
for subpath in &vector_data.subpaths {
for (manipulator_index, bezier) in subpath.iter().enumerate() {
let t = bezier.project(layer_pos, projection_options);
let layerspace = bezier.evaluate(TValue::Parametric(t));
if distance_squared < closest_distance_squared {
closest_distance_squared = distance_squared;
result = Some((bezier_id, t));
let screenspace = transform.transform_point2(layerspace);
let distance_squared = screenspace.distance_squared(position);
if distance_squared < closest_distance_squared {
closest_distance_squared = distance_squared;
let start = subpath.manipulator_groups()[manipulator_index];
let end = subpath.manipulator_groups()[(manipulator_index + 1) % subpath.len()];
result = Some((start.id, end.id, bezier, t));
}
}
}
@@ -426,34 +397,36 @@ impl ShapeEditor {
/// Handles the splitting of a curve to insert new points (which can be activated by double clicking on a curve with the Path tool).
pub fn split(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) {
for layer_path in &self.selected_layers {
if let Some((bezier_id, t)) = self.closest_segment(document, layer_path, position, tolerance) {
let [first, second] = bezier_id.internal.split(TValue::Parametric(t));
for layer_path in self.selected_layers() {
if let Some((start, end, bezier, t)) = self.closest_segment(document, layer_path, position, tolerance) {
let [first, second] = bezier.split(TValue::Parametric(t));
// Adjust the first manipulator group's out handle
let out_handle = Operation::SetManipulatorPoints {
layer_path: layer_path.clone(),
id: bezier_id.start,
manipulator_type: ManipulatorType::OutHandle,
position: first.handle_start().map(|p| p.into()),
let point = ManipulatorPointId::new(start, SelectedType::OutHandle);
let position = first.handle_start().unwrap_or(first.start());
let out_handle = GraphOperationMessage::Vector {
layer: layer_path.clone(),
modification: VectorDataModification::SetManipulatorPosition { point, position },
};
responses.add(out_handle);
// Insert a new manipulator group between the existing ones
let insert = Operation::InsertManipulatorGroup {
layer_path: layer_path.clone(),
manipulator_group: ManipulatorGroup::new_with_handles(first.end(), first.handle_end(), second.handle_start()),
after_id: bezier_id.end,
let manipulator_group = bezier_rs::ManipulatorGroup::new(first.end(), first.handle_end(), second.handle_start());
let insert = GraphOperationMessage::Vector {
layer: layer_path.clone(),
modification: VectorDataModification::AddManipulatorGroup { manipulator_group, after_id: start },
};
responses.add(insert);
// Adjust the last manipulator group's in handle
let in_handle = Operation::SetManipulatorPoints {
layer_path: layer_path.clone(),
id: bezier_id.end,
manipulator_type: ManipulatorType::InHandle,
position: second.handle_end().map(|p| p.into()),
let point = ManipulatorPointId::new(end, SelectedType::InHandle);
let position = second.handle_end().unwrap_or(second.end());
let in_handle = GraphOperationMessage::Vector {
layer: layer_path.clone(),
modification: VectorDataModification::SetManipulatorPosition { point, position },
};
responses.add(in_handle);
responses.extend([out_handle.into(), insert.into(), in_handle.into()]);
return;
}
}
@@ -462,53 +435,57 @@ impl ShapeEditor {
/// Handles the flipping between sharp corner and smooth (which can be activated by double clicking on an anchor with the Path tool).
pub fn flip_sharp(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
let mut process_layer = |layer_path| {
let manipulator_groups = document.layer(layer_path).ok()?.as_subpath()?.manipulator_groups();
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?;
let transform_to_screenspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
let mut result = None;
let mut closest_distance_squared = tolerance * tolerance;
// Find the closest anchor point on the current layer
for (index, (&bezier_id, group)) in manipulator_groups.enumerate().enumerate() {
if let Some(anchor) = &group.points[ManipulatorType::Anchor as usize] {
let screenspace = transform_to_screenspace.transform_point2(anchor.position);
for (subpath_index, subpath) in vector_data.subpaths.iter().enumerate() {
for (manipulator_index, manipulator) in subpath.manipulator_groups().iter().enumerate() {
let screenspace = transform_to_screenspace.transform_point2(manipulator.anchor);
let distance_squared = screenspace.distance_squared(position);
if distance_squared < closest_distance_squared {
closest_distance_squared = distance_squared;
result = Some((anchor.position, index, bezier_id, group));
result = Some((subpath_index, manipulator_index, manipulator));
}
}
}
let (anchor_position, index, bezier_id, group) = result?;
let (subpath_index, index, manipulator) = result?;
let anchor_position = manipulator.anchor;
let subpath = &vector_data.subpaths[subpath_index];
// Check by comparing the handle positions to the anchor if this maniuplator group is a point
let already_sharp = match &group.points {
[_, Some(in_handle), Some(out_handle)] => anchor_position.abs_diff_eq(in_handle.position, f64::EPSILON * 100.) && anchor_position.abs_diff_eq(out_handle.position, f64::EPSILON * 100.),
[_, Some(handle), None] | [_, None, Some(handle)] => anchor_position.abs_diff_eq(handle.position, f64::EPSILON * 100.),
[_, None, None] => true,
let already_sharp = match (manipulator.in_handle, manipulator.out_handle) {
(Some(in_handle), Some(out_handle)) => anchor_position.abs_diff_eq(in_handle, 1e-10) && anchor_position.abs_diff_eq(out_handle, 1e-10),
(Some(handle), None) | (None, Some(handle)) => anchor_position.abs_diff_eq(handle, 1e-10),
(None, None) => true,
};
let manipulator_groups = subpath.manipulator_groups();
let (in_handle, out_handle) = if already_sharp {
let is_closed = manipulator_groups.last().filter(|group| group.is_close()).is_some();
let is_closed = subpath.closed();
// Grab the next and previous manipulator groups by simply looking at the next / previous index
let mut previous_position = index.checked_sub(1).and_then(|index| manipulator_groups.by_index(index)).and_then(|group| group.points[0].as_ref());
let mut next_position = manipulator_groups.by_index(index + 1).and_then(|group| group.points[0].as_ref());
let mut previous_position = index.checked_sub(1).and_then(|index| manipulator_groups.get(index)).map(|group| group.anchor);
let mut next_position = manipulator_groups.get(index + 1).map(|group| group.anchor);
// Wrapping around closed path (assuming format is point elements then a single close path)
// Wrapping around closed path
if is_closed {
previous_position = previous_position.or_else(|| manipulator_groups.iter().nth_back(1).and_then(|group| group.points[0].as_ref()));
next_position = next_position.or_else(|| manipulator_groups.first().and_then(|group| group.points[0].as_ref()));
previous_position = previous_position.or_else(|| manipulator_groups.last().map(|group| group.anchor));
next_position = next_position.or_else(|| manipulator_groups.first().map(|group| group.anchor));
}
// To find the length of the new tangent we just take the distance to the anchor and divide by 3 (pretty arbitrary)
let length_previous = previous_position.map(|point| (point.position - anchor_position).length() / 3.);
let length_next = next_position.map(|point| (point.position - anchor_position).length() / 3.);
let length_previous = previous_position.map(|point| (point - anchor_position).length() / 3.);
let length_next = next_position.map(|point| (point - anchor_position).length() / 3.);
// Use the position relative to the anchor
let previous_angle = previous_position.map(|point| (point.position - anchor_position)).map(|pos| pos.y.atan2(pos.x));
let next_angle = next_position.map(|point| (point.position - anchor_position)).map(|pos| pos.y.atan2(pos.x));
let previous_angle = previous_position.map(|point| (point - anchor_position)).map(|pos| pos.y.atan2(pos.x));
let next_angle = next_position.map(|point| (point - anchor_position)).map(|pos| pos.y.atan2(pos.x));
// The direction of the handles is either the perpendicular vector to the sum of the anchors' positions or just the anchor's position (if only one)
let handle_direction = match (previous_angle, next_angle) {
@@ -519,21 +496,20 @@ impl ShapeEditor {
};
// Mirror the angle but not the distance
responses.push_back(
Operation::SetManipulatorHandleMirroring {
layer_path: layer_path.to_vec(),
id: bezier_id,
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorHandleMirroring {
id: manipulator.id,
mirror_angle: true,
}
.into(),
);
},
});
let (sin, cos) = handle_direction.sin_cos();
let mut handle_vector = DVec2::new(cos, sin);
// Flip the vector if it is not facing towards the same direction as the anchor
if previous_position.filter(|pos| (pos.position - anchor_position).normalize().dot(handle_vector) < 0.).is_some()
|| next_position.filter(|pos| (pos.position - anchor_position).normalize().dot(handle_vector) > 0.).is_some()
if previous_position.filter(|&pos| (pos - anchor_position).normalize().dot(handle_vector) < 0.).is_some()
|| next_position.filter(|&pos| (pos - anchor_position).normalize().dot(handle_vector) > 0.).is_some()
{
handle_vector = -handle_vector;
}
@@ -548,34 +524,26 @@ impl ShapeEditor {
// Push both in and out handles into the correct position
if let Some(in_handle) = in_handle {
let in_handle = Operation::SetManipulatorPoints {
layer_path: layer_path.to_vec(),
id: bezier_id,
manipulator_type: ManipulatorType::InHandle,
position: Some(in_handle.into()),
};
responses.push_back(in_handle.into());
let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle);
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorPosition { point, position: in_handle },
});
}
if let Some(out_handle) = out_handle {
let out_handle = Operation::SetManipulatorPoints {
layer_path: layer_path.to_vec(),
id: bezier_id,
manipulator_type: ManipulatorType::OutHandle,
position: Some(out_handle.into()),
};
responses.push_back(out_handle.into());
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorPosition { point, position: out_handle },
});
}
Some(true)
};
for layer_path in &self.selected_layers {
for layer_path in self.selected_shape_state.keys() {
if let Some(result) = process_layer(layer_path) {
return result;
}
}
false
}
fn shape<'a>(&'a self, document: &'a Document, layer_id: &[u64]) -> Option<&'a Subpath> {
document.layer(layer_id).ok()?.as_subpath()
}
}
@@ -6,10 +6,10 @@ use crate::consts::{
};
use crate::messages::prelude::*;
use document_legacy::layers::layer_info::{Layer, LayerDataType};
use document_legacy::layers::layer_info::Layer;
use document_legacy::layers::style::{self, Stroke};
use document_legacy::{LayerId, Operation};
use graphene_std::vector::consts::ManipulatorType;
use graphene_core::vector::{ManipulatorPointId, SelectedType};
use glam::{DAffine2, DVec2};
use std::f64::consts::PI;
@@ -260,35 +260,26 @@ impl SnapManager {
include_handles: bool,
ignore_points: &[ManipulatorPointInfo],
) {
if let LayerDataType::Shape(shape_layer) = &layer.data {
let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap();
let snap_points = shape_layer
.shape
.manipulator_groups()
.enumerate()
.flat_map(|(id, shape)| {
if include_handles {
[
(*id, &shape.points[ManipulatorType::Anchor]),
(*id, &shape.points[ManipulatorType::InHandle]),
(*id, &shape.points[ManipulatorType::OutHandle]),
]
} else {
[(*id, &shape.points[ManipulatorType::Anchor]), (0, &None), (0, &None)]
}
})
.filter_map(|(id, point)| point.as_ref().map(|val| (id, val)))
.filter(|(id, point)| {
!ignore_points.contains(&ManipulatorPointInfo {
shape_layer_path: path,
manipulator_group_id: *id,
manipulator_type: point.manipulator_type,
})
})
.map(|(_id, point)| DVec2::new(point.position.x, point.position.y))
.map(|pos| transform.transform_point2(pos));
self.add_snap_points(document_message_handler, input, snap_points);
}
let Some(vector_data) = &layer.as_vector_data() else { return };
let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap();
let snap_points = vector_data
.manipulator_groups()
.flat_map(|group| {
if include_handles {
[
Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)),
group.in_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::InHandle), pos)),
group.out_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::OutHandle), pos)),
]
} else {
[Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)), None, None]
}
})
.flatten()
.filter(|&(point_id, _)| !ignore_points.contains(&ManipulatorPointInfo { shape_layer_path: path, point_id }))
.map(|(_, pos)| transform.transform_point2(pos));
self.add_snap_points(document_message_handler, input, snap_points);
}
/// Adds all of the shape handles in the document, including bézier handles of the points specified
@@ -1,4 +1,6 @@
use super::utility_types::{tool_message_to_tool_type, ToolFsmState};
use super::common_functionality::overlay_renderer::OverlayRenderer;
use super::common_functionality::shape_editor::ShapeState;
use super::utility_types::{tool_message_to_tool_type, ToolActionHandlerData, ToolFsmState};
use crate::application::generate_uuid;
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::layout::utility_types::misc::LayoutTarget;
@@ -11,8 +13,10 @@ use graphene_core::raster::color::Color;
#[derive(Debug, Default)]
pub struct ToolMessageHandler {
tool_state: ToolFsmState,
transform_layer_handler: TransformLayerMessageHandler,
pub tool_state: ToolFsmState,
pub transform_layer_handler: TransformLayerMessageHandler,
pub shape_overlay: OverlayRenderer,
pub shape_editor: ShapeState,
}
impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocessorMessageHandler, &PersistentData)> for ToolMessageHandler {
@@ -31,7 +35,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
#[remain::unsorted]
ToolMessage::TransformLayer(message) => self
.transform_layer_handler
.process_message(message, responses, (document, input, &render_data, &self.tool_state.tool_data)),
.process_message(message, responses, (document, input, &render_data, &self.tool_state.tool_data, &mut self.shape_editor)),
#[remain::unsorted]
ToolMessage::ActivateToolSelect => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Select }.into()),
@@ -70,7 +74,6 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
ToolMessage::ActivateTool { tool_type } => {
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;
// Do nothing if switching to the same tool
@@ -81,13 +84,26 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
// Send the old and new tools a transition to their FSM Abort states
let mut send_abort_to_tool = |tool_type, update_hints_and_cursor: bool| {
if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
let mut data = ToolActionHandlerData {
document,
document_id,
global_tool_data: &self.tool_state.document_tool_data,
input,
render_data: &render_data,
shape_overlay: &mut self.shape_overlay,
shape_editor: &mut self.shape_editor,
};
if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort {
tool.process_message(tool_abort_message, responses, (document, document_id, document_data, input, &render_data));
tool.process_message(tool_abort_message, responses, &mut data);
}
if update_hints_and_cursor {
tool.process_message(ToolMessage::UpdateHints, responses, (document, document_id, document_data, input, &render_data));
tool.process_message(ToolMessage::UpdateCursor, responses, (document, document_id, document_data, input, &render_data));
if self.transform_layer_handler.is_transforming() {
self.transform_layer_handler.hints(responses);
} else {
tool.process_message(ToolMessage::UpdateHints, responses, &mut data)
}
tool.process_message(ToolMessage::UpdateCursor, responses, &mut data);
}
}
};
@@ -150,13 +166,19 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
document_data.update_working_colors(responses);
responses.push_back(FrontendMessage::TriggerRefreshBoundsOfViewports.into());
let mut data = ToolActionHandlerData {
document,
document_id,
global_tool_data: &self.tool_state.document_tool_data,
input,
render_data: &render_data,
shape_overlay: &mut self.shape_overlay,
shape_editor: &mut self.shape_editor,
};
// Set initial hints and cursor
tool_data
.active_tool_mut()
.process_message(ToolMessage::UpdateHints, responses, (document, document_id, document_data, input, &render_data));
tool_data
.active_tool_mut()
.process_message(ToolMessage::UpdateCursor, responses, (document, document_id, document_data, input, &render_data));
tool_data.active_tool_mut().process_message(ToolMessage::UpdateHints, responses, &mut data);
tool_data.active_tool_mut().process_message(ToolMessage::UpdateCursor, responses, &mut data);
}
ToolMessage::RefreshToolOptions => {
let tool_data = &mut self.tool_state.tool_data;
@@ -210,12 +232,28 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
ToolMessage::UpdateCursor | ToolMessage::UpdateHints => self.tool_state.tool_data.active_tool_type,
tool_message => tool_message_to_tool_type(tool_message),
};
let document_data = &self.tool_state.document_tool_data;
let tool_data = &mut self.tool_state.tool_data;
if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
if tool_type == tool_data.active_tool_type {
tool.process_message(tool_message, responses, (document, document_id, document_data, input, &render_data));
let mut data = ToolActionHandlerData {
document,
document_id,
global_tool_data: &self.tool_state.document_tool_data,
input,
render_data: &render_data,
shape_overlay: &mut self.shape_overlay,
shape_editor: &mut self.shape_editor,
};
if matches!(tool_message, ToolMessage::UpdateHints) {
if self.transform_layer_handler.is_transforming() {
self.transform_layer_handler.hints(responses);
} else {
tool.process_message(ToolMessage::UpdateHints, responses, &mut data)
}
} else {
tool.process_message(tool_message, responses, &mut data);
}
}
}
}
@@ -58,8 +58,8 @@ impl ToolMetadata for ArtboardTool {
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ArtboardTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ArtboardTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false);
}
@@ -112,7 +112,7 @@ impl Fsm for ArtboardToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -1,15 +1,16 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::layers::style;
use document_legacy::Operation;
use graphene_core::vector::style::Fill;
use glam::DAffine2;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)]
@@ -49,8 +50,8 @@ impl ToolMetadata for EllipseTool {
impl PropertyHolder for EllipseTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EllipseTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EllipseTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
}
@@ -100,7 +101,13 @@ impl Fsm for EllipseToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData {
document,
global_tool_data,
input,
render_data,
..
}: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -114,18 +121,32 @@ impl Fsm for EllipseToolFsmState {
(Ready, DragStart) => {
shape_data.start(responses, document, input, render_data);
responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into());
responses.push_back(
Operation::AddEllipse {
path: shape_data.path.clone().unwrap(),
insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(),
style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
}
.into(),
);
// Create a new layer path for this shape
let layer_path = document.get_path_for_new_layer();
shape_data.path = Some(layer_path.clone());
// Create a new ellipse vector shape
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
let manipulator_groups = subpath.manipulator_groups().to_vec();
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
// Set the four manipulator groups to have their handle angles mirrored by default
for manipulator_group in manipulator_groups {
responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(),
modification: VectorDataModification::SetManipulatorHandleMirroring {
id: manipulator_group.id,
mirror_angle: true,
},
});
}
// Set the fill color to the primary working color
responses.add(GraphOperationMessage::FillSet {
layer: layer_path,
fill: Fill::solid(global_tool_data.primary_color),
});
Drawing
}
@@ -43,8 +43,8 @@ impl ToolMetadata for EyedropperTool {
impl PropertyHolder for EyedropperTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EyedropperTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EyedropperTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
}
@@ -87,7 +87,7 @@ impl Fsm for EyedropperToolFsmState {
self,
event: ToolMessage,
_tool_data: &mut Self::ToolData,
(_document, _document_id, global_tool_data, input, _render_data): ToolActionHandlerData,
ToolActionHandlerData { global_tool_data, input, .. }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -8,7 +8,6 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::intersection::Quad;
use document_legacy::layers::style::Fill;
use document_legacy::Operation;
use glam::DVec2;
use serde::{Deserialize, Serialize};
@@ -46,8 +45,8 @@ impl ToolMetadata for FillTool {
impl PropertyHolder for FillTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FillTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
}
@@ -84,7 +83,13 @@ impl Fsm for FillToolFsmState {
self,
event: ToolMessage,
_tool_data: &mut Self::ToolData,
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData {
document,
global_tool_data,
input,
render_data,
..
}: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -106,9 +111,12 @@ impl Fsm for FillToolFsmState {
};
let fill = Fill::Solid(color);
responses.push_back(DocumentMessage::StartTransaction.into());
responses.push_back(Operation::SetLayerFill { path: path.to_vec(), fill }.into());
responses.push_back(DocumentMessage::CommitTransaction.into());
responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![path.to_vec()],
});
responses.add(GraphOperationMessage::FillSet { layer: path.to_vec(), fill });
responses.add(DocumentMessage::CommitTransaction);
}
Ready
@@ -3,14 +3,15 @@ use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::layers::style;
use document_legacy::LayerId;
use document_legacy::Operation;
use graphene_core::vector::style::Stroke;
use glam::{DAffine2, DVec2};
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)]
@@ -82,8 +83,8 @@ impl PropertyHolder for FreehandTool {
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FreehandTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message {
match action {
FreehandToolMessageOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
@@ -137,7 +138,9 @@ impl Fsm for FreehandToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, global_tool_data, input, _render_data): ToolActionHandlerData,
ToolActionHandlerData {
document, global_tool_data, input, ..
}: &mut ToolActionHandlerData,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -159,7 +162,7 @@ impl Fsm for FreehandToolFsmState {
tool_data.weight = tool_options.line_weight;
responses.push_back(add_polyline(tool_data, global_tool_data));
add_polyline(tool_data, global_tool_data, responses);
Drawing
}
@@ -170,15 +173,14 @@ impl Fsm for FreehandToolFsmState {
tool_data.points.push(pos);
}
responses.push_back(remove_preview(tool_data));
responses.push_back(add_polyline(tool_data, global_tool_data));
add_polyline(tool_data, global_tool_data, responses);
Drawing
}
(Drawing, DragStop) | (Drawing, Abort) => {
if tool_data.points.len() >= 2 {
responses.push_back(remove_preview(tool_data));
responses.push_back(add_polyline(tool_data, global_tool_data));
add_polyline(tool_data, global_tool_data, responses);
responses.push_back(DocumentMessage::CommitTransaction.into());
} else {
responses.push_back(DocumentMessage::AbortTransaction.into());
@@ -214,15 +216,13 @@ fn remove_preview(data: &FreehandToolData) -> Message {
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into()
}
fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData) -> Message {
let points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x, p.y)).collect();
fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
let layer_path = data.path.clone().unwrap();
let subpath = bezier_rs::Subpath::from_anchors(data.points.iter().copied(), false);
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
Operation::AddPolyline {
path: data.path.clone().unwrap(),
insert_index: -1,
transform: DAffine2::IDENTITY.to_cols_array(),
points,
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight)), style::Fill::None),
}
.into()
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(tool_data.primary_color, data.weight),
});
}
@@ -75,8 +75,8 @@ impl ToolMetadata for GradientTool {
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message {
match action {
GradientOptionsUpdate::Type(gradient_type) => {
@@ -353,8 +353,8 @@ impl SelectedGradient {
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
self.gradient.transform = self.transform;
let fill = Fill::Gradient(self.gradient.clone());
let path = self.path.clone();
responses.push_back(Operation::SetLayerFill { path, fill }.into());
let layer = self.path.clone();
responses.add(GraphOperationMessage::FillSet { layer, fill });
}
}
@@ -396,7 +396,13 @@ impl Fsm for GradientToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData {
document,
global_tool_data,
input,
render_data,
..
}: &mut ToolActionHandlerData,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -450,8 +456,8 @@ impl Fsm for GradientToolFsmState {
// The gradient has only one point and so should become a fill
if selected_gradient.gradient.positions.len() == 1 {
let fill = Fill::Solid(selected_gradient.gradient.positions[0].1.unwrap_or(Color::BLACK));
let path = selected_gradient.path.clone();
responses.push_back(Operation::SetLayerFill { path, fill }.into());
let layer = selected_gradient.path.clone();
responses.add(GraphOperationMessage::FillSet { layer, fill });
return self;
}
@@ -37,8 +37,8 @@ pub enum ImaginateToolMessage {
impl PropertyHolder for ImaginateTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ImaginateTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ImaginateTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
@@ -100,7 +100,7 @@ impl Fsm for ImaginateToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -5,15 +5,15 @@ use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::layers::style;
use document_legacy::LayerId;
use document_legacy::Operation;
use graphene_core::vector::style::Stroke;
use glam::{DAffine2, DVec2};
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)]
@@ -82,8 +82,8 @@ impl PropertyHolder for LineTool {
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for LineTool {
fn process_message(&mut self, message: ToolMessage, messages: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for LineTool {
fn process_message(&mut self, message: ToolMessage, messages: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message {
match action {
LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
@@ -137,7 +137,13 @@ impl Fsm for LineToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData {
document,
global_tool_data,
input,
render_data,
..
}: &mut ToolActionHandlerData,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -151,22 +157,19 @@ impl Fsm for LineToolFsmState {
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
tool_data.drag_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X);
responses.push_back(DocumentMessage::StartTransaction.into());
tool_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into());
let layer_path = document.get_path_for_new_layer();
tool_data.path = Some(layer_path.clone());
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path.clone(),
stroke: Stroke::new(global_tool_data.primary_color, tool_options.line_weight),
});
tool_data.weight = tool_options.line_weight;
responses.push_back(
Operation::AddLine {
path: tool_data.path.clone().unwrap(),
insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(),
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
}
.into(),
);
Drawing
}
(Drawing, Redraw { center, snap_angle, lock_angle }) => {
@@ -249,9 +252,10 @@ fn generate_transform(tool_data: &mut LineToolData, lock_angle: bool, snap_angle
line_length *= 2.;
}
Operation::SetLayerTransformInViewport {
path: tool_data.path.clone().unwrap(),
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start).to_cols_array(),
GraphOperationMessage::TransformSet {
layer: tool_data.path.clone().unwrap(),
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start),
transform_in: TransformIn::Viewport,
}
.into()
}
@@ -50,8 +50,8 @@ impl ToolMetadata for NavigateTool {
impl PropertyHolder for NavigateTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NavigateTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NavigateTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
@@ -105,7 +105,7 @@ impl Fsm for NavigateToolFsmState {
self,
message: ToolMessage,
tool_data: &mut Self::ToolData,
(_document, _document_id, _global_tool_data, input, _render_data): ToolActionHandlerData,
ToolActionHandlerData { input, .. }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
messages: &mut VecDeque<Message>,
) -> Self {
@@ -37,8 +37,8 @@ pub enum NodeGraphFrameToolMessage {
impl PropertyHolder for NodeGraphFrameTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NodeGraphFrameTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NodeGraphFrameTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
@@ -100,7 +100,7 @@ impl Fsm for NodeGraphToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -4,13 +4,14 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, ShapeEditor};
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, ShapeState};
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, HintData, HintGroup, HintInfo, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use document_legacy::intersection::Quad;
use document_legacy::LayerId;
use graphene_std::vector::consts::ManipulatorType;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::{ManipulatorPointId, SelectedType};
use glam::DVec2;
use serde::{Deserialize, Serialize};
@@ -62,8 +63,8 @@ impl ToolMetadata for PathTool {
impl PropertyHolder for PathTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PathTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
@@ -106,14 +107,28 @@ enum PathToolFsmState {
#[derive(Default)]
struct PathToolData {
shape_editor: ShapeEditor,
overlay_renderer: OverlayRenderer,
snap_manager: SnapManager,
drag_start_pos: DVec2,
previous_mouse_position: DVec2,
alt_debounce: bool,
opposing_handle_lengths: Option<HashMap<Vec<LayerId>, HashMap<u64, f64>>>,
opposing_handle_lengths: Option<HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>>>,
}
impl PathToolData {
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, shape_editor: &mut ShapeState, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) {
// Set the previously selected layers to invisible
for layer_path in document.all_layers() {
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
}
// Render the new overlays
for layer_path in shape_editor.selected_shape_state.keys() {
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
}
self.opposing_handle_lengths = None;
}
}
impl Fsm for PathToolFsmState {
@@ -124,27 +139,25 @@ impl Fsm for PathToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData {
document,
input,
render_data,
shape_editor,
shape_overlay,
..
}: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
if let ToolMessage::Path(event) = event {
match (self, event) {
(_, PathToolMessage::SelectionChanged) => {
// Set the previously selected layers to invisible
for layer_path in document.all_layers() {
tool_data.overlay_renderer.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
}
// Set the newly targeted layers to visible
let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect();
tool_data.shape_editor.set_selected_layers(layer_paths);
// Render the new overlays
for layer_path in tool_data.shape_editor.selected_layers() {
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
shape_editor.set_selected_layers(layer_paths);
tool_data.opposing_handle_lengths = None;
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
// This can happen in any state (which is why we return self)
self
}
@@ -152,7 +165,7 @@ impl Fsm for PathToolFsmState {
// When the document has moved / needs to be redraw, re-render the overlays
// TODO the overlay system should probably receive this message instead of the tool
for layer_path in document.selected_visible_layers() {
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
}
self
@@ -162,39 +175,39 @@ impl Fsm for PathToolFsmState {
let shift_pressed = input.keyboard.get(add_to_selection as usize);
tool_data.opposing_handle_lengths = None;
let selected_layers = shape_editor.selected_layers().cloned().collect();
// Select the first point within the threshold (in pixels)
if let Some(mut selected_points) = tool_data
.shape_editor
.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed, responses)
{
if let Some(mut selected_points) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed) {
responses.push_back(DocumentMessage::StartTransaction.into());
let ignore_document = tool_data.shape_editor.selected_layers().clone();
tool_data
.snap_manager
.start_snap(document, input, document.bounding_boxes(Some(&ignore_document), None, render_data), true, true);
.start_snap(document, input, document.bounding_boxes(Some(&selected_layers), None, render_data), true, true);
// Do not snap against handles when anchor is selected
let mut extension = Vec::new();
let mut additional_selected_points = Vec::new();
for point in selected_points.points.iter() {
if point.manipulator_type == ManipulatorType::Anchor {
extension.push(ManipulatorPointInfo {
manipulator_type: ManipulatorType::InHandle,
..*point
if point.point_id.manipulator_type == SelectedType::Anchor {
additional_selected_points.push(ManipulatorPointInfo {
shape_layer_path: point.shape_layer_path,
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::InHandle),
});
extension.push(ManipulatorPointInfo {
manipulator_type: ManipulatorType::OutHandle,
..*point
additional_selected_points.push(ManipulatorPointInfo {
shape_layer_path: point.shape_layer_path,
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::OutHandle),
});
}
}
selected_points.points.extend(extension);
selected_points.points.extend(additional_selected_points);
let include_handles = tool_data.shape_editor.selected_layers_ref();
let include_handles: Vec<_> = selected_layers.iter().map(|x| x.as_slice()).collect();
tool_data.snap_manager.add_all_document_handles(document, input, &include_handles, &[], &selected_points.points);
tool_data.drag_start_pos = input.mouse.position;
tool_data.previous_mouse_position = input.mouse.position - selected_points.offset;
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
PathToolFsmState::Dragging
}
// We didn't find a point nearby, so consider selecting the nearest shape instead
@@ -219,7 +232,7 @@ impl Fsm for PathToolFsmState {
tool_data.drag_start_pos = input.mouse.position;
tool_data.previous_mouse_position = input.mouse.position;
// Selects all the anchor points when clicking in a filled area of shape. If two shapes intersect we pick the topmost layer.
tool_data.shape_editor.select_all_anchors(responses, top_most_intersection);
shape_editor.select_all_anchors(&document.document_legacy, &top_most_intersection);
return PathToolFsmState::Dragging;
}
} else {
@@ -241,51 +254,47 @@ impl Fsm for PathToolFsmState {
) => {
// Determine when alt state changes
let alt_pressed = input.keyboard.get(alt_mirror_angle as usize);
if alt_pressed != tool_data.alt_debounce {
tool_data.alt_debounce = alt_pressed;
// Only on alt down
if alt_pressed {
tool_data.opposing_handle_lengths = None;
tool_data.shape_editor.toggle_handle_mirroring_on_selected(true, responses);
}
// Only on alt down
if alt_pressed && !tool_data.alt_debounce {
tool_data.opposing_handle_lengths = None;
shape_editor.toggle_handle_mirroring_on_selected(responses);
}
tool_data.alt_debounce = alt_pressed;
// Determine when shift state changes
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
if shift_pressed {
if tool_data.opposing_handle_lengths.is_none() {
tool_data.opposing_handle_lengths = Some(tool_data.shape_editor.opposing_handle_lengths(&document.document_legacy));
tool_data.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(&document.document_legacy));
}
} else {
if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths {
tool_data.shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
tool_data.opposing_handle_lengths = None;
}
}
// Move the selected points by the mouse position
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
tool_data
.shape_editor
.move_selected_points(snapped_position - tool_data.previous_mouse_position, shift_pressed, responses);
shape_editor.move_selected_points(&document.document_legacy, snapped_position - tool_data.previous_mouse_position, shift_pressed, responses);
tool_data.previous_mouse_position = snapped_position;
PathToolFsmState::Dragging
}
// Mouse up
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
let selected_points = tool_data.shape_editor.selected_points(&document.document_legacy);
let nearest_point = tool_data.shape_editor.find_nearest_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD);
let nearest_point = shape_editor
.find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD)
.map(|(_, nearest_point)| nearest_point)
.clone();
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
for point in selected_points {
if nearest_point == Some(point) {
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
tool_data
.shape_editor
.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false, responses);
}
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
if clicked_selected {
shape_editor.deselect_all();
shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false);
}
}
@@ -296,18 +305,18 @@ impl Fsm for PathToolFsmState {
(_, PathToolMessage::Delete) => {
// Delete the selected points and clean up overlays
responses.push_back(DocumentMessage::StartTransaction.into());
tool_data.shape_editor.delete_selected_points(responses);
shape_editor.delete_selected_points(responses);
responses.push_back(PathToolMessage::SelectionChanged.into());
for layer_path in document.all_layers() {
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::InsertPoint) => {
// First we try and flip the sharpness (if they have clicked on an anchor)
if !tool_data.shape_editor.flip_sharp(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses) {
if !shape_editor.flip_sharp(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses) {
// If not, then we try and split the path that may have been clicked upon
tool_data.shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses);
shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses);
}
self
@@ -315,7 +324,7 @@ impl Fsm for PathToolFsmState {
(_, PathToolMessage::Abort) => {
// TODO Tell overlay manager to remove the overlays
for layer_path in document.all_layers() {
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
PathToolFsmState::Ready
}
+399 -405
View File
@@ -3,17 +3,20 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::layers::style;
use document_legacy::LayerId;
use document_legacy::Operation;
use graphene_std::vector::consts::ManipulatorType;
use graphene_std::vector::manipulator_group::ManipulatorGroup;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::style::Stroke;
use graphene_core::vector::{ManipulatorPointId, SelectedType};
use graphene_core::Color;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
@@ -98,8 +101,8 @@ impl PropertyHolder for PenTool {
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PenTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message {
match action {
PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
@@ -139,18 +142,342 @@ impl ToolTransition for PenTool {
}
}
}
struct ModifierState {
snap_angle: bool,
lock_angle: bool,
break_handle: bool,
}
#[derive(Clone, Debug, Default)]
struct PenToolData {
weight: f64,
path: Option<Vec<LayerId>>,
overlay_renderer: OverlayRenderer,
subpath_index: usize,
snap_manager: SnapManager,
should_mirror: bool,
// Indicates that curve extension is occurring from the first point, rather than (more commonly) the last point
from_start: bool,
angle: f64,
}
impl PenToolData {
fn extend_subpath(&mut self, layer: &[LayerId], subpath_index: usize, from_start: bool, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
self.path = Some(layer.to_vec());
self.from_start = from_start;
self.subpath_index = subpath_index;
// Stop the handles on the first point from mirroring
let Some(vector_data) = document.document_legacy.layer(layer).ok().and_then(|layer| layer.as_vector_data()) else { return };
let manipulator_groups = vector_data.subpaths[subpath_index].manipulator_groups();
let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else { return };
responses.add(GraphOperationMessage::Vector {
layer: layer.to_vec(),
modification: VectorDataModification::SetManipulatorHandleMirroring {
id: last_handle.id,
mirror_angle: false,
},
});
}
fn create_new_path(&mut self, document: &DocumentMessageHandler, line_weight: f64, color: Color, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
// Deselect layers because we are now creating a new layer
responses.add(DocumentMessage::DeselectAllLayers);
let layer_path = document.get_path_for_new_layer();
// Get the position and set properties
let transform = document.document_legacy.multiply_transforms(&layer_path[..layer_path.len() - 1]).unwrap_or_default();
let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position);
let start_position = transform.inverse().transform_point2(snapped_position);
self.weight = line_weight;
// Create the initial shape with a `bez_path` (only contains a moveto initially)
let subpath = bezier_rs::Subpath::new(vec![bezier_rs::ManipulatorGroup::new(start_position, Some(start_position), Some(start_position))], false);
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path.clone(),
stroke: Stroke::new(color, line_weight),
});
self.path = Some(layer_path);
self.from_start = false;
self.subpath_index = 0;
}
/// If you place the anchor on top of the previous anchor then you break the mirror
///
/// TODO: tooltip / user documentation?
fn check_break(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) -> Option<()> {
// Get subpath
let layer_path = self.path.as_ref()?;
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
let subpath = &vector_data.subpaths[self.subpath_index];
// Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().iter();
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let previous_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
// Get correct handle types
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
// Get manipulator points
let last_anchor = last_manipulator_group.anchor;
let previous_anchor = previous_manipulator_group.anchor;
// Break the control
let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
if !on_top {
return None;
}
// Remove the point that has just been placed
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
});
// Move the in handle of the previous anchor to on top of the previous position
let point = ManipulatorPointId::new(previous_manipulator_group.id, outwards_handle);
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor },
});
// Stop the handles on the last point from mirroring
let id = previous_manipulator_group.id;
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
});
// The overlay system cannot detect deleted points so we must just delete all the overlays
for layer_path in document.all_layers() {
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
self.should_mirror = false;
None
}
fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
// Get subpath
let layer_path = self.path.as_ref()?;
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
let subpath = &vector_data.subpaths[self.subpath_index];
// Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().iter();
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
// Get the first manipulator group
let first_manipulator_group = if self.from_start {
subpath.manipulator_groups().last()?
} else {
subpath.manipulator_groups().first()?
};
// Get correct handle types
let inwards_handle = if self.from_start { SelectedType::OutHandle } else { SelectedType::InHandle };
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
// Get manipulator points
let last_anchor = last_manipulator_group.anchor;
let first_anchor = first_manipulator_group.anchor;
let last_in = inwards_handle.get_position(last_manipulator_group)?;
let transformed_distance_between_squared = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(first_anchor));
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
let should_close_path = transformed_distance_between_squared < snap_point_tolerance_squared && previous_manipulator_group.is_some();
if should_close_path {
// Move the in handle of the first point to where the user has placed it
let point = ManipulatorPointId::new(first_manipulator_group.id, inwards_handle);
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorPosition { point, position: last_in },
});
// Stop the handles on the first point from mirroring
let id = first_manipulator_group.id;
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
});
// Remove the point that has just been placed
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
});
// Push a close path node
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetClosed { index: 0, closed: true },
});
responses.push_back(DocumentMessage::CommitTransaction.into());
// Clean up overlays
for layer_path in document.all_layers() {
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
// Clean up tool data
self.path = None;
self.snap_manager.cleanup(responses);
// Return to ready state
return Some(PenToolFsmState::Ready);
}
// Add a new manipulator for the next anchor that we will place
if let Some(out_handle) = outwards_handle.get_position(last_manipulator_group) {
responses.push_back(add_manipulator_group(&self.path, self.from_start, bezier_rs::ManipulatorGroup::new_anchor(out_handle)));
}
Some(PenToolFsmState::PlacingAnchor)
}
fn drag_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
// Get subpath
let layer_path = self.path.as_ref()?;
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
let subpath = &vector_data.subpaths[self.subpath_index];
// Get the last manipulator group
let manipulator_groups = subpath.manipulator_groups();
let last_manipulator_group = if self.from_start { manipulator_groups.first()? } else { manipulator_groups.last()? };
// Get correct handle types
let inwards_handle = if self.from_start { SelectedType::OutHandle } else { SelectedType::InHandle };
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
// Get manipulator points
let last_anchor = last_manipulator_group.anchor;
let mouse = self.snap_manager.snap_position(responses, document, mouse);
let pos = transform.inverse().transform_point2(mouse);
let pos = compute_snapped_angle(&mut self.angle, modifiers.lock_angle, modifiers.snap_angle, pos, last_anchor);
// Update points on current segment (to show preview of new handle)
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
});
let should_mirror = !modifiers.break_handle && self.should_mirror;
// Mirror handle of last segment
if should_mirror {
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
let pos = last_anchor - (pos - last_anchor);
let point = ManipulatorPointId::new(last_manipulator_group.id, inwards_handle);
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
});
}
// Update the mirror status of the currently modifying point
let id = last_manipulator_group.id;
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: should_mirror },
});
Some(PenToolFsmState::DraggingHandle)
}
fn place_anchor(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
// Get subpath
let layer_path = self.path.as_ref()?;
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
let subpath = &vector_data.subpaths[self.subpath_index];
// Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().iter();
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
// Get the first manipulator group
let manipulator_groups = subpath.manipulator_groups();
let first_manipulator_group = if self.from_start { manipulator_groups.last()? } else { manipulator_groups.first()? };
// Get manipulator points
let first_anchor = first_manipulator_group.anchor;
let mouse = self.snap_manager.snap_position(responses, document, mouse);
let mut pos = transform.inverse().transform_point2(mouse);
// Snap to the first point (to show close path)
let show_close_path = mouse.distance_squared(transform.transform_point2(first_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
if show_close_path {
pos = first_anchor;
}
if let Some(relative_previous_anchor) = previous_manipulator_group.map(|group| group.anchor) {
// Snap to the previously placed point (to show break control)
if mouse.distance_squared(transform.transform_point2(relative_previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
pos = relative_previous_anchor;
} else {
pos = compute_snapped_angle(&mut self.angle, modifiers.lock_angle, modifiers.snap_angle, pos, relative_previous_anchor);
}
}
for manipulator_type in [SelectedType::Anchor, SelectedType::InHandle, SelectedType::OutHandle] {
let point = ManipulatorPointId::new(last_manipulator_group.id, manipulator_type);
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
});
}
Some(PenToolFsmState::PlacingAnchor)
}
fn finish_transaction(&mut self, fsm: PenToolFsmState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Option<DocumentMessage> {
// Get subpath
let layer_path = self.path.as_ref()?;
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
let subpath = &vector_data.subpaths[self.subpath_index];
// Abort if only one manipulator group has been placed
if fsm == PenToolFsmState::PlacingAnchor && subpath.len() < 3 {
return None;
}
// Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().iter();
let mut last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
// Get correct handle types
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
// If placing anchor we should abort if there are less than three manipulators (as the last one gets deleted)
let Some(previous_manipulator_group) = previous_manipulator_group else {
return Some(DocumentMessage::AbortTransaction);
};
// Clean up if there are two or more manipulators
// Remove the unplaced anchor if in anchor placing mode
if fsm == PenToolFsmState::PlacingAnchor {
let layer_path = layer_path.clone();
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
});
last_manipulator_group = previous_manipulator_group;
}
// Remove the out handle
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
let position = last_manipulator_group.anchor;
responses.add(GraphOperationMessage::Vector {
layer: layer_path.to_vec(),
modification: VectorDataModification::SetManipulatorPosition { point, position },
});
return Some(DocumentMessage::CommitTransaction);
}
}
impl Fsm for PenToolFsmState {
type ToolData = PenToolData;
@@ -160,7 +487,15 @@ impl Fsm for PenToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData {
document,
global_tool_data,
input,
render_data,
shape_editor,
shape_overlay,
..
}: &mut ToolActionHandlerData,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -172,19 +507,19 @@ impl Fsm for PenToolFsmState {
// When the document has moved / needs to be redraw, re-render the overlays
// TODO the overlay system should probably receive this message instead of the tool
for layer_path in document.selected_visible_layers() {
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
}
self
}
(_, PenToolMessage::SelectionChanged) => {
// Set the previously selected layers to invisible
for layer_path in document.all_layers() {
tool_data.overlay_renderer.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
}
// Redraw the overlays of the newly selected layers
for layer_path in document.selected_visible_layers() {
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
}
self
}
@@ -199,383 +534,49 @@ impl Fsm for PenToolFsmState {
tool_data.should_mirror = false;
// Perform extension of an existing path
if let Some((layer, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
tool_data.path = Some(layer.to_vec());
tool_data.from_start = from_start;
// Stop the handles on the first point from mirroring
let mut stop_mirror = || {
let subpath = document.document_legacy.layer(layer).ok().and_then(|layer| layer.as_subpath())?;
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
let (&id, _) = if from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let op = Operation::SetManipulatorHandleMirroring {
layer_path: layer.to_vec(),
id,
mirror_angle: false,
};
responses.push_back(op.into());
Some(())
};
stop_mirror();
return PenToolFsmState::DraggingHandle;
}
// Deselect layers because we are now creating a new layer
responses.push_back(DocumentMessage::DeselectAllLayers.into());
// Create a new layer
tool_data.path = Some(document.get_path_for_new_layer());
tool_data.from_start = false;
// Get the position and set properties
let transform = tool_data
.path
.as_ref()
.and_then(|path| document.document_legacy.multiply_transforms(&path[..path.len() - 1]).ok())
.unwrap_or_default();
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let start_position = transform.inverse().transform_point2(snapped_position);
tool_data.weight = tool_options.line_weight;
// Create the initial shape with a `bez_path` (only contains a moveto initially)
if let Some(layer_path) = &tool_data.path {
responses.push_back(
Operation::AddShape {
path: layer_path.clone(),
transform: DAffine2::IDENTITY.to_cols_array(),
insert_index: -1,
subpath: Default::default(),
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
}
.into(),
);
responses.push_back(add_manipulator_group(
&tool_data.path,
tool_data.from_start,
ManipulatorGroup::new_with_handles(start_position, Some(start_position), Some(start_position)),
));
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
tool_data.extend_subpath(layer, subpath_index, from_start, document, responses);
} else {
tool_data.create_new_path(document, tool_options.line_weight, global_tool_data.primary_color, input, responses);
}
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
PenToolFsmState::DraggingHandle
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
// If you place the anchor on top of the previous anchor then you break the mirror
let mut check_break = || {
// Get subpath
let layer_path = tool_data.path.as_ref()?;
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
// Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
// Get correct handle types
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
// Get manipulator points
let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
if let Some((previous_id, previous_anchor)) = previous
.as_ref()
.and_then(|(&id, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref().map(|x| (id, x)))
{
// Break the control
if transform.transform_point2(last_anchor.position).distance_squared(transform.transform_point2(previous_anchor.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
// Remove the point that has just been placed
let op = Operation::RemoveManipulatorGroup {
layer_path: layer_path.clone(),
id: last_id,
};
responses.push_back(op.into());
// Move the in handle of the previous anchor to on top of the previous position
let op = Operation::MoveManipulatorPoint {
layer_path: layer_path.clone(),
id: previous_id,
manipulator_type: outwards_handle,
position: previous_anchor.position.into(),
};
responses.push_back(op.into());
// Stop the handles on the last point from mirroring
let op = Operation::SetManipulatorHandleMirroring {
layer_path: layer_path.clone(),
id: previous_id,
mirror_angle: false,
};
responses.push_back(op.into());
// The overlay system cannot detect deleted points so we must just delete all the overlays
for layer_path in document.all_layers() {
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
tool_data.should_mirror = false;
}
}
None
};
check_break().unwrap_or(PenToolFsmState::DraggingHandle)
tool_data.check_break(document, transform, shape_overlay, responses);
PenToolFsmState::DraggingHandle
}
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
let mut process = || {
// Get subpath
let layer_path = tool_data.path.as_ref()?;
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
// Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
// Get the first manipulator group
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
let (&first_id, first_manipulator_group) = if tool_data.from_start { manipulator_groups.next_back()? } else { manipulator_groups.next()? };
// Get correct handle types
let inwards_handle = if tool_data.from_start { ManipulatorType::OutHandle } else { ManipulatorType::InHandle };
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
// Get manipulator points
let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
let first_anchor = first_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
let last_in = last_manipulator_group.points[inwards_handle].as_ref()?;
// Close path
let transformed_distance_between_squared = transform.transform_point2(last_anchor.position).distance_squared(transform.transform_point2(first_anchor.position));
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
if transformed_distance_between_squared < snap_point_tolerance_squared && previous.is_some() {
// Move the in handle of the first point to where the user has placed it
let op = Operation::MoveManipulatorPoint {
layer_path: layer_path.clone(),
id: first_id,
manipulator_type: inwards_handle,
position: last_in.position.into(),
};
responses.push_back(op.into());
// Stop the handles on the first point from mirroring
let op = Operation::SetManipulatorHandleMirroring {
layer_path: layer_path.clone(),
id: first_id,
mirror_angle: false,
};
responses.push_back(op.into());
// Remove the point that has just been placed
let op = Operation::RemoveManipulatorGroup {
layer_path: layer_path.clone(),
id: last_id,
};
responses.push_back(op.into());
// Push a close path node
responses.push_back(add_manipulator_group(&tool_data.path, false, ManipulatorGroup::closed()));
responses.push_back(DocumentMessage::CommitTransaction.into());
// Clean up overlays
for layer_path in document.all_layers() {
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
// Clean up tool data
tool_data.path = None;
tool_data.snap_manager.cleanup(responses);
// Return the new tool state, wrapped in `Some()` because this closure returns an Option used by the `?` operation various times above
return Some(PenToolFsmState::Ready);
}
// Add a new manipulator for the next anchor that we will place
if let Some(out_handle) = &last_manipulator_group.points[outwards_handle] {
responses.push_back(add_manipulator_group(&tool_data.path, tool_data.from_start, ManipulatorGroup::new_with_anchor(out_handle.position)));
}
// Returning `None` means the `unwrap_or` clause below returns the state `PlacingAnchor`
None
};
tool_data.should_mirror = true;
process().unwrap_or(PenToolFsmState::PlacingAnchor)
tool_data.finish_placing_handle(document, transform, shape_overlay, responses).unwrap_or(PenToolFsmState::PlacingAnchor)
}
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
let mut process = || {
// Get subpath
let layer_path = tool_data.path.as_ref()?;
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
// Get the last manipulator group
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
// Get correct handle types
let inwards_handle = if tool_data.from_start { ManipulatorType::OutHandle } else { ManipulatorType::InHandle };
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
// Get manipulator points
let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(mouse);
let snap_angle = input.keyboard.get(snap_angle as usize);
let lock_angle = input.keyboard.get(lock_angle as usize);
let pos = compute_snapped_angle(&mut tool_data.angle, lock_angle, snap_angle, pos, last_anchor.position);
// Update points on current segment (to show preview of new handle)
let msg = Operation::MoveManipulatorPoint {
layer_path: layer_path.clone(),
id: last_id,
manipulator_type: outwards_handle,
position: pos.into(),
};
responses.push_back(msg.into());
let should_mirror = !input.keyboard.get(break_handle as usize) && tool_data.should_mirror;
// Mirror handle of last segment
if should_mirror {
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
let pos = last_anchor.position - (pos - last_anchor.position);
let msg = Operation::MoveManipulatorPoint {
layer_path: layer_path.clone(),
id: last_id,
manipulator_type: inwards_handle,
position: pos.into(),
};
responses.push_back(msg.into());
}
// Update the mirror status of the currently modifying point
let op = Operation::SetManipulatorHandleMirroring {
layer_path: layer_path.clone(),
id: last_id,
mirror_angle: should_mirror,
};
responses.push_back(op.into());
Some(())
let modifiers = ModifierState {
snap_angle: input.keyboard.key(snap_angle),
lock_angle: input.keyboard.key(lock_angle),
break_handle: input.keyboard.key(break_handle),
};
if process().is_none() {
PenToolFsmState::Ready
} else {
self
}
tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready)
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, lock_angle, .. }) => {
let mut process = || {
// Get subpath
let data = tool_data.clone();
let layer_path = data.path.as_ref()?;
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
// Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
let (&last_id, _last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
// Get the first manipulator group
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
let (_first_id, first_manipulator_group) = if tool_data.from_start { manipulator_groups.next_back()? } else { manipulator_groups.next()? };
// Get manipulator points
let first_anchor = first_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let mut pos = transform.inverse().transform_point2(mouse);
// Snap to the first point (to show close path)
if mouse.distance_squared(transform.transform_point2(first_anchor.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
pos = first_anchor.position;
}
if let Some(relative) = previous.as_ref().and_then(|(_, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref()) {
// Snap to the previously placed point (to show break control)
if mouse.distance_squared(transform.transform_point2(relative.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
pos = relative.position;
} else {
let snap_angle = input.keyboard.get(snap_angle as usize);
let lock_angle = input.keyboard.get(lock_angle as usize);
pos = compute_snapped_angle(&mut tool_data.angle, lock_angle, snap_angle, pos, relative.position);
}
}
for manipulator_type in [ManipulatorType::Anchor, ManipulatorType::InHandle, ManipulatorType::OutHandle] {
let msg = Operation::MoveManipulatorPoint {
layer_path: layer_path.clone(),
id: last_id,
manipulator_type,
position: pos.into(),
};
responses.push_back(msg.into());
}
Some(())
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
let modifiers = ModifierState {
snap_angle: input.keyboard.key(snap_angle),
lock_angle: input.keyboard.key(lock_angle),
break_handle: input.keyboard.key(break_handle),
};
if process().is_none() {
PenToolFsmState::Ready
} else {
self
}
tool_data
.place_anchor(document, transform, input.mouse.position, modifiers, responses)
.unwrap_or(PenToolFsmState::Ready)
}
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
// Abort or commit the transaction to the undo history
let mut commit = || {
// Get subpath
let layer_path = tool_data.path.as_ref()?;
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
// If placing anchor we should abort if there are less than three manipulators (as the last one gets deleted)
if self == PenToolFsmState::PlacingAnchor && subpath.manipulator_groups().len() < 3 {
return None;
}
// Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
let (&(mut last_id), mut last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
// Get correct handle types
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
// Clean up if there are two or more manipulators
if let Some((&previous_id, previous_manipulator_group)) = previous {
// Remove the unplaced anchor if in anchor placing mode
if self == PenToolFsmState::PlacingAnchor {
let layer_path = layer_path.clone();
let op = Operation::RemoveManipulatorGroup { layer_path, id: last_id };
responses.push_back(op.into());
last_id = previous_id;
last_manipulator_group = previous_manipulator_group;
}
// Remove the out handle
let op = Operation::MoveManipulatorPoint {
layer_path: layer_path.clone(),
id: last_id,
manipulator_type: outwards_handle,
position: last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?.position.into(),
};
responses.push_back(op.into());
responses.push_back(DocumentMessage::CommitTransaction.into());
return Some(());
}
// Abort if only one manipulator group has been placed
None
};
if commit().is_none() {
responses.push_back(DocumentMessage::AbortTransaction.into());
}
let message = tool_data.finish_transaction(self, document, responses).unwrap_or(DocumentMessage::AbortTransaction);
responses.add(message);
// Clean up overlays
for layer_path in document.all_layers() {
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
tool_data.path = None;
tool_data.snap_manager.cleanup(responses);
@@ -585,7 +586,7 @@ impl Fsm for PenToolFsmState {
(_, PenToolMessage::Abort) => {
// Clean up overlays
for layer_path in document.all_layers() {
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
self
}
@@ -640,51 +641,44 @@ fn compute_snapped_angle(cached_angle: &mut f64, lock_angle: bool, snap_angle: b
}
}
/// Pushes a [ManipulatorGroup] to the current layer via an [Operation].
fn add_manipulator_group(layer_path: &Option<Vec<LayerId>>, from_start: bool, manipulator_group: ManipulatorGroup) -> Message {
match (layer_path, from_start) {
(Some(layer_path), true) => Operation::PushFrontManipulatorGroup {
layer_path: layer_path.clone(),
manipulator_group,
}
.into(),
(Some(layer_path), false) => Operation::PushManipulatorGroup {
layer_path: layer_path.clone(),
manipulator_group,
}
.into(),
(None, _) => Message::NoOp,
}
/// Pushes a [ManipulatorGroup] to the current layer via a [GraphOperationMessage].
fn add_manipulator_group(layer_path: &Option<Vec<LayerId>>, from_start: bool, manipulator_group: bezier_rs::ManipulatorGroup<ManipulatorGroupId>) -> Message {
let Some(layer) = layer_path.clone() else {
return Message::NoOp;
};
let modification = if from_start {
VectorDataModification::AddStartManipulatorGroup { subpath_index: 0, manipulator_group }
} else {
VectorDataModification::AddEndManipulatorGroup { subpath_index: 0, manipulator_group }
};
GraphOperationMessage::Vector { layer, modification }.into()
}
/// Determines if a path should be extended. Returns the path and if it is extending from the start, if applicable.
fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(&[LayerId], bool)> {
fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(&[LayerId], usize, bool)> {
let mut best = None;
let mut best_distance_squared = tolerance * tolerance;
for layer_path in document.selected_layers() {
(|| {
let viewspace = document.document_legacy.generate_transform_relative_to_viewport(layer_path).ok()?;
let Ok(viewspace) = document.document_legacy.generate_transform_relative_to_viewport(layer_path) else { continue };
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
let (_first_id, first) = subpath.manipulator_groups().enumerate().next()?;
let (_last_id, last) = subpath.manipulator_groups().enumerate().next_back()?;
if !last.is_close() {
for (manipulator_group, from_start) in [(first, true), (last, false)] {
if let Some(point) = &manipulator_group.points[ManipulatorType::Anchor] {
let distance_squared = viewspace.transform_point2(point.position).distance_squared(pos);
if distance_squared < best_distance_squared {
best = Some((layer_path, from_start));
best_distance_squared = distance_squared;
}
}
}
let Some(vector_data) = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data()) else { continue };
for (subpath_index, subpath) in vector_data.subpaths.iter().enumerate() {
if subpath.closed() {
continue;
}
None::<()>
})();
for (manipulator_group, from_start) in [(subpath.manipulator_groups().first(), true), (subpath.manipulator_groups().last(), false)] {
let Some(manipulator_group) = manipulator_group else { break };
let distance_squared = viewspace.transform_point2(manipulator_group.anchor).distance_squared(pos);
if distance_squared < best_distance_squared {
best = Some((layer_path, subpath_index, from_start));
best_distance_squared = distance_squared;
}
}
}
}
best
@@ -2,14 +2,13 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::layers::style;
use document_legacy::Operation;
use glam::DAffine2;
use glam::DVec2;
use graphene_core::vector::style::Fill;
use serde::{Deserialize, Serialize};
#[derive(Default)]
@@ -37,8 +36,8 @@ pub enum RectangleToolMessage {
impl PropertyHolder for RectangleTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for RectangleTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for RectangleTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
@@ -100,7 +99,13 @@ impl Fsm for RectangleToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData {
document,
global_tool_data,
input,
render_data,
..
}: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -113,19 +118,17 @@ impl Fsm for RectangleToolFsmState {
match (self, event) {
(Ready, DragStart) => {
shape_data.start(responses, document, input, render_data);
responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into());
responses.push_back(
Operation::AddRect {
path: shape_data.path.clone().unwrap(),
insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(),
style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
}
.into(),
);
let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
let layer_path = document.get_path_for_new_layer();
responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(layer_path.clone());
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
responses.add(GraphOperationMessage::FillSet {
layer: layer_path,
fill: Fill::solid(global_tool_data.primary_color),
});
Drawing
}
@@ -19,7 +19,6 @@ use crate::messages::tool::common_functionality::transformation_cage::*;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::boolean_ops::BooleanOperation;
use document_legacy::document::Document;
use document_legacy::intersection::Quad;
use document_legacy::layers::layer_info::{Layer, LayerDataType};
@@ -219,24 +218,24 @@ impl PropertyHolder for SelectTool {
})),
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(),
IconButton::new("BooleanUnion", 24)
.tooltip("Boolean Union")
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Union).into())
.tooltip("Boolean Union (coming soon)")
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
.widget_holder(),
IconButton::new("BooleanSubtractFront", 24)
.tooltip("Boolean Subtract Front")
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractFront).into())
.tooltip("Boolean Subtract Front (coming soon)")
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
.widget_holder(),
IconButton::new("BooleanSubtractBack", 24)
.tooltip("Boolean Subtract Back")
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractBack).into())
.tooltip("Boolean Subtract Back (coming soon)")
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
.widget_holder(),
IconButton::new("BooleanIntersect", 24)
.tooltip("Boolean Intersect")
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Intersection).into())
.tooltip("Boolean Intersect (coming soon)")
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
.widget_holder(),
IconButton::new("BooleanDifference", 24)
.tooltip("Boolean Difference")
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Difference).into())
.tooltip("Boolean Difference (coming soon)")
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
.widget_holder(),
WidgetHolder::related_separator(),
PopoverButton::new("Boolean", "Coming soon").widget_holder(),
@@ -245,8 +244,8 @@ impl PropertyHolder for SelectTool {
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SelectTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message {
self.tool_data.nested_selection_behavior = nested_selection_behavior;
responses.push_back(ToolMessage::UpdateHints.into());
@@ -345,9 +344,10 @@ impl SelectToolData {
for layer_path in Document::shallowest_unique_layers(self.layers_dragging.iter_mut()) {
// Moves the original back to its starting position.
responses.push_front(
Operation::TransformLayerInViewport {
path: layer_path.clone(),
transform: DAffine2::from_translation(self.drag_start - self.drag_current).to_cols_array(),
GraphOperationMessage::TransformChange {
layer: layer_path.clone(),
transform: DAffine2::from_translation(self.drag_start - self.drag_current),
transform_in: TransformIn::Viewport,
}
.into(),
);
@@ -401,9 +401,10 @@ impl SelectToolData {
// Move the original to under the mouse
for layer_path in Document::shallowest_unique_layers(originals.iter()) {
responses.push_front(
Operation::TransformLayerInViewport {
path: layer_path.clone(),
transform: DAffine2::from_translation(self.drag_current - self.drag_start).to_cols_array(),
GraphOperationMessage::TransformChange {
layer: layer_path.clone(),
transform: DAffine2::from_translation(self.drag_current - self.drag_start),
transform_in: TransformIn::Viewport,
}
.into(),
);
@@ -429,7 +430,7 @@ impl Fsm for SelectToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -619,9 +620,10 @@ impl Fsm for SelectToolFsmState {
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
for path in Document::shallowest_unique_layers(tool_data.layers_dragging.iter()) {
responses.push_front(
Operation::TransformLayerInViewport {
path: path.to_vec(),
transform: DAffine2::from_translation(mouse_delta + closest_move).to_cols_array(),
GraphOperationMessage::TransformChange {
layer: path.to_vec(),
transform: DAffine2::from_translation(mouse_delta + closest_move),
transform_in: TransformIn::Viewport,
}
.into(),
);
@@ -3,14 +3,13 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::layers::style;
use document_legacy::Operation;
use glam::DAffine2;
use glam::DVec2;
use graphene_core::vector::style::Fill;
use serde::{Deserialize, Serialize};
#[derive(Default)]
@@ -80,8 +79,8 @@ impl PropertyHolder for ShapeTool {
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ShapeTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ShapeTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message {
match action {
ShapeOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
@@ -139,7 +138,13 @@ impl Fsm for ShapeToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData {
document,
global_tool_data,
input,
render_data,
..
}: &mut ToolActionHandlerData,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -153,20 +158,16 @@ impl Fsm for ShapeToolFsmState {
(Ready, DragStart) => {
shape_data.start(responses, document, input, render_data);
responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into());
let layer_path = document.get_path_for_new_layer();
shape_data.path = Some(layer_path.clone());
tool_data.sides = tool_options.vertices;
responses.push_back(
Operation::AddNgon {
path: shape_data.path.clone().unwrap(),
insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(),
sides: tool_data.sides,
style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
}
.into(),
);
let subpath = bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_data.sides as u64, 1.);
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
responses.add(GraphOperationMessage::FillSet {
layer: layer_path,
fill: Fill::solid(global_tool_data.primary_color),
});
Drawing
}
@@ -4,15 +4,15 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::layers::style;
use document_legacy::LayerId;
use document_legacy::Operation;
use document_legacy::{LayerId, Operation};
use graphene_core::vector::style::Stroke;
use glam::{DAffine2, DVec2};
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Default)]
@@ -86,8 +86,8 @@ impl PropertyHolder for SplineTool {
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SplineTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message {
match action {
SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
@@ -146,7 +146,13 @@ impl Fsm for SplineToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData {
document,
global_tool_data,
input,
render_data,
..
}: &mut ToolActionHandlerData,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -173,7 +179,7 @@ impl Fsm for SplineToolFsmState {
tool_data.weight = tool_options.line_weight;
responses.push_back(add_spline(tool_data, global_tool_data, true));
add_spline(tool_data, global_tool_data, true, responses);
Drawing
}
@@ -189,7 +195,7 @@ impl Fsm for SplineToolFsmState {
}
responses.push_back(remove_preview(tool_data));
responses.push_back(add_spline(tool_data, global_tool_data, true));
add_spline(tool_data, global_tool_data, true, responses);
Drawing
}
@@ -199,14 +205,14 @@ impl Fsm for SplineToolFsmState {
tool_data.next_point = pos;
responses.push_back(remove_preview(tool_data));
responses.push_back(add_spline(tool_data, global_tool_data, true));
add_spline(tool_data, global_tool_data, true, responses);
Drawing
}
(Drawing, Confirm) | (Drawing, Abort) => {
if tool_data.points.len() >= 2 {
responses.push_back(remove_preview(tool_data));
responses.push_back(add_spline(tool_data, global_tool_data, false));
add_spline(tool_data, global_tool_data, false, responses);
responses.push_back(DocumentMessage::CommitTransaction.into());
} else {
responses.push_back(DocumentMessage::AbortTransaction.into());
@@ -249,18 +255,18 @@ fn remove_preview(tool_data: &SplineToolData) -> Message {
.into()
}
fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, show_preview: bool) -> Message {
let mut points: Vec<(f64, f64)> = tool_data.points.iter().map(|p| (p.x, p.y)).collect();
fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, show_preview: bool, responses: &mut VecDeque<Message>) {
let mut points = tool_data.points.clone();
if show_preview {
points.push((tool_data.next_point.x, tool_data.next_point.y))
points.push(tool_data.next_point)
}
Operation::AddSpline {
path: tool_data.path.clone().unwrap(),
insert_index: -1,
transform: DAffine2::IDENTITY.to_cols_array(),
points,
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
}
.into()
let subpath = bezier_rs::Subpath::new_cubic_spline(points);
let layer_path = tool_data.path.clone().unwrap();
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path.clone(),
stroke: Stroke::new(global_tool_data.primary_color, tool_data.weight),
});
}
@@ -127,8 +127,8 @@ impl PropertyHolder for TextTool {
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message {
match action {
TextOptionsUpdate::Font { family, style } => {
@@ -266,7 +266,13 @@ impl Fsm for TextToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
ToolActionHandlerData {
document,
global_tool_data,
input,
render_data,
..
}: &mut ToolActionHandlerData,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -300,7 +306,7 @@ impl Fsm for TextToolFsmState {
else if state == TextToolFsmState::Ready {
responses.push_back(DocumentMessage::StartTransaction.into());
let transform = DAffine2::from_translation(input.mouse.position).to_cols_array();
let transform = DAffine2::from_translation(input.mouse.position);
let font_size = tool_options.font_size;
let font_name = tool_options.font_name.clone();
let font_style = tool_options.font_style.clone();
@@ -320,9 +326,10 @@ impl Fsm for TextToolFsmState {
.into(),
);
responses.push_back(
Operation::SetLayerTransformInViewport {
path: tool_data.layer_path.clone(),
GraphOperationMessage::TransformSet {
layer: tool_data.layer_path.clone(),
transform,
transform_in: TransformIn::Viewport,
}
.into(),
);
@@ -2,7 +2,8 @@ use crate::consts::SLOWING_DIVISOR;
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::shape_editor::ShapeEditor;
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::utility_types::{ToolData, ToolType};
use document_legacy::layers::style::RenderData;
@@ -11,7 +12,7 @@ use glam::DVec2;
#[derive(Debug, Clone, Default)]
pub struct TransformLayerMessageHandler {
transform_operation: TransformOperation,
pub transform_operation: TransformOperation,
slow: bool,
snap: bool,
@@ -22,22 +23,25 @@ pub struct TransformLayerMessageHandler {
original_transforms: OriginalTransforms,
pivot: DVec2,
shape_editor: ShapeEditor,
}
impl TransformLayerMessageHandler {
pub fn is_transforming(&self) -> bool {
self.transform_operation != TransformOperation::None
}
pub fn hints(&self, responses: &mut VecDeque<Message>) {
self.transform_operation.hints(self.snap, responses);
}
}
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a RenderData<'a>, &'a ToolData);
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a RenderData<'a>, &'a ToolData, &'a mut ShapeState);
impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformLayerMessageHandler {
#[remain::check]
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, ipp, render_data, tool_data): TransformData) {
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, ipp, render_data, tool_data, shape_editor): TransformData) {
use TransformLayerMessage::*;
// TODO: Transform individual points when using the path tool.
let _using_path_tool = tool_data.active_tool_type == ToolType::Path;
// You may also want the shape editor here? If not, then feel free to remove.
let _shape_editor = &self.shape_editor;
let selected_layers = document.layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then_some(layer_path)).collect::<Vec<_>>();
let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, &selected_layers, responses, &document.document_legacy);
@@ -61,7 +65,8 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.transform_operation = TransformOperation::None;
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
responses.add(ToolMessage::UpdateHints);
responses.add(BroadcastEvent::DocumentIsDirty);
}
BeginGrab => {
if let TransformOperation::Grabbing(_) = self.transform_operation {
@@ -123,7 +128,8 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.transform_operation = TransformOperation::None;
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
responses.add(ToolMessage::UpdateHints);
responses.add(BroadcastEvent::DocumentIsDirty);
}
ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap),
ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap),
@@ -178,7 +184,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
}
SelectionChanged => {
let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect();
self.shape_editor.set_selected_layers(layer_paths);
shape_editor.set_selected_layers(layer_paths);
}
TypeBackspace => self.transform_operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap),
TypeDecimalPoint => self.transform_operation.handle_typed(self.typing.type_decimal_point(), &mut selected, self.snap),
+46 -5
View File
@@ -1,3 +1,5 @@
use super::common_functionality::overlay_renderer::OverlayRenderer;
use super::common_functionality::shape_editor::ShapeState;
use super::tool_messages::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::macros::action_keys;
@@ -15,10 +17,39 @@ use graphene_core::raster::color::Color;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Debug};
pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, u64, &'a DocumentToolData, &'a InputPreprocessorMessageHandler, &'a RenderData<'a>);
pub struct ToolActionHandlerData<'a> {
pub document: &'a DocumentMessageHandler,
pub document_id: u64,
pub global_tool_data: &'a DocumentToolData,
pub input: &'a InputPreprocessorMessageHandler,
pub render_data: &'a RenderData<'a>,
pub shape_overlay: &'a mut OverlayRenderer,
pub shape_editor: &'a mut ShapeState,
}
impl<'a> ToolActionHandlerData<'a> {
pub fn new(
document: &'a DocumentMessageHandler,
document_id: u64,
global_tool_data: &'a DocumentToolData,
input: &'a InputPreprocessorMessageHandler,
render_data: &'a RenderData<'a>,
shape_overlay: &'a mut OverlayRenderer,
shape_editor: &'a mut ShapeState,
) -> Self {
Self {
document,
document_id,
global_tool_data,
input,
render_data,
shape_overlay,
shape_editor,
}
}
}
pub trait ToolCommon: for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> + PropertyHolder + ToolTransition + ToolMetadata {}
impl<T> ToolCommon for T where T: for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> + PropertyHolder + ToolTransition + ToolMetadata {}
pub trait ToolCommon: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionHandlerData<'a>> + PropertyHolder + ToolTransition + ToolMetadata {}
impl<T> ToolCommon for T where T: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionHandlerData<'a>> + PropertyHolder + ToolTransition + ToolMetadata {}
type Tool = dyn ToolCommon + Send + Sync;
@@ -41,7 +72,7 @@ pub trait Fsm {
/// For example, if the tool's FSM is in a `Ready` state and receives a `DragStart` message as its event, it may decide to send some messages,
/// update some internal tool variables, and end by transitioning to a `Drawing` state.
#[must_use]
fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: ToolActionHandlerData, options: &Self::ToolOptions, messages: &mut VecDeque<Message>) -> Self;
fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionHandlerData, options: &Self::ToolOptions, messages: &mut VecDeque<Message>) -> Self;
/// Implementing this trait function lets a specific tool provide a list of hints (user input actions presently available) to draw in the footer bar.
fn update_hints(&self, responses: &mut VecDeque<Message>);
@@ -70,7 +101,7 @@ pub trait Fsm {
&mut self,
message: ToolMessage,
tool_data: &mut Self::ToolData,
transition_data: ToolActionHandlerData,
transition_data: &mut ToolActionHandlerData,
options: &Self::ToolOptions,
messages: &mut VecDeque<Message>,
update_cursor_on_transition: bool,
@@ -539,6 +570,16 @@ impl HintInfo {
}
}
pub fn label(label: impl Into<String>) -> Self {
Self {
key_groups: vec![],
key_groups_mac: None,
mouse: None,
label: label.into(),
plus: false,
}
}
pub fn keys_and_mouse(keys: impl IntoIterator<Item = Key>, mouse_motion: MouseMotion, label: impl Into<String>) -> Self {
let keys: Vec<_> = keys.into_iter().collect();
Self {