Rename vector components to match new terminology (#719)

* Renamed VectorAnchor, VectorShape and VectorControlPoint. Also fixed other naming inconsistencies.

* Renamed messages relating to vector and updated naming in several tools

* Renamed comments + caught a few areas I had missed.

* Caught a few more incorrect names

* Code review pass

* Review changes

* Fixed warning

* Additional review feedback

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Oliver Davies
2022-07-12 17:59:06 -07:00
committed by Keavon Chambers
parent 5d1d93917d
commit 03633bf313
39 changed files with 1125 additions and 1095 deletions

View File

@@ -45,7 +45,7 @@ pub const BOUNDS_SELECT_THRESHOLD: f64 = 10.;
pub const BOUNDS_ROTATE_THRESHOLD: f64 = 20.;
// Path tool
pub const VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE: f64 = 5.;
pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 5.;
pub const SELECTION_THRESHOLD: f64 = 10.;
// Pen tool

View File

@@ -2,7 +2,7 @@ use crate::layout::widgets::*;
use crate::message_prelude::FrontendMessage;
use crate::misc::build_metadata::{commit_info_localized, release_series};
/// A dialog for displaying information on [`BuildMetadata`] viewable via *Help* > *About Graphite* in the menu bar.
/// A dialog for displaying information on [BuildMetadata] viewable via *Help* > *About Graphite* in the menu bar.
pub struct AboutGraphite {
pub localized_commit_date: String,
}

View File

@@ -7,7 +7,7 @@ use crate::message_prelude::*;
use serde::{Deserialize, Serialize};
/// A dialog to allow users to customise their file export.
/// A dialog to allow users to customize their file export.
#[derive(Debug, Clone, Default)]
pub struct Export {
pub file_name: String,

View File

@@ -53,9 +53,9 @@ pub enum DocumentMessage {
layer_path: Vec<LayerId>,
},
DeleteSelectedLayers,
DeleteSelectedVectorPoints,
DeleteSelectedManipulatorPoints,
DeselectAllLayers,
DeselectAllVectorPoints,
DeselectAllManipulatorPoints,
DirtyRenderDocument,
DirtyRenderDocumentInOutlineView,
DocumentHistoryBackward,
@@ -83,7 +83,7 @@ pub enum DocumentMessage {
insert_index: isize,
reverse_index: bool,
},
MoveSelectedVectorPoints {
MoveSelectedManipulatorPoints {
layer_path: Vec<LayerId>,
delta: (f64, f64),
absolute_position: (f64, f64),

View File

@@ -23,7 +23,7 @@ use graphene::layers::folder_layer::FolderLayer;
use graphene::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
use graphene::layers::style::{Fill, RenderData, ViewMode};
use graphene::layers::text_layer::{Font, FontCache};
use graphene::layers::vector::vector_shape::VectorShape;
use graphene::layers::vector::subpath::Subpath;
use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
use glam::{DAffine2, DVec2};
@@ -201,20 +201,20 @@ impl DocumentMessageHandler {
})
}
/// Returns a copy of all the currently selected VectorShapes.
pub fn selected_vector_shapes(&self) -> Vec<VectorShape> {
/// Returns a copy of all the currently selected [Subpath]s.
pub fn selected_subpaths(&self) -> Vec<Subpath> {
self.selected_visible_layers()
.flat_map(|layer| self.graphene_document.layer(layer))
.flat_map(|layer| layer.as_vector_shape_copy())
.collect::<Vec<VectorShape>>()
.flat_map(|layer| layer.as_subpath_copy())
.collect::<Vec<Subpath>>()
}
/// Returns references to all the currently selected VectorShapes.
pub fn selected_vector_shapes_ref(&self) -> Vec<&VectorShape> {
/// Returns references to all the currently selected [Subpath]s.
pub fn selected_subpaths_ref(&self) -> Vec<&Subpath> {
self.selected_visible_layers()
.flat_map(|layer| self.graphene_document.layer(layer))
.flat_map(|layer| layer.as_vector_shape())
.collect::<Vec<&VectorShape>>()
.flat_map(|layer| layer.as_subpath())
.collect::<Vec<&Subpath>>()
}
/// Returns the bounding boxes for all visible layers and artboards, optionally excluding any paths.
@@ -501,7 +501,7 @@ impl DocumentMessageHandler {
/// Calculate the path that new layers should be inserted to.
/// Depends on the selected layers as well as their types (Folder/Non-Folder)
pub fn get_path_for_new_layer(&self) -> Vec<u64> {
// If the selected layers dont actually exist, a new uuid for the
// If the selected layers don't actually exist, a new uuid for the
// root folder will be returned
let mut path = self.graphene_document.shallowest_common_folder(self.selected_layers()).map_or(vec![], |v| v.to_vec());
path.push(generate_uuid());
@@ -1016,11 +1016,11 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
responses.push_front(BroadcastSignal::SelectionChanged.into());
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
}
DeleteSelectedVectorPoints => {
DeleteSelectedManipulatorPoints => {
responses.push_back(StartTransaction.into());
responses.push_front(
DocumentOperation::DeleteSelectedVectorPoints {
DocumentOperation::DeleteSelectedManipulatorPoints {
layer_paths: self.selected_layers_without_children().iter().map(|path| path.to_vec()).collect(),
}
.into(),
@@ -1030,9 +1030,9 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
responses.push_front(SetSelectedLayers { replacement_selected_layers: vec![] }.into());
self.layer_range_selection_reference.clear();
}
DeselectAllVectorPoints => {
DeselectAllManipulatorPoints => {
for layer_path in self.selected_layers_without_children() {
responses.push_back(DocumentOperation::DeselectAllVectorPoints { layer_path: layer_path.to_vec() }.into());
responses.push_back(DocumentOperation::DeselectAllManipulatorPoints { layer_path: layer_path.to_vec() }.into());
}
}
DirtyRenderDocument => {
@@ -1074,12 +1074,7 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
let bbox = match bounds {
ExportBounds::AllArtwork => self.all_layer_bounds(font_cache),
ExportBounds::Selection => self.selected_visible_layers_bounding_box(font_cache),
ExportBounds::Artboard(id) => self
.artboard_message_handler
.artboards_graphene_document
.layer(&[id])
.ok()
.and_then(|layer| layer.aabounding_box(font_cache)),
ExportBounds::Artboard(id) => self.artboard_message_handler.artboards_graphene_document.layer(&[id]).ok().and_then(|layer| layer.aabb(font_cache)),
}
.unwrap_or_default();
let size = bbox[1] - bbox[0];
@@ -1195,10 +1190,10 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
.into(),
);
}
MoveSelectedVectorPoints { layer_path, delta, absolute_position } => {
MoveSelectedManipulatorPoints { layer_path, delta, absolute_position } => {
self.backup(responses);
if let Ok(_layer) = self.graphene_document.layer(&layer_path) {
responses.push_back(DocumentOperation::MoveSelectedVectorPoints { layer_path, delta, absolute_position }.into());
responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta, absolute_position }.into());
}
}
NudgeSelectedLayers { delta_x, delta_y } => {

View File

@@ -226,7 +226,7 @@ impl<'a> Selected<'a> {
.document
.layer(path)
.unwrap()
.aabounding_box_for_transform(multiplied_transform, font_cache)
.aabb_for_transform(multiplied_transform, font_cache)
.unwrap_or([multiplied_transform.translation; 2]);
(bounds[0] + bounds[1]) / 2.

View File

@@ -1,6 +1,6 @@
/// Provides metadata about the build environment.
///
/// This data is viewable in the editor via the [`crate::dialog::AboutGraphite`] dialog.
//! Provides metadata about the build environment.
//!
//! This data is viewable in the editor via the [AboutGraphite](crate::dialog::AboutGraphite) dialog.
pub fn release_series() -> String {
format!("Release Series: {}", env!("GRAPHITE_RELEASE_SERIES"))

View File

@@ -7,7 +7,7 @@ use crate::message_prelude::*;
use graphene::layers::layer_info::{Layer, LayerDataType};
use graphene::layers::style::{self, Stroke};
use graphene::layers::vector::constants::ControlPointType;
use graphene::layers::vector::constants::ManipulatorType;
use graphene::{LayerId, Operation};
use glam::{DAffine2, DVec2};
@@ -246,25 +246,25 @@ impl SnapHandler {
}
}
/// Add the control points (optionally including bézier handles) of the specified shape layer to the snapping points
/// Add the [ManipulatorGroup]s (optionally including handles) of the specified shape layer to the snapping points
///
/// This should be called after start_snap
pub fn add_snap_path(&mut self, document_message_handler: &DocumentMessageHandler, layer: &Layer, path: &[LayerId], include_handles: bool, ignore_points: &[(&[LayerId], u64, ControlPointType)]) {
pub fn add_snap_path(&mut self, document_message_handler: &DocumentMessageHandler, layer: &Layer, path: &[LayerId], include_handles: bool, ignore_points: &[(&[LayerId], u64, ManipulatorType)]) {
if let LayerDataType::Shape(shape_layer) = &layer.data {
let transform = document_message_handler.graphene_document.multiply_transforms(path).unwrap();
let snap_points = shape_layer
.shape
.anchors()
.manipulator_groups()
.enumerate()
.flat_map(|(id, shape)| {
if include_handles {
[
(*id, &shape.points[ControlPointType::Anchor]),
(*id, &shape.points[ControlPointType::InHandle]),
(*id, &shape.points[ControlPointType::OutHandle]),
(*id, &shape.points[ManipulatorType::Anchor]),
(*id, &shape.points[ManipulatorType::InHandle]),
(*id, &shape.points[ManipulatorType::OutHandle]),
]
} else {
[(*id, &shape.points[ControlPointType::Anchor]), (0, &None), (0, &None)]
[(*id, &shape.points[ManipulatorType::Anchor]), (0, &None), (0, &None)]
}
})
.filter_map(|(id, point)| point.as_ref().map(|val| (id, val)))
@@ -281,7 +281,7 @@ impl SnapHandler {
document_message_handler: &DocumentMessageHandler,
include_handles: &[&[LayerId]],
exclude: &[&[LayerId]],
ignore_points: &[(&[LayerId], u64, ControlPointType)],
ignore_points: &[(&[LayerId], u64, ManipulatorType)],
) {
for path in document_message_handler.all_layers() {
if !exclude.contains(&path) {

View File

@@ -1,4 +1,4 @@
use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE, VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE};
use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_TOLERANCE};
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion};
@@ -141,7 +141,7 @@ impl Default for GradientToolFsmState {
/// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space)
fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, font_cache: &FontCache) -> DAffine2 {
let bounds = layer.aabounding_box_for_transform(DAffine2::IDENTITY, font_cache).unwrap();
let bounds = layer.aabb_for_transform(DAffine2::IDENTITY, font_cache).unwrap();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let multiplied = document.graphene_document.multiply_transforms(path).unwrap();
@@ -163,7 +163,7 @@ impl GradientOverlay {
fn generate_overlay_handle(translation: DVec2, responses: &mut VecDeque<Message>, selected: bool) -> Vec<LayerId> {
let path = vec![generate_uuid()];
let size = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
let size = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
let fill = if selected { Fill::solid(COLOR_ACCENT) } else { Fill::solid(Color::WHITE) };
@@ -359,7 +359,7 @@ impl Fsm for GradientToolFsmState {
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
let mouse = input.mouse.position;
let tolerance = VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE.powi(2);
let tolerance = MANIPULATOR_GROUP_MARKER_SIZE.powi(2);
let mut dragging = false;
for overlay in &tool_data.gradient_overlays {

View File

@@ -10,7 +10,7 @@ use crate::viewport_tools::vector_editor::overlay_renderer::OverlayRenderer;
use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor;
use graphene::intersection::Quad;
use graphene::layers::vector::constants::ControlPointType;
use graphene::layers::vector::constants::ManipulatorType;
use glam::DVec2;
use serde::{Deserialize, Serialize};
@@ -152,7 +152,7 @@ impl Fsm for PathToolFsmState {
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_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
tool_data.overlay_renderer.render_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
}
// This can happen in any state (which is why we return self)
@@ -162,7 +162,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_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
tool_data.overlay_renderer.render_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
}
self
@@ -186,9 +186,9 @@ impl Fsm for PathToolFsmState {
// Do not snap against handles when anchor is selected
let mut extension = Vec::new();
for &(path, id, point_type) in new_selected.iter() {
if point_type == ControlPointType::Anchor {
extension.push((path, id, ControlPointType::InHandle));
extension.push((path, id, ControlPointType::OutHandle));
if point_type == ManipulatorType::Anchor {
extension.push((path, id, ManipulatorType::InHandle));
extension.push((path, id, ManipulatorType::OutHandle));
}
}
new_selected.extend(extension);
@@ -269,14 +269,14 @@ impl Fsm for PathToolFsmState {
tool_data.shape_editor.delete_selected_points(responses);
responses.push_back(SelectionChanged.into());
for layer_path in document.all_layers() {
tool_data.overlay_renderer.clear_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
}
Ready
}
(_, Abort) => {
// TODO Tell overlay manager to remove the overlays
for layer_path in document.all_layers() {
tool_data.overlay_renderer.clear_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
}
Ready
}

View File

@@ -11,9 +11,9 @@ use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData
use crate::viewport_tools::vector_editor::overlay_renderer::OverlayRenderer;
use graphene::layers::style;
use graphene::layers::vector::constants::ControlPointType;
use graphene::layers::vector::vector_anchor::VectorAnchor;
use graphene::layers::vector::vector_shape::VectorShape;
use graphene::layers::vector::constants::ManipulatorType;
use graphene::layers::vector::manipulator_group::ManipulatorGroup;
use graphene::layers::vector::subpath::Subpath;
use graphene::Operation;
use glam::{DAffine2, DVec2};
@@ -180,7 +180,7 @@ 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_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
tool_data.overlay_renderer.render_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
}
self
}
@@ -217,12 +217,15 @@ impl Fsm for PenToolFsmState {
path: layer_path.clone(),
transform: DAffine2::IDENTITY.to_cols_array(),
insert_index: -1,
vector_path: Default::default(),
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_anchor(&tool_data.path, VectorAnchor::new_with_handles(start_position, Some(start_position), Some(start_position))));
responses.push_back(add_manipulator_group(
&tool_data.path,
ManipulatorGroup::new_with_handles(start_position, Some(start_position), Some(start_position)),
));
}
PenToolFsmState::DraggingHandle
@@ -231,9 +234,9 @@ impl Fsm for PenToolFsmState {
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
// Add new point onto path
if let Some(layer_path) = &tool_data.path {
if let Some(vector_anchor) = get_vector_shape(layer_path, document).and_then(|shape| shape.anchors().last()) {
if let Some(anchor) = &vector_anchor.points[ControlPointType::OutHandle] {
responses.push_back(add_anchor(&tool_data.path, VectorAnchor::new(anchor.position)));
if let Some(manipulator_group) = get_subpath(layer_path, document).and_then(|subpath| subpath.manipulator_groups().last()) {
if let Some(out_handle) = &manipulator_group.points[ManipulatorType::OutHandle] {
responses.push_back(add_manipulator_group(&tool_data.path, ManipulatorGroup::new_with_anchor(out_handle.position)));
}
}
}
@@ -244,29 +247,29 @@ impl Fsm for PenToolFsmState {
if let Some(layer_path) = &tool_data.path {
let mouse = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
let mut pos = transform.inverse().transform_point2(mouse);
if let Some(((&id, anchor), _previous)) = get_vector_shape(layer_path, document).and_then(last_2_anchors) {
if let Some(anchor) = anchor.points[ControlPointType::Anchor as usize].as_ref() {
if let Some(((&id, manipulator_group), _previous)) = get_subpath(layer_path, document).and_then(last_2_manipulator_groups) {
if let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() {
pos = compute_snapped_angle(input, snap_angle, pos, anchor.position);
}
// Update points on current segment (to show preview of new handle)
let msg = Operation::MoveVectorPoint {
let msg = Operation::MoveManipulatorPoint {
layer_path: layer_path.clone(),
id,
control_type: ControlPointType::OutHandle,
manipulator_type: ManipulatorType::OutHandle,
position: pos.into(),
};
responses.push_back(msg.into());
// Mirror handle of last segement
if !input.keyboard.get(break_handle as usize) && get_vector_shape(layer_path, document).map(|shape| shape.anchors().len() > 1).unwrap_or_default() {
if let Some(anchor) = anchor.points[ControlPointType::Anchor as usize].as_ref() {
// Mirror handle of last segment
if !input.keyboard.get(break_handle as usize) && get_subpath(layer_path, document).map(|shape| shape.manipulator_groups().len() > 1).unwrap_or_default() {
if let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() {
pos = anchor.position - (pos - anchor.position);
}
let msg = Operation::MoveVectorPoint {
let msg = Operation::MoveManipulatorPoint {
layer_path: layer_path.clone(),
id,
control_type: ControlPointType::InHandle,
manipulator_type: ManipulatorType::InHandle,
position: pos.into(),
};
responses.push_back(msg.into());
@@ -281,16 +284,16 @@ impl Fsm for PenToolFsmState {
let mouse = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
let mut pos = transform.inverse().transform_point2(mouse);
if let Some(((&id, _anchor), previous)) = get_vector_shape(layer_path, document).and_then(last_2_anchors) {
if let Some(relative) = previous.as_ref().and_then(|(_, anchor)| anchor.points[ControlPointType::Anchor as usize].as_ref()) {
if let Some(((&id, _), previous)) = get_subpath(layer_path, document).and_then(last_2_manipulator_groups) {
if let Some(relative) = previous.as_ref().and_then(|(_, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref()) {
pos = compute_snapped_angle(input, snap_angle, pos, relative.position);
}
for control_type in [ControlPointType::Anchor, ControlPointType::InHandle, ControlPointType::OutHandle] {
let msg = Operation::MoveVectorPoint {
for manipulator_type in [ManipulatorType::Anchor, ManipulatorType::InHandle, ManipulatorType::OutHandle] {
let msg = Operation::MoveManipulatorPoint {
layer_path: layer_path.clone(),
id,
control_type,
manipulator_type,
position: pos.into(),
};
responses.push_back(msg.into());
@@ -303,25 +306,25 @@ impl Fsm for PenToolFsmState {
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
// Abort or commit the transaction to the undo history
if let Some(layer_path) = tool_data.path.as_ref() {
if let Some(vector_shape) = (get_vector_shape(layer_path, document)).filter(|vector_shape| vector_shape.anchors().len() > 1) {
if let Some(((&(mut id), mut anchor), previous)) = last_2_anchors(vector_shape) {
if let Some(subpath) = (get_subpath(layer_path, document)).filter(|subpath| subpath.manipulator_groups().len() > 1) {
if let Some(((&(mut id), mut manipulator_group), previous)) = last_2_manipulator_groups(subpath) {
// Remove the unplaced anchor if in anchor placing mode
if self == PenToolFsmState::PlacingAnchor {
let layer_path = layer_path.clone();
let op = Operation::RemoveVectorAnchor { layer_path, id };
let op = Operation::RemoveManipulatorGroup { layer_path, id };
responses.push_back(op.into());
if let Some((&new_id, new_anchor)) = previous {
if let Some((&new_id, new_manipulator_group)) = previous {
id = new_id;
anchor = new_anchor;
manipulator_group = new_manipulator_group;
}
}
// Remove the out handle if in dragging handle mode
let op = Operation::MoveVectorPoint {
let op = Operation::MoveManipulatorPoint {
layer_path: layer_path.clone(),
id,
control_type: ControlPointType::OutHandle,
position: anchor.points[ControlPointType::Anchor as usize].as_ref().unwrap().position.into(),
manipulator_type: ManipulatorType::OutHandle,
position: manipulator_group.points[ManipulatorType::Anchor].as_ref().unwrap().position.into(),
};
responses.push_back(op.into());
}
@@ -334,7 +337,7 @@ impl Fsm for PenToolFsmState {
// Clean up overlays
for layer_path in document.all_layers() {
tool_data.overlay_renderer.clear_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
}
tool_data.path = None;
tool_data.snap_handler.cleanup(responses);
@@ -344,7 +347,7 @@ impl Fsm for PenToolFsmState {
(_, PenToolMessage::Abort) => {
// Clean up overlays
for layer_path in document.all_layers() {
tool_data.overlay_renderer.clear_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
}
self
}
@@ -373,7 +376,7 @@ impl Fsm for PenToolFsmState {
HintGroup(vec![HintInfo {
key_groups: vec![],
mouse: Some(MouseMotion::Lmb),
label: String::from("Add Control Point"),
label: String::from("Add Anchor"),
plus: false,
}]),
HintGroup(vec![HintInfo {
@@ -406,7 +409,7 @@ impl Fsm for PenToolFsmState {
}
// TODO: Expand `pos` name below to the full word (position?)
/// Snap the angle of the line from relative to pos if the key is pressed
/// Snap the angle of the line from relative to pos if the key is pressed.
fn compute_snapped_angle(input: &InputPreprocessorMessageHandler, key: Key, pos: DVec2, relative: DVec2) -> DVec2 {
if input.keyboard.get(key as usize) {
let delta = relative - pos;
@@ -424,12 +427,12 @@ fn compute_snapped_angle(input: &InputPreprocessorMessageHandler, key: Key, pos:
}
}
/// Pushes an anchor to the current layer via an [Operation]
fn add_anchor(layer_path: &Option<Vec<LayerId>>, anchor: VectorAnchor) -> Message {
/// Pushes a [ManipulatorGroup] to the current layer via an [Operation].
fn add_manipulator_group(layer_path: &Option<Vec<LayerId>>, manipulator_group: ManipulatorGroup) -> Message {
if let Some(layer_path) = layer_path {
Operation::PushVectorAnchor {
Operation::PushManipulatorGroup {
layer_path: layer_path.clone(),
anchor,
manipulator_group,
}
.into()
} else {
@@ -437,20 +440,20 @@ fn add_anchor(layer_path: &Option<Vec<LayerId>>, anchor: VectorAnchor) -> Messag
}
}
/// Gets the currently editing [VectorShape]
fn get_vector_shape<'a>(layer_path: &'a [LayerId], document: &'a DocumentMessageHandler) -> Option<&'a VectorShape> {
document.graphene_document.layer(layer_path).ok().and_then(|layer| layer.as_vector_shape())
/// Gets the currently editing [Subpath].
fn get_subpath<'a>(layer_path: &'a [LayerId], document: &'a DocumentMessageHandler) -> Option<&'a Subpath> {
document.graphene_document.layer(layer_path).ok().and_then(|layer| layer.as_subpath())
}
type AnchorRef<'a> = (&'a u64, &'a VectorAnchor);
type ManipulatorGroupRef<'a> = (&'a u64, &'a ManipulatorGroup);
/// Gets the last 2 [VectorAnchor] on the currently editing layer along with its id
fn last_2_anchors(vector_shape: &VectorShape) -> Option<(AnchorRef, Option<AnchorRef>)> {
vector_shape.anchors().enumerate().last().map(|last| {
/// Gets the last 2 [ManipulatorGroup]s on the currently editing layer along with its ID.
fn last_2_manipulator_groups(subpath: &Subpath) -> Option<(ManipulatorGroupRef, Option<ManipulatorGroupRef>)> {
subpath.manipulator_groups().enumerate().last().map(|last| {
(
last,
(vector_shape.anchors().len() > 1)
.then(|| vector_shape.anchors().enumerate().nth(vector_shape.anchors().len() - 2))
(subpath.manipulator_groups().len() > 1)
.then(|| subpath.manipulator_groups().enumerate().nth(subpath.manipulator_groups().len() - 2))
.flatten(),
)
})

View File

@@ -7,7 +7,7 @@ use graphene::intersection::Quad;
use graphene::layers::layer_info::LayerDataType;
use graphene::layers::style::{self, Fill, Stroke};
use graphene::layers::text_layer::FontCache;
use graphene::layers::vector::vector_shape::VectorShape;
use graphene::layers::vector::subpath::Subpath;
use graphene::{LayerId, Operation};
use glam::{DAffine2, DVec2};
@@ -35,12 +35,10 @@ impl PathOutline {
// TODO Purge this area of BezPath and Kurbo
// Get the bezpath from the shape or text
let vector_path = match &document_layer.data {
let subpath = match &document_layer.data {
LayerDataType::Shape(layer_shape) => Some(layer_shape.shape.clone()),
LayerDataType::Text(text) => Some(text.to_vector_path_nonmut(font_cache)),
_ => document_layer
.aabounding_box_for_transform(DAffine2::IDENTITY, font_cache)
.map(|[p1, p2]| VectorShape::new_rect(p1, p2)),
LayerDataType::Text(text) => Some(text.to_subpath_nonmut(font_cache)),
_ => document_layer.aabb_for_transform(DAffine2::IDENTITY, font_cache).map(|[p1, p2]| Subpath::new_rect(p1, p2)),
}?;
// Generate a new overlay layer if necessary
@@ -50,7 +48,7 @@ impl PathOutline {
let overlay_path = vec![generate_uuid()];
let operation = Operation::AddShape {
path: overlay_path.clone(),
vector_path: Default::default(),
subpath: Default::default(),
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, PATH_OUTLINE_WEIGHT)), Fill::None),
insert_index: -1,
transform: DAffine2::IDENTITY.to_cols_array(),
@@ -63,7 +61,7 @@ impl PathOutline {
};
// Update the shape bezpath
let operation = Operation::SetShapePath { path: overlay.clone(), vector_path };
let operation = Operation::SetShapePath { path: overlay.clone(), subpath };
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
// Update the transform to match the document
@@ -110,7 +108,7 @@ impl PathOutline {
}
}
/// Clears overlays for the seleted paths and removes references
/// Clears overlays for the selected paths and removes references
pub fn clear_selected(&mut self, responses: &mut VecDeque<Message>) {
while let Some(path) = self.selected_overlay_paths.pop() {
let operation = Operation::DeleteLayer { path };

View File

@@ -1,4 +1,4 @@
use crate::consts::{BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, COLOR_ACCENT, SELECTION_DRAG_ANGLE, VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE};
use crate::consts::{BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, COLOR_ACCENT, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_DRAG_ANGLE};
use crate::document::transformation::OriginalTransforms;
use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::InputPreprocessorMessageHandler;
@@ -219,7 +219,7 @@ impl BoundingBoxOverlays {
}
}
/// Calculats the transformed handle positions based on the bounding box and the transform
/// Calculates the transformed handle positions based on the bounding box and the transform
pub fn evaluate_transform_handle_positions(&self) -> [DVec2; 8] {
let (left, top): (f64, f64) = self.bounds[0].into();
let (right, bottom): (f64, f64) = self.bounds[1].into();
@@ -245,7 +245,7 @@ impl BoundingBoxOverlays {
const BIAS: f64 = 0.0001;
for (position, path) in self.evaluate_transform_handle_positions().into_iter().zip(&self.transform_handles) {
let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
let translation = (position - (scale / 2.) - 0.5 + BIAS).round();
let transform = DAffine2::from_scale_angle_translation(scale, 0., translation).to_cols_array();
let path = path.clone();

View File

@@ -238,7 +238,7 @@ fn update_overlays(document: &DocumentMessageHandler, tool_data: &mut TextToolDa
.graphene_document
.layer(layer_path)
.unwrap()
.aabounding_box_for_transform(document.graphene_document.multiply_transforms(layer_path).unwrap(), font_cache)
.aabb_for_transform(document.graphene_document.multiply_transforms(layer_path).unwrap(), font_cache)
.map(|bounds| (bounds, overlay_path))
})
.collect::<Vec<_>>();

View File

@@ -1,6 +1,6 @@
pub mod constants;
pub mod overlay_renderer;
pub mod shape_editor;
use graphene::layers::vector::vector_anchor;
use graphene::layers::vector::vector_control_point;
use graphene::layers::vector::vector_shape;
use graphene::layers::vector::manipulator_group;
use graphene::layers::vector::manipulator_point;
use graphene::layers::vector::subpath;

View File

@@ -1,47 +1,46 @@
use super::constants::ROUNDING_BIAS;
use super::vector_anchor::VectorAnchor;
use super::vector_control_point::VectorControlPoint;
use crate::consts::{COLOR_ACCENT, PATH_OUTLINE_WEIGHT, VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE};
use super::manipulator_group::ManipulatorGroup;
use super::manipulator_point::ManipulatorPoint;
use crate::consts::{COLOR_ACCENT, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT};
use crate::message_prelude::{generate_uuid, DocumentMessage, Message};
use graphene::color::Color;
use graphene::document::Document;
use graphene::layers::style::{self, Fill, Stroke};
use graphene::layers::vector::constants::ControlPointType;
use graphene::layers::vector::vector_shape::VectorShape;
use graphene::layers::vector::constants::ManipulatorType;
use graphene::layers::vector::subpath::Subpath;
use graphene::{LayerId, Operation};
use glam::{DAffine2, DVec2};
use std::collections::{HashMap, VecDeque};
/// AnchorOverlay is the collection of overlays that make up an anchor
/// Notably the anchor point, handles and the lines for the handles
type AnchorOverlays = [Option<Vec<LayerId>>; 5];
type AnchorId = u64;
/// [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;
const POINT_STROKE_WEIGHT: f64 = 2.;
#[derive(Clone, Debug, Default)]
pub struct OverlayRenderer {
shape_overlay_cache: HashMap<LayerId, Vec<LayerId>>,
anchor_overlay_cache: HashMap<(LayerId, AnchorId), AnchorOverlays>,
manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorId), ManipulatorGroupOverlays>,
}
impl OverlayRenderer {
pub fn new() -> Self {
OverlayRenderer {
anchor_overlay_cache: HashMap::new(),
manipulator_group_overlay_cache: HashMap::new(),
shape_overlay_cache: HashMap::new(),
}
}
pub fn render_vector_shape_overlays(&mut self, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
pub fn render_subpath_overlays(&mut self, 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_vector_shape() {
if let Some(shape) = layer.as_subpath() {
let outline_cache = self.shape_overlay_cache.get(layer_id);
log::trace!("Overlay: Outline cache {:?}", &outline_cache);
@@ -57,27 +56,27 @@ impl OverlayRenderer {
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
}
// Create, place and style the anchor / handle overlays
for (anchor_id, anchor) in shape.anchors().enumerate() {
let anchor_cache = self.anchor_overlay_cache.get_mut(&(*layer_id, *anchor_id));
// 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.get_mut(&(*layer_id, *manipulator_group_id));
// If cached update placement and style
if let Some(anchor_overlays) = anchor_cache {
log::trace!("Overlay: Updating detail overlays for {:?}", anchor_overlays);
Self::place_anchor_overlays(anchor, anchor_overlays, &transform, responses);
Self::style_overlays(anchor, anchor_overlays, responses);
if let Some(manipulator_group_overlays) = manipulator_group_cache {
log::trace!("Overlay: Updating detail overlays for {:?}", manipulator_group_overlays);
Self::place_manipulator_group_overlays(manipulator_group, manipulator_group_overlays, &transform, responses);
Self::style_overlays(manipulator_group, manipulator_group_overlays, responses);
} else {
// Create if not cached
let mut anchor_overlays = [
let mut manipulator_group_overlays = [
Some(self.create_anchor_overlay(responses)),
Self::create_handle_overlay_if_exists(&anchor.points[ControlPointType::InHandle], responses),
Self::create_handle_overlay_if_exists(&anchor.points[ControlPointType::OutHandle], responses),
Self::create_handle_line_overlay_if_exists(&anchor.points[ControlPointType::InHandle], responses),
Self::create_handle_line_overlay_if_exists(&anchor.points[ControlPointType::OutHandle], responses),
Self::create_handle_overlay_if_exists(&manipulator_group.points[ManipulatorType::InHandle], responses),
Self::create_handle_overlay_if_exists(&manipulator_group.points[ManipulatorType::OutHandle], responses),
Self::create_handle_line_overlay_if_exists(&manipulator_group.points[ManipulatorType::InHandle], responses),
Self::create_handle_line_overlay_if_exists(&manipulator_group.points[ManipulatorType::OutHandle], responses),
];
Self::place_anchor_overlays(anchor, &mut anchor_overlays, &transform, responses);
Self::style_overlays(anchor, &anchor_overlays, responses);
self.anchor_overlay_cache.insert((*layer_id, *anchor_id), anchor_overlays);
Self::place_manipulator_group_overlays(manipulator_group, &mut manipulator_group_overlays, &transform, responses);
Self::style_overlays(manipulator_group, &manipulator_group_overlays, responses);
self.manipulator_group_overlay_cache.insert((*layer_id, *manipulator_group_id), manipulator_group_overlays);
}
}
// TODO Handle removing shapes from cache so we don't memory leak
@@ -86,7 +85,7 @@ impl OverlayRenderer {
}
}
pub fn clear_vector_shape_overlays(&mut self, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
pub fn clear_subpath_overlays(&mut self, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
let layer_id = layer_path.last().unwrap();
// Remove the shape outline overlays
@@ -95,13 +94,13 @@ impl OverlayRenderer {
}
self.shape_overlay_cache.remove(layer_id);
// Remove the anchor overlays
// Remove the ManipulatorGroup overlays
if let Ok(layer) = document.layer(&layer_path) {
if let Some(shape) = layer.as_vector_shape() {
for (id, _) in shape.anchors().enumerate() {
if let Some(anchor_overlays) = self.anchor_overlay_cache.get(&(*layer_id, *id)) {
Self::remove_anchor_overlays(anchor_overlays, responses);
self.anchor_overlay_cache.remove(&(*layer_id, *id));
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)) {
Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses);
self.manipulator_group_overlay_cache.remove(&(*layer_id, *id));
}
}
}
@@ -116,24 +115,24 @@ impl OverlayRenderer {
Self::set_outline_overlay_visibility(overlay_path.clone(), visibility, responses);
}
// Hide the anchor overlays
// Hide the manipulator group overlays
if let Ok(layer) = document.layer(&layer_path) {
if let Some(shape) = layer.as_vector_shape() {
for (id, _) in shape.anchors().enumerate() {
if let Some(anchor_overlays) = self.anchor_overlay_cache.get(&(*layer_id, *id)) {
Self::set_anchor_overlay_visibility(anchor_overlays, visibility, responses);
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)) {
Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses);
}
}
}
}
}
/// Create the kurbo shape that matches the selected viewport shape
fn create_shape_outline_overlay(&self, vector_path: VectorShape, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
/// Create the kurbo shape that matches the selected viewport shape.
fn create_shape_outline_overlay(&self, subpath: Subpath, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
let layer_path = vec![generate_uuid()];
let operation = Operation::AddShape {
path: layer_path.clone(),
vector_path,
subpath,
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, PATH_OUTLINE_WEIGHT)), Fill::None),
insert_index: -1,
transform: DAffine2::IDENTITY.to_cols_array(),
@@ -143,7 +142,7 @@ impl OverlayRenderer {
layer_path
}
/// Create a single anchor overlay and return its layer id
/// Create a single anchor overlay and return its layer ID.
fn create_anchor_overlay(&self, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
let layer_path = vec![generate_uuid()];
let operation = Operation::AddRect {
@@ -156,7 +155,7 @@ impl OverlayRenderer {
layer_path
}
/// Create a single handle overlay and return its layer id
/// Create a single handle overlay and return its layer ID.
fn create_handle_overlay(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
let layer_path = vec![generate_uuid()];
let operation = Operation::AddEllipse {
@@ -169,12 +168,12 @@ impl OverlayRenderer {
layer_path
}
/// Create a single handle overlay and return its layer id if it exists
fn create_handle_overlay_if_exists(handle: &Option<VectorControlPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
/// 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>> {
handle.as_ref().map(|_| Self::create_handle_overlay(responses))
}
/// Create the shape outline overlay and return its layer id
/// Create the shape outline overlay and return its layer ID.
fn create_handle_line_overlay(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
let layer_path = vec![generate_uuid()];
let operation = Operation::AddLine {
@@ -187,8 +186,8 @@ impl OverlayRenderer {
layer_path
}
/// Create the shape outline overlay and return its layer id
fn create_handle_line_overlay_if_exists(handle: &Option<VectorControlPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
/// 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>> {
handle.as_ref().map(|_| Self::create_handle_line_overlay(responses))
}
@@ -197,18 +196,18 @@ impl OverlayRenderer {
responses.push_back(transform_message);
}
fn modify_outline_overlays(outline_path: Vec<LayerId>, vector_path: VectorShape, responses: &mut VecDeque<Message>) {
let outline_modify_message = Self::overlay_modify_message(outline_path, vector_path);
fn modify_outline_overlays(outline_path: Vec<LayerId>, subpath: 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 VectorShape points
fn place_anchor_overlays(anchor: &VectorAnchor, overlays: &mut AnchorOverlays, parent_transform: &DAffine2, responses: &mut VecDeque<Message>) {
if let Some(anchor_point) = &anchor.points[ControlPointType::Anchor] {
// Helper function to keep things DRY
let mut place_handle_and_line = |handle: &VectorControlPoint, line_source: &mut Option<Vec<LayerId>>, marker_source: &mut Option<Vec<LayerId>>| {
/// 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_source: &mut Option<Vec<LayerId>>, marker_source: &mut Option<Vec<LayerId>>| {
let line_overlay = line_source.take().unwrap_or_else(|| Self::create_handle_line_overlay(responses));
let line_vector = parent_transform.transform_point2(anchor_point.position) - parent_transform.transform_point2(handle.position);
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) + ROUNDING_BIAS).round() + DVec2::splat(0.5);
@@ -217,7 +216,7 @@ impl OverlayRenderer {
*line_source = Some(line_overlay);
let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses));
let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
let angle = 0.;
let translation = (parent_transform.transform_point2(handle.position) - (scale / 2.) + ROUNDING_BIAS).round();
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
@@ -226,7 +225,7 @@ impl OverlayRenderer {
};
// Place the handle overlays
let [_, h1, h2] = &anchor.points;
let [_, h1, h2] = &manipulator_group.points;
let [a, b, c, line1, line2] = overlays;
let markers = [a, b, c];
if let Some(handle) = &h1 {
@@ -237,10 +236,10 @@ impl OverlayRenderer {
}
// Place the anchor point overlay
if let Some(anchor_overlay) = &overlays[ControlPointType::Anchor as usize] {
let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
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(anchor_point.position) - (scale / 2.) + ROUNDING_BIAS).round();
let translation = (parent_transform.transform_point2(manipulator_point.position) - (scale / 2.) + 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);
@@ -249,8 +248,8 @@ impl OverlayRenderer {
}
}
/// Removes the anchor / handle overlays from the overlay document
fn remove_anchor_overlays(overlay_paths: &AnchorOverlays, responses: &mut VecDeque<Message>) {
/// Removes the manipulator overlays from the overlay document.
fn remove_manipulator_group_overlays(overlay_paths: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) {
overlay_paths.iter().flatten().for_each(|layer_id| {
log::trace!("Overlay: Sending delete message for: {:?}", layer_id);
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer_id.clone() }.into()).into());
@@ -261,9 +260,9 @@ impl OverlayRenderer {
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: overlay_path }.into()).into());
}
/// Sets the visibility of the handles overlay
fn set_anchor_overlay_visibility(anchor_overlays: &AnchorOverlays, visibility: bool, responses: &mut VecDeque<Message>) {
anchor_overlays.iter().flatten().for_each(|layer_id| {
/// Sets the visibility of the handles overlay.
fn set_manipulator_group_overlay_visibility(manipulator_group_overlays: &ManipulatorGroupOverlays, visibility: bool, responses: &mut VecDeque<Message>) {
manipulator_group_overlays.iter().flatten().for_each(|layer_id| {
responses.push_back(Self::overlay_visibility_message(layer_id.clone(), visibility));
});
}
@@ -272,7 +271,7 @@ impl OverlayRenderer {
responses.push_back(Self::overlay_visibility_message(overlay_path, visibility));
}
/// Create a visibility message for an overlay
/// Create a visibility message for an overlay.
fn overlay_visibility_message(layer_path: Vec<LayerId>, visibility: bool) -> Message {
DocumentMessage::Overlays(
Operation::SetLayerVisibility {
@@ -284,28 +283,27 @@ impl OverlayRenderer {
.into()
}
/// Create a transform message for an overlay
/// Create a transform message for an overlay.
fn overlay_transform_message(layer_path: Vec<LayerId>, transform: [f64; 6]) -> Message {
DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path: layer_path, transform }.into()).into()
}
/// Create an update message for an overlay
fn overlay_modify_message(layer_path: Vec<LayerId>, vector_path: VectorShape) -> Message {
DocumentMessage::Overlays(Operation::SetShapePath { path: layer_path, vector_path }.into()).into()
/// Create an update message for an overlay.
fn overlay_modify_message(layer_path: Vec<LayerId>, subpath: Subpath) -> Message {
DocumentMessage::Overlays(Operation::SetShapePath { path: layer_path, subpath }.into()).into()
}
/// Sets the overlay style for this point
fn style_overlays(anchor: &VectorAnchor, overlays: &AnchorOverlays, responses: &mut VecDeque<Message>) {
// TODO Move the style definitions out of the VectorShape, should be looked up from a stylesheet or similar
/// Sets the overlay style for this point.
fn style_overlays(manipulator_group: &ManipulatorGroup, 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));
// Update if the anchor / handle points are shown as selected
// 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 anchor.points.iter().enumerate() {
for (index, point) in manipulator_group.points.iter().enumerate() {
if let Some(point) = point {
if let Some(overlay) = &overlays[index] {
// log::debug!("style_overlays: {:?}", &overlay);
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());
}

View File

@@ -1,37 +1,38 @@
// Overview:
// ShapeEditor
// / \
// selected_shape_layers <- Paths to selected layers that may contain VectorShapes
// | |
// VectorShape ... VectorShape <- Reference from layer paths, one Vectorshape per layer
// / \
// VectorAnchor ... VectorAnchor <- VectorShape contains many VectorAnchors
use super::vector_anchor::VectorAnchor;
use super::vector_control_point::VectorControlPoint;
use super::vector_shape::VectorShape;
use super::manipulator_group::ManipulatorGroup;
use super::manipulator_point::ManipulatorPoint;
use super::subpath::Subpath;
use crate::message_prelude::{DocumentMessage, Message};
use graphene::layers::vector::constants::ControlPointType;
use graphene::layers::vector::constants::ManipulatorType;
use graphene::{LayerId, Operation};
use glam::DVec2;
use graphene::document::Document;
use std::collections::VecDeque;
/// ShapeEditor is the container for all of the layer paths that are
/// represented as VectorShapes and provides functionality required
/// to query and create the VectorShapes / VectorAnchors / VectorControlPoints
/// 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 {
// The layers we can select and edit anchors / handles from
// The layers we can select and edit manipulators (anchors and handles) from
selected_layers: Vec<Vec<LayerId>>,
}
// TODO Consider keeping a list of selected anchors to minimize traversals of the layers
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
impl ShapeEditor {
/// Select the first point within the selection threshold
/// Returns the points if found, none otherwise
/// Select the first point within the selection threshold.
/// Returns the points if found, None otherwise.
pub fn select_point(
&self,
document: &Document,
@@ -39,23 +40,23 @@ impl ShapeEditor {
select_threshold: f64,
add_to_selection: bool,
responses: &mut VecDeque<Message>,
) -> Option<Vec<(&[LayerId], u64, ControlPointType)>> {
) -> Option<Vec<(&[LayerId], u64, ManipulatorType)>> {
if self.selected_layers.is_empty() {
return None;
}
if let Some((shape_layer_path, anchor_id, point_index)) = self.find_nearest_point_indicies(document, mouse_position, select_threshold) {
log::trace!("Selecting: anchor {} / point {}", anchor_id, point_index);
if let Some((shape_layer_path, manipulator_group_id, manipulator_point_index)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) {
log::trace!("Selecting... manipulator group ID: {}, manipulator point index: {}", manipulator_group_id, manipulator_point_index);
// If the point we're selecting has already been selected
// we can assume this point exists.. since we did just click on it hense the unwrap
let is_point_selected = self.shape(document, shape_layer_path).unwrap().anchors().by_id(anchor_id).unwrap().points[point_index]
// 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 point_position = self.shape(document, shape_layer_path).unwrap().anchors().by_id(anchor_id).unwrap().points[point_index]
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;
@@ -65,35 +66,33 @@ impl ShapeEditor {
.selected_layers()
.iter()
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
.filter_map(|(path, shape)| shape.as_vector_shape().map(|vector| (path, vector)))
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
.flat_map(|(path, shape)| {
shape
.anchors()
.manipulator_groups()
.enumerate()
.filter(|(_id, anchor)| anchor.is_anchor_selected())
.flat_map(|(id, anchor)| anchor.selected_points().map(move |point| (id, point.manipulator_type)))
.map(|(anchor, control_point)| (path.as_slice(), *anchor, control_point))
.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)| (path.as_slice(), *anchor, manipulator_point))
})
.collect::<Vec<_>>();
// let selected_shape = self.shape(document, shape_layer_path).unwrap();
// Should we select or deselect the point?
let should_select = if is_point_selected { !add_to_selection } else { true };
// This is selecting the anchor only for now, next to generalize to points
// This is selecting the manipulator only for now, next to generalize to points
if should_select {
let add = add_to_selection || is_point_selected;
let point = (anchor_id, ControlPointType::from_index(point_index));
let point = (manipulator_group_id, ManipulatorType::from_index(manipulator_point_index));
// Clear all point in other selected shapes
if !(add) {
responses.push_back(DocumentMessage::DeselectAllVectorPoints.into());
if !add {
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
points = vec![(shape_layer_path, point.0, point.1)];
} else {
points.push((shape_layer_path, point.0, point.1));
}
responses.push_back(
Operation::SelectVectorPoints {
Operation::SelectManipulatorPoints {
layer_path: shape_layer_path.to_vec(),
point_ids: vec![point],
add,
@@ -106,34 +105,34 @@ impl ShapeEditor {
}
} else {
responses.push_back(
Operation::DeselectVectorPoints {
Operation::DeselectManipulatorPoints {
layer_path: shape_layer_path.to_vec(),
point_ids: vec![(anchor_id, ControlPointType::from_index(point_index))],
point_ids: vec![(manipulator_group_id, ManipulatorType::from_index(manipulator_point_index))],
}
.into(),
);
points.retain(|x| *x != (shape_layer_path, anchor_id, ControlPointType::from_index(point_index)))
points.retain(|x| *x != (shape_layer_path, manipulator_group_id, ManipulatorType::from_index(manipulator_point_index)))
}
return Some(points);
}
// Deselect all points if no nearby point
responses.push_back(DocumentMessage::DeselectAllVectorPoints.into());
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
None
}
/// A wrapper for find_nearest_point_indicies and returns a VectorControlPoint
pub fn find_nearest_point<'a>(&'a self, document: &'a Document, mouse_position: DVec2, select_threshold: f64) -> Option<&'a VectorControlPoint> {
let (shape_layer_path, anchor_id, point_index) = self.find_nearest_point_indicies(document, mouse_position, select_threshold)?;
/// 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(anchor) = selected_shape.anchors().by_id(anchor_id) {
return anchor.points[point_index].as_ref();
if let Some(manipulator_group) = selected_shape.manipulator_groups().by_id(manipulator_group_id) {
return manipulator_group.points[manipulator_point_index].as_ref();
}
None
}
/// Set the shapes we consider for selection, we will choose draggable handles / anchors from these shapes.
/// 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;
}
@@ -146,7 +145,7 @@ impl ShapeEditor {
self.selected_layers.iter().map(|l| l.as_slice()).collect::<Vec<_>>()
}
/// Clear all of the shapes we can modify
/// Clear all of the shapes we can modify.
pub fn clear_selected_layers(&mut self) {
self.selected_layers.clear();
}
@@ -155,26 +154,26 @@ impl ShapeEditor {
!self.selected_layers.is_empty()
}
/// Provide the currently selected anchor by reference
pub fn selected_anchors<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorAnchor> {
self.iter(document).flat_map(|shape| shape.selected_anchors())
/// 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())
}
/// A mutable iterator of all the anchors, regardless of selection
pub fn anchors<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorAnchor> {
self.iter(document).flat_map(|shape| shape.anchors().iter())
/// 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())
}
/// Provide the currently selected points by reference
pub fn selected_points<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorControlPoint> {
self.selected_anchors(document).flat_map(|anchors| anchors.selected_points())
/// 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())
}
/// Move the selected points by dragging the moue
/// Move the selected points by dragging the mouse.
pub fn move_selected_points(&self, delta: DVec2, absolute_position: DVec2, responses: &mut VecDeque<Message>) {
for layer_path in &self.selected_layers {
responses.push_back(
DocumentMessage::MoveSelectedVectorPoints {
DocumentMessage::MoveSelectedManipulatorPoints {
layer_path: layer_path.clone(),
delta: (delta.x, delta.y),
absolute_position: (absolute_position.x, absolute_position.y),
@@ -184,12 +183,12 @@ impl ShapeEditor {
}
}
/// Dissolve the selected points
/// Dissolve the selected points.
pub fn delete_selected_points(&self, responses: &mut VecDeque<Message>) {
responses.push_back(DocumentMessage::DeleteSelectedVectorPoints.into());
responses.push_back(DocumentMessage::DeleteSelectedManipulatorPoints.into());
}
/// Toggle if the handles should mirror angle across the anchor positon
/// Toggle if the handles should mirror angle across the anchor position.
pub fn toggle_handle_mirroring_on_selected(&self, toggle_angle: bool, toggle_distance: bool, responses: &mut VecDeque<Message>) {
for layer_path in &self.selected_layers {
responses.push_back(
@@ -203,18 +202,19 @@ impl ShapeEditor {
}
}
/// Deselect all anchors from the shapes the manipulation handler has created
/// 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::DeselectAllVectorPoints.into());
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
}
/// Iterate over the shapes
pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorShape> + 'a {
self.selected_layers.iter().flat_map(|layer_id| document.layer(layer_id)).filter_map(|shape| shape.as_vector_shape())
/// 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())
}
/// Find a point that is within the selection threshold and return an index to the shape, anchor, and point
fn find_nearest_point_indicies(&self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(&[LayerId], u64, usize)> {
/// 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() {
return None;
}
@@ -222,34 +222,36 @@ impl ShapeEditor {
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((anchor_id, point_index, distance_squared)) = self.closest_point_in_layer(document, layer, mouse_position) {
if let Some((manipulator_id, manipulator_point_index, distance_squared)) = self.closest_point_in_layer(document, layer, mouse_position) {
// Choose the first point under the threshold
if distance_squared < select_threshold_squared {
log::trace!("Selecting: anchor {} / point {}", anchor_id, point_index);
return Some((layer, anchor_id, point_index));
log::trace!("Selecting... manipulator ID: {}, manipulator point index: {}", manipulator_id, manipulator_point_index);
return Some((layer, manipulator_id, manipulator_point_index));
}
}
}
None
}
// TODO Use quadtree or some equivalent spatial acceleration structure to improve this to O(log(n))
/// Find the closest point, anchor and distance so we can select path elements
/// Brute force comparison to determine which handle / anchor we want to select, O(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;
if let Some(shape) = document.layer(layer_path).ok()?.as_vector_shape() {
if let Some(shape) = document.layer(layer_path).ok()?.as_subpath() {
let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
for (anchor_id, anchor) in shape.anchors().enumerate() {
let point_index = anchor.closest_point(&viewspace, pos);
if let Some(point) = &anchor.points[point_index] {
for (manipulator_id, manipulator) in shape.manipulator_groups().enumerate() {
let manipulator_point_index = manipulator.closest_point(&viewspace, pos);
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((*anchor_id, point_index, distance_squared));
result = Some((*manipulator_id, manipulator_point_index, distance_squared));
}
}
}
@@ -258,7 +260,7 @@ impl ShapeEditor {
result
}
fn shape<'a>(&'a self, document: &'a Document, layer_id: &[u64]) -> Option<&'a VectorShape> {
document.layer(layer_id).ok()?.as_vector_shape()
fn shape<'a>(&'a self, document: &'a Document, layer_id: &[u64]) -> Option<&'a Subpath> {
document.layer(layer_id).ok()?.as_subpath()
}
}