mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-26 08:28:12 +08:00
Refactor internal shape and reduce reliance on Kurbo (#617)
* Dissolve Points from path * Add handling for removing the first anchor * Add function to turn handles into bez_paths * Created overlay manager, wip * WIP Refactor of VectorShape / Overlays / ShapeEditor * WIP stripping vector shape, anchor, point. * WIP Removed kurbo deps from vector shape, anchor, point * WIP Further work to make vector shapes / anchors / points more standalone. * WIP more pruning * WIP Progress on overlay_renderer * WIP more overlay_renderer work * WIP more pruning, cleared warnings * WIP decided ShapeRenderer wasn't an accurate name, ShapeAdapter now. Error squashing continues. * WIP squashed more errors, now need to decide if anchors should have unique IDs * WIP Errors squashed, now to actually make it work. * WIP Moved vector structs to graphene, beginning to remove bezpath from shape_layer * Refactoring: disentangle kurbo from apply_affine * Refactor internal shape and remove reliance on Kurbo (PR #617) - Disentangle Kurbo (#619) * Refactoring: disentangle kurbo from apply_affine * Broke boolean operations, refactor in state which compiles * "fixed" boolean operation refactor related errors * fixed apply_affine, which would not have applied any type of affine * Small Cleanup, readability * Fix issue with overlay styles no longer showing selection state. * Resolved error with point option * WIP, figuring out how to have one source of truth for VectorShape. Trying to avoid cloning. * WIP work on single source of truth vectorshapes * More steps toward single source of truth VectorShape * Continued wip on making VectorShapes mutably accessible without cloning * Wip using paths to reference vectorshapes instead, need to restructure ShapeEditor * Decided to allow temporary copies of vectorshapes. * Removed HashSet for selected shape indices * Added @TrueDoctor's id_storage.rs with some heavy modification. Added it to VectorShape. Isn't yet used for folders. * Integrated UniqueElements<T> with VectorShape to store VectorAnchors * Improved storage_id.rs perf and cleaned up it's interface * Iterator Implementations and fixes (#637) * Refactoring: disentangle kurbo from apply_affine * Broke boolean operations, refactor in state which compiles * "fixed" boolean operation refactor related errors * fixed apply_affine, which would not have applied any type of affine * implemented transforms for VectorAnchors implemented Not for VectorControlPointType * started adding Vector Shape implementations of shape prototypes * added several useful implemtations to UniqueElements * added another implemnation for UniqueElements to make working with iterators easier, fixed vector-shape errors * package-lock.json * clean up rebase, added back Layer paths * added deref implementation for VectorShape * unnecesary variable * simplify code by removing levels of indirection * fixed errors * merge cleanup * removed package-lock.json * Removed .selected from VectorShape, it isn't needed as layers are selected not shapes specifically. * Removed transform and layer_path from VectorShape * Auto-saving tentitively working. Work toward Overlay transform issues. * Overlays properly hiding and caching. Not clearing cache yet and some tool switching issues remain, but progress. * Putting layers in folders changes their unique ID. This is problematic. Assumed this was not the case. * Removed need for closed bool, changed VectorShape to a tuple struct. * WIP Switched to layer paths as opposed to VectorShapes. Next up add messages for changing VectorShapes. * Added initial messages to edit VectorShape points. * DeleteSelectedPoints messages implemented, selection isn't working currently though. * Selection messages arriving in document, but transform is wrong. * Selection, Deselection working, delete working for first point. * Working towards moving points again * Removed extra vec from UniqueElements, attempting to squash ordering bug. Still appears to occur though. * Delete more stable, clean up, renamed to HandleIn, HandleOut * Further vec_unique cleanup * Further cleanup * Removed Deref / DerefMut from VectorShape * Document version++, will likely revert before merge into master * Seleting / deleting handles tentitively working again. * Version number bump, fixed tests. * Fixed comment in VecUnique * Improved VecUnique descriptor comment * Renamed VecUnique to IdBackedVec to further clarify usage. * Resolved formatting. * WIP Fixing dragging points * Fixed an instance where an OverlayMessage could be sent to the main document incorrectly. * Deleting all of a shapes points now gracefully deletes the layer instead of crashing. * Fixed handle configurations that would panic on deletion * Single anchor dragging restored with multi-dragging next plus handles * sides.into() * Handle and Multi-point dragging working * WIP Handle symmetry working again * Handle mirroring functional again. * Cleaned up warnings * Fixed overlay outline not matching shape * Git branch fix of compatibility with new master * Fixed closed shape bug, replaced kurbo ellipse * Removed unused func, updated comments * Deleting points can undo, multiple shape selection deletes now working * Removed AddOverlay* operations * Partial fix for select drift, added helpers * Don't snap against dragging points * Properly cleanup path outline with multiple shapes * Clear all points in other selected shapes * Actually don't snap against dragging points * Fix path tool & add snap angle and break handle * Fix handle being set to NaN causing render issues * Fix cached overlays not showing line -> curve * Add operations for modifying paths * Remove kurbo from pen tool * Do not snap against handles when anchor selected * Fix overlays not being cleaned up on path tool * Fix handle position after dragging * Use `Anchor` for text & no kurbo in operations * Replace kurbo to_svg function * Ngon no longer center scales by default, still some weird behaviour when holding alt * Cleanup overlays * Fix render and bounding box doctests * Fix fun to_svg error * Fix compile error * Some code review * Remove legacy `SelectPoint` on doubleclick * Remove font from test document * Fix the pen tool selection changed * Reorder imports Co-authored-by: Dennis <dennis@kobert.dev> Co-authored-by: Caleb Dennis <caleb.dennis429@gmail.com> Co-authored-by: caleb <56044292+caleb-ad@users.noreply.github.com> Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: 0hypercube <0hypercube@gmail.com> Co-authored-by: 0HyperCube <78500760+0HyperCube@users.noreply.github.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Dennis
Caleb Dennis
caleb
Keavon Chambers
0hypercube
0HyperCube
parent
3c2fff4465
commit
58675eb64d
@@ -182,7 +182,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||
tool_data
|
||||
.snap_handler
|
||||
.start_snap(document, document.bounding_boxes(None, Some(tool_data.selected_board.unwrap()), font_cache), snap_x, snap_y);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]);
|
||||
|
||||
ArtboardToolFsmState::ResizingBounds
|
||||
} else {
|
||||
@@ -197,7 +197,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||
tool_data
|
||||
.snap_handler
|
||||
.start_snap(document, document.bounding_boxes(None, Some(intersection[0]), font_cache), true, true);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]);
|
||||
|
||||
responses.push_back(
|
||||
PropertiesPanelMessage::SetActiveLayers {
|
||||
@@ -213,7 +213,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||
tool_data.selected_board = Some(id);
|
||||
|
||||
tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, Some(id), font_cache), true, true);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]);
|
||||
|
||||
responses.push_back(
|
||||
ArtboardMessage::AddArtboard {
|
||||
|
||||
@@ -191,7 +191,6 @@ impl Fsm for FreehandToolFsmState {
|
||||
}
|
||||
(Drawing, DragStop) | (Drawing, Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_polyline(tool_data, global_tool_data));
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
|
||||
@@ -167,10 +167,11 @@ impl GradientOverlay {
|
||||
|
||||
let fill = if selected { Fill::solid(COLOR_ACCENT) } else { Fill::solid(Color::WHITE) };
|
||||
|
||||
let operation = Operation::AddOverlayEllipse {
|
||||
let operation = Operation::AddEllipse {
|
||||
path: path.clone(),
|
||||
transform: DAffine2::from_scale_angle_translation(size, 0., translation - size / 2.).to_cols_array(),
|
||||
style: PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), fill),
|
||||
insert_index: -1,
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
@@ -185,10 +186,11 @@ impl GradientOverlay {
|
||||
let translation = start;
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
|
||||
let operation = Operation::AddOverlayLine {
|
||||
let operation = Operation::AddLine {
|
||||
path: path.clone(),
|
||||
transform,
|
||||
style: PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Fill::None),
|
||||
insert_index: -1,
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
@@ -315,7 +317,7 @@ struct GradientToolData {
|
||||
|
||||
pub fn start_snap(snap_handler: &mut SnapHandler, document: &DocumentMessageHandler, font_cache: &FontCache) {
|
||||
snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
|
||||
snap_handler.add_all_document_handles(document, &[], &[]);
|
||||
snap_handler.add_all_document_handles(document, &[], &[], &[]);
|
||||
}
|
||||
|
||||
impl Fsm for GradientToolFsmState {
|
||||
|
||||
@@ -173,7 +173,7 @@ impl Fsm for LineToolFsmState {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]);
|
||||
tool_data.drag_start = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
@@ -6,9 +6,11 @@ use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::snapping::SnapHandler;
|
||||
use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
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 glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -32,6 +34,7 @@ pub enum PathToolMessage {
|
||||
SelectionChanged,
|
||||
|
||||
// Tool-specific messages
|
||||
Delete,
|
||||
DragStart {
|
||||
add_to_selection: Key,
|
||||
},
|
||||
@@ -82,8 +85,8 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PathTool {
|
||||
use PathToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(PathToolMessageDiscriminant; DragStart),
|
||||
Dragging => actions!(PathToolMessageDiscriminant; DragStop, PointerMove),
|
||||
Ready => actions!(PathToolMessageDiscriminant; DragStart, Delete),
|
||||
Dragging => actions!(PathToolMessageDiscriminant; DragStop, PointerMove, Delete),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,6 +116,7 @@ impl Default for PathToolFsmState {
|
||||
#[derive(Default)]
|
||||
struct PathToolData {
|
||||
shape_editor: ShapeEditor,
|
||||
overlay_renderer: OverlayRenderer,
|
||||
snap_handler: SnapHandler,
|
||||
|
||||
drag_start_pos: DVec2,
|
||||
@@ -137,39 +141,56 @@ impl Fsm for PathToolFsmState {
|
||||
use PathToolMessage::*;
|
||||
|
||||
match (self, event) {
|
||||
// TODO: Capture a tool event instead of doing this?
|
||||
(_, SelectionChanged) => {
|
||||
// Remove any residual overlays that might exist on selection change
|
||||
tool_data.shape_editor.remove_overlays(responses);
|
||||
// Set the previously selected layers to invisible
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.layer_overlay_visibility(&document.graphene_document, layer_path.to_vec(), false, responses);
|
||||
}
|
||||
|
||||
// This currently creates new VectorManipulatorShapes for every shape, which is not ideal
|
||||
// At least it is only on selection change for now
|
||||
tool_data.shape_editor.set_shapes_to_modify(document.selected_visible_layers_vector_shapes(responses, font_cache));
|
||||
// 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);
|
||||
|
||||
// This can happen in any state (which is why we return self)
|
||||
self
|
||||
}
|
||||
(_, DocumentIsDirty) => {
|
||||
// Update the VectorManipulatorShapes by reference so they match the kurbo tool_data
|
||||
for shape in &mut tool_data.shape_editor.shapes_to_modify {
|
||||
shape.update_shape(document, responses);
|
||||
// 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);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
// Mouse down
|
||||
(_, DragStart { add_to_selection }) => {
|
||||
let add_to_selection = input.keyboard.get(add_to_selection as usize);
|
||||
let toggle_add_to_selection = input.keyboard.get(add_to_selection as usize);
|
||||
|
||||
// Select the first point within the threshold (in pixels)
|
||||
if tool_data.shape_editor.select_point(input.mouse.position, SELECTION_THRESHOLD, add_to_selection, responses) {
|
||||
if let Some(mut new_selected) = tool_data
|
||||
.shape_editor
|
||||
.select_point(&document.graphene_document, input.mouse.position, SELECTION_THRESHOLD, toggle_add_to_selection, responses)
|
||||
{
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
let ignore_document = tool_data.shape_editor.shapes_to_modify.iter().map(|shape| shape.layer_path.clone()).collect::<Vec<_>>();
|
||||
let ignore_document = tool_data.shape_editor.selected_layers().clone();
|
||||
tool_data
|
||||
.snap_handler
|
||||
.start_snap(document, document.bounding_boxes(Some(&ignore_document), None, font_cache), true, true);
|
||||
|
||||
let include_handles = tool_data.shape_editor.shapes_to_modify.iter().map(|shape| shape.layer_path.as_slice()).collect::<Vec<_>>();
|
||||
tool_data.snap_handler.add_all_document_handles(document, &include_handles, &[]);
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
new_selected.extend(extension);
|
||||
|
||||
let include_handles = tool_data.shape_editor.selected_layers_ref();
|
||||
tool_data.snap_handler.add_all_document_handles(document, &include_handles, &[], &new_selected);
|
||||
|
||||
tool_data.drag_start_pos = input.mouse.position;
|
||||
Dragging
|
||||
@@ -182,7 +203,7 @@ impl Fsm for PathToolFsmState {
|
||||
.graphene_document
|
||||
.intersects_quad_root(Quad::from_box([input.mouse.position - selection_size, input.mouse.position + selection_size]), font_cache);
|
||||
if !intersection.is_empty() {
|
||||
if add_to_selection {
|
||||
if toggle_add_to_selection {
|
||||
responses.push_back(DocumentMessage::AddSelectedLayers { additional_layers: intersection }.into());
|
||||
} else {
|
||||
responses.push_back(
|
||||
@@ -194,7 +215,7 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
} else {
|
||||
// Clear the previous selection if we didn't find anything
|
||||
if !input.keyboard.get(add_to_selection as usize) {
|
||||
if !input.keyboard.get(toggle_add_to_selection as usize) {
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
}
|
||||
}
|
||||
@@ -215,7 +236,7 @@ impl Fsm for PathToolFsmState {
|
||||
tool_data.alt_debounce = alt_pressed;
|
||||
// Only on alt down
|
||||
if alt_pressed {
|
||||
tool_data.shape_editor.toggle_selected_mirror_angle();
|
||||
tool_data.shape_editor.toggle_handle_mirroring_on_selected(true, false, responses);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,12 +244,13 @@ impl Fsm for PathToolFsmState {
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
if shift_pressed != tool_data.shift_debounce {
|
||||
tool_data.shift_debounce = shift_pressed;
|
||||
tool_data.shape_editor.toggle_selected_mirror_distance();
|
||||
tool_data.shape_editor.toggle_handle_mirroring_on_selected(false, true, responses);
|
||||
}
|
||||
|
||||
// Move the selected points by the mouse position
|
||||
let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
|
||||
tool_data.shape_editor.move_selected_points(snapped_position - tool_data.drag_start_pos, true, responses);
|
||||
tool_data.shape_editor.move_selected_points(snapped_position - tool_data.drag_start_pos, snapped_position, responses);
|
||||
tool_data.drag_start_pos = snapped_position;
|
||||
Dragging
|
||||
}
|
||||
// Mouse up
|
||||
@@ -236,8 +258,22 @@ impl Fsm for PathToolFsmState {
|
||||
tool_data.snap_handler.cleanup(responses);
|
||||
Ready
|
||||
}
|
||||
// Delete key
|
||||
(_, Delete) => {
|
||||
// Delete the selected points and clean up overlays
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
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);
|
||||
}
|
||||
Ready
|
||||
}
|
||||
(_, Abort) => {
|
||||
tool_data.shape_editor.remove_overlays(responses);
|
||||
// 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);
|
||||
}
|
||||
Ready
|
||||
}
|
||||
(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::consts::CREATE_CURVE_THRESHOLD;
|
||||
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
@@ -8,15 +8,15 @@ use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::snapping::SnapHandler;
|
||||
use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::viewport_tools::vector_editor::constants::ControlPointType;
|
||||
use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor;
|
||||
use crate::viewport_tools::vector_editor::vector_shape::VectorShape;
|
||||
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::Operation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{PathEl, Point};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -45,12 +45,17 @@ pub enum PenToolMessage {
|
||||
DocumentIsDirty,
|
||||
#[remain::unsorted]
|
||||
Abort,
|
||||
#[remain::unsorted]
|
||||
SelectionChanged,
|
||||
|
||||
// Tool-specific messages
|
||||
Confirm,
|
||||
DragStart,
|
||||
DragStop,
|
||||
PointerMove,
|
||||
PointerMove {
|
||||
snap_angle: Key,
|
||||
break_handle: Key,
|
||||
},
|
||||
Undo,
|
||||
UpdateOptions(PenOptionsUpdate),
|
||||
}
|
||||
@@ -58,7 +63,8 @@ pub enum PenToolMessage {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum PenToolFsmState {
|
||||
Ready,
|
||||
Drawing,
|
||||
DraggingHandle,
|
||||
PlacingAnchor,
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
@@ -124,11 +130,9 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PenTool {
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
use PenToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(PenToolMessageDiscriminant; Undo, DragStart, DragStop, Confirm, Abort),
|
||||
Drawing => actions!(PenToolMessageDiscriminant; DragStart, DragStop, PointerMove, Confirm, Abort),
|
||||
PenToolFsmState::Ready => actions!(PenToolMessageDiscriminant; Undo, DragStart, DragStop, Confirm, Abort),
|
||||
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => actions!(PenToolMessageDiscriminant; DragStart, DragStop, PointerMove, Confirm, Abort),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +142,7 @@ impl ToolTransition for PenTool {
|
||||
SignalToMessageMap {
|
||||
document_dirty: Some(PenToolMessage::DocumentIsDirty.into()),
|
||||
tool_abort: Some(PenToolMessage::Abort.into()),
|
||||
selection_changed: None,
|
||||
selection_changed: Some(PenToolMessage::SelectionChanged.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,11 +156,8 @@ impl Default for PenToolFsmState {
|
||||
struct PenToolData {
|
||||
weight: f64,
|
||||
path: Option<Vec<LayerId>>,
|
||||
curve_shape: VectorShape,
|
||||
bez_path: Vec<PathEl>,
|
||||
overlay_renderer: OverlayRenderer,
|
||||
snap_handler: SnapHandler,
|
||||
shape_editor: ShapeEditor,
|
||||
drag_start_position: DVec2,
|
||||
}
|
||||
|
||||
impl Fsm for PenToolFsmState {
|
||||
@@ -171,112 +172,181 @@ impl Fsm for PenToolFsmState {
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use PenToolFsmState::*;
|
||||
use PenToolMessage::*;
|
||||
|
||||
let transform = document.graphene_document.root.transform;
|
||||
let transform = tool_data.path.as_ref().and_then(|path| document.graphene_document.multiply_transforms(path).ok()).unwrap_or_default();
|
||||
|
||||
if let ToolMessage::Pen(event) = event {
|
||||
match (self, event) {
|
||||
(_, DocumentIsDirty) => {
|
||||
tool_data.shape_editor.update_shapes(document, responses);
|
||||
(_, PenToolMessage::DocumentIsDirty) => {
|
||||
// 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);
|
||||
}
|
||||
self
|
||||
}
|
||||
(Ready, DragStart) => {
|
||||
(_, PenToolMessage::SelectionChanged) => {
|
||||
// Set the previously selected layers to invisible
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.layer_overlay_visibility(&document.graphene_document, layer_path.to_vec(), false, responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
|
||||
// Create a new layer and prep snap system
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]);
|
||||
let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
// Get the position and set properties
|
||||
let transform = tool_data
|
||||
.path
|
||||
.as_ref()
|
||||
.and_then(|path| document.graphene_document.multiply_transforms(&path[..path.len() - 1]).ok())
|
||||
.unwrap_or_default();
|
||||
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 {
|
||||
tool_data.bez_path = start_bez_path(start_position);
|
||||
responses.push_back(
|
||||
Operation::AddShape {
|
||||
path: layer_path.clone(),
|
||||
transform: transform.to_cols_array(),
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
insert_index: -1,
|
||||
bez_path: tool_data.bez_path.clone().into_iter().collect(),
|
||||
vector_path: Default::default(),
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
|
||||
closed: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(add_anchor(&tool_data.path, VectorAnchor::new(start_position)));
|
||||
}
|
||||
|
||||
add_to_curve(tool_data, input, transform, document, responses);
|
||||
Drawing
|
||||
PenToolFsmState::DraggingHandle
|
||||
}
|
||||
(Drawing, DragStart) => {
|
||||
tool_data.drag_start_position = input.mouse.position;
|
||||
add_to_curve(tool_data, input, transform, document, responses);
|
||||
Drawing
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
// Deselect everything (this means we are no longer dragging the handle)
|
||||
tool_data.shape_editor.deselect_all(responses);
|
||||
|
||||
// If the drag does not exceed the threshold, then replace the curve with a line
|
||||
if tool_data.drag_start_position.distance(input.mouse.position) < CREATE_CURVE_THRESHOLD {
|
||||
// Modify the second to last element (as we have an unplaced element tracing to the cursor as the last element)
|
||||
let replace_index = tool_data.bez_path.len() - 2;
|
||||
let line_from_curve = convert_curve_to_line(tool_data.bez_path[replace_index]);
|
||||
replace_path_element(tool_data, transform, replace_index, line_from_curve, responses);
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => PenToolFsmState::DraggingHandle,
|
||||
(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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reselect the last point
|
||||
if let Some(last_anchor) = tool_data.shape_editor.select_last_anchor() {
|
||||
last_anchor.select_point(ControlPointType::Anchor as usize, true, responses);
|
||||
PenToolFsmState::PlacingAnchor
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle }) => {
|
||||
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() {
|
||||
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 {
|
||||
layer_path: layer_path.clone(),
|
||||
id,
|
||||
control_type: ControlPointType::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() {
|
||||
pos = anchor.position - (pos - anchor.position);
|
||||
}
|
||||
let msg = Operation::MoveVectorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id,
|
||||
control_type: ControlPointType::InHandle,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move the newly selected points to the cursor
|
||||
let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
|
||||
tool_data.shape_editor.move_selected_points(snapped_position, false, responses);
|
||||
|
||||
Drawing
|
||||
self
|
||||
}
|
||||
(Drawing, PointerMove) => {
|
||||
// Move selected points
|
||||
let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
|
||||
tool_data.shape_editor.move_selected_points(snapped_position, false, responses);
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, .. }) => {
|
||||
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);
|
||||
|
||||
Drawing
|
||||
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()) {
|
||||
pos = compute_snapped_angle(input, snap_angle, pos, relative.position);
|
||||
}
|
||||
|
||||
for control_type in [ControlPointType::Anchor, ControlPointType::InHandle, ControlPointType::OutHandle] {
|
||||
let msg = Operation::MoveVectorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id,
|
||||
control_type,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(Drawing, Confirm) | (Drawing, Abort) => {
|
||||
// Cleanup, we are either canceling or finished drawing
|
||||
if tool_data.bez_path.len() >= 2 {
|
||||
// Remove the last segment
|
||||
remove_from_curve(tool_data);
|
||||
if let Some(layer_path) = &tool_data.path {
|
||||
responses.push_back(apply_bez_path(layer_path.clone(), tool_data.bez_path.clone(), transform));
|
||||
(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) {
|
||||
// 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 };
|
||||
responses.push_back(op.into());
|
||||
if let Some((&new_id, new_anchor)) = previous {
|
||||
id = new_id;
|
||||
anchor = new_anchor;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the out handle if in dragging handle mode
|
||||
let op = Operation::MoveVectorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id,
|
||||
control_type: ControlPointType::OutHandle,
|
||||
position: anchor.points[ControlPointType::Anchor as usize].as_ref().unwrap().position.into(),
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
}
|
||||
}
|
||||
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
} else {
|
||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||
}
|
||||
|
||||
tool_data.shape_editor.remove_overlays(responses);
|
||||
tool_data.shape_editor.clear_shapes_to_modify();
|
||||
|
||||
// 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.path = None;
|
||||
tool_data.snap_handler.cleanup(responses);
|
||||
|
||||
Ready
|
||||
PenToolFsmState::Ready
|
||||
}
|
||||
(_, Abort) => {
|
||||
tool_data.shape_editor.remove_overlays(responses);
|
||||
tool_data.shape_editor.clear_shapes_to_modify();
|
||||
Ready
|
||||
(_, 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);
|
||||
}
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
@@ -293,11 +363,29 @@ impl Fsm for PenToolFsmState {
|
||||
label: String::from("Draw Path"),
|
||||
plus: false,
|
||||
}])]),
|
||||
PenToolFsmState::Drawing => HintData(vec![
|
||||
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => HintData(vec![
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Add Handle"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Extend Path"),
|
||||
label: String::from("Add Control Point"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
mouse: None,
|
||||
label: String::from("Snap 15°"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
mouse: None,
|
||||
label: String::from("Break Handle"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
@@ -317,91 +405,53 @@ impl Fsm for PenToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add to the curve and select the second anchor of the last point and the newly added anchor point
|
||||
fn add_to_curve(tool_data: &mut PenToolData, input: &InputPreprocessorMessageHandler, transform: DAffine2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
// Refresh tool_data's representation of the path
|
||||
update_path_representation(tool_data);
|
||||
// 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
|
||||
fn compute_snapped_angle(input: &InputPreprocessorMessageHandler, key: Key, pos: DVec2, relative: DVec2) -> DVec2 {
|
||||
if input.keyboard.get(key as usize) {
|
||||
let delta = relative - pos;
|
||||
|
||||
// Setup our position params
|
||||
let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
|
||||
let position = transform.inverse().transform_point2(snapped_position);
|
||||
let length = delta.length();
|
||||
let mut angle = -delta.angle_between(DVec2::X);
|
||||
|
||||
// Add a curve to the path
|
||||
if let Some(layer_path) = &tool_data.path {
|
||||
// Push curve onto path
|
||||
let point = Point { x: position.x, y: position.y };
|
||||
tool_data.bez_path.push(PathEl::CurveTo(point, point, point));
|
||||
let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
|
||||
angle = (angle / snap_resolution).round() * snap_resolution;
|
||||
|
||||
responses.push_back(apply_bez_path(layer_path.clone(), tool_data.bez_path.clone(), transform));
|
||||
let rotated = DVec2::new(length * angle.cos(), length * angle.sin());
|
||||
relative - rotated
|
||||
} else {
|
||||
pos
|
||||
}
|
||||
}
|
||||
|
||||
// Clear previous overlays
|
||||
tool_data.shape_editor.remove_overlays(responses);
|
||||
|
||||
// Create a new `shape` from the updated `bez_path`
|
||||
let bez_path = tool_data.bez_path.clone().into_iter().collect();
|
||||
tool_data.curve_shape = VectorShape::new(layer_path.to_vec(), transform, &bez_path, false, responses);
|
||||
tool_data.shape_editor.set_shapes_to_modify(vec![tool_data.curve_shape.clone()]);
|
||||
|
||||
// Select the second to last `PathEl`'s handle
|
||||
tool_data.shape_editor.set_shape_selected(0);
|
||||
let handle_element = tool_data.shape_editor.select_nth_anchor(0, -2);
|
||||
handle_element.select_point(ControlPointType::Handle2 as usize, true, responses);
|
||||
|
||||
// Select the last `PathEl`'s anchor point
|
||||
if let Some(last_anchor) = tool_data.shape_editor.select_last_anchor() {
|
||||
last_anchor.select_point(ControlPointType::Anchor as usize, true, responses);
|
||||
/// Pushes an anchor to the current layer via an [Operation]
|
||||
fn add_anchor(layer_path: &Option<Vec<LayerId>>, anchor: VectorAnchor) -> Message {
|
||||
if let Some(layer_path) = layer_path {
|
||||
Operation::PushVectorAnchor {
|
||||
layer_path: layer_path.clone(),
|
||||
anchor,
|
||||
}
|
||||
tool_data.shape_editor.set_selected_mirror_options(true, true);
|
||||
.into()
|
||||
} else {
|
||||
Message::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace a `PathEl` with another inside of `bez_path` by index
|
||||
fn replace_path_element(tool_data: &mut PenToolData, transform: DAffine2, replace_index: usize, replacement: PathEl, responses: &mut VecDeque<Message>) {
|
||||
tool_data.bez_path[replace_index] = replacement;
|
||||
if let Some(layer_path) = &tool_data.path {
|
||||
responses.push_back(apply_bez_path(layer_path.clone(), tool_data.bez_path.clone(), transform));
|
||||
}
|
||||
/// 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())
|
||||
}
|
||||
|
||||
/// Remove a curve from the end of the `bez_path`
|
||||
fn remove_from_curve(tool_data: &mut PenToolData) {
|
||||
// Refresh tool_data's representation of the path
|
||||
update_path_representation(tool_data);
|
||||
tool_data.bez_path.pop();
|
||||
}
|
||||
type AnchorRef<'a> = (&'a u64, &'a VectorAnchor);
|
||||
|
||||
/// Create the initial moveto for the `bez_path`
|
||||
fn start_bez_path(start_position: DVec2) -> Vec<PathEl> {
|
||||
vec![PathEl::MoveTo(Point {
|
||||
x: start_position.x,
|
||||
y: start_position.y,
|
||||
})]
|
||||
}
|
||||
|
||||
/// Convert curve `PathEl` into a line `PathEl`
|
||||
fn convert_curve_to_line(curve: PathEl) -> PathEl {
|
||||
match curve {
|
||||
PathEl::CurveTo(_, _, p) => PathEl::LineTo(p),
|
||||
_ => PathEl::MoveTo(Point::ZERO),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update tool_data's version of `bez_path` to match `ShapeEditor`'s version
|
||||
fn update_path_representation(tool_data: &mut PenToolData) {
|
||||
// TODO Update ShapeEditor to provide similar functionality
|
||||
// We need to make sure we have the most up-to-date bez_path
|
||||
if !tool_data.shape_editor.shapes_to_modify.is_empty() {
|
||||
// Hacky way of saving the curve changes
|
||||
tool_data.bez_path = tool_data.shape_editor.shapes_to_modify[0].bez_path.elements().to_vec();
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply the `bez_path` to the `shape` in the viewport
|
||||
fn apply_bez_path(layer_path: Vec<LayerId>, bez_path: Vec<PathEl>, transform: DAffine2) -> Message {
|
||||
Operation::SetShapePathInViewport {
|
||||
path: layer_path,
|
||||
bez_path: bez_path.into_iter().collect(),
|
||||
transform: transform.to_cols_array(),
|
||||
}
|
||||
.into()
|
||||
/// 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| {
|
||||
(
|
||||
last,
|
||||
(vector_shape.anchors().len() > 1)
|
||||
.then(|| vector_shape.anchors().enumerate().nth(vector_shape.anchors().len() - 2))
|
||||
.flatten(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ impl Fsm for SelectToolFsmState {
|
||||
tool_data.snap_handler.start_snap(document, document.bounding_boxes(Some(&selected), None, font_cache), snap_x, snap_y);
|
||||
tool_data
|
||||
.snap_handler
|
||||
.add_all_document_handles(document, &[], &selected.iter().map(|x| x.as_slice()).collect::<Vec<_>>());
|
||||
.add_all_document_handles(document, &[], &selected.iter().map(|x| x.as_slice()).collect::<Vec<_>>(), &[]);
|
||||
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ 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::{LayerId, Operation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{BezPath, Shape};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Manages the overlay used by the select tool for outlining selected shapes and when hovering over a non selected shape.
|
||||
@@ -33,13 +33,14 @@ impl PathOutline {
|
||||
// Get layer data
|
||||
let document_layer = document.graphene_document.layer(&document_layer_path).ok()?;
|
||||
|
||||
// TODO Purge this area of BezPath and Kurbo
|
||||
// Get the bezpath from the shape or text
|
||||
let path = match &document_layer.data {
|
||||
LayerDataType::Shape(shape) => Some(shape.path.clone()),
|
||||
LayerDataType::Text(text) => Some(text.to_bez_path_nonmut(font_cache)),
|
||||
let vector_path = 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(|bounds| kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y).to_path(0.)),
|
||||
.map(|[p1, p2]| VectorShape::new_rect(p1, p2)),
|
||||
}?;
|
||||
|
||||
// Generate a new overlay layer if necessary
|
||||
@@ -47,11 +48,12 @@ impl PathOutline {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
let overlay_path = vec![generate_uuid()];
|
||||
let operation = Operation::AddOverlayShape {
|
||||
let operation = Operation::AddShape {
|
||||
path: overlay_path.clone(),
|
||||
bez_path: BezPath::new(),
|
||||
vector_path: Default::default(),
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, PATH_OUTLINE_WEIGHT)), Fill::None),
|
||||
closed: false,
|
||||
insert_index: -1,
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
};
|
||||
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
@@ -61,10 +63,7 @@ impl PathOutline {
|
||||
};
|
||||
|
||||
// Update the shape bezpath
|
||||
let operation = Operation::SetShapePath {
|
||||
path: overlay.clone(),
|
||||
bez_path: path,
|
||||
};
|
||||
let operation = Operation::SetShapePath { path: overlay.clone(), vector_path };
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
// Update the transform to match the document
|
||||
|
||||
@@ -21,7 +21,7 @@ impl Resize {
|
||||
/// Starts a resize, assigning the snap targets and snapping the starting position.
|
||||
pub fn start(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, mouse_position: DVec2, font_cache: &FontCache) {
|
||||
self.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
|
||||
self.snap_handler.add_all_document_handles(document, &[], &[]);
|
||||
self.snap_handler.add_all_document_handles(document, &[], &[], &[]);
|
||||
self.drag_start = self.snap_handler.snap_position(responses, document, mouse_position);
|
||||
}
|
||||
|
||||
|
||||
@@ -140,10 +140,11 @@ impl SelectedEdges {
|
||||
pub fn add_bounding_box(responses: &mut Vec<Message>) -> Vec<LayerId> {
|
||||
let path = vec![generate_uuid()];
|
||||
|
||||
let operation = Operation::AddOverlayRect {
|
||||
let operation = Operation::AddRect {
|
||||
path: path.clone(),
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Fill::None),
|
||||
insert_index: -1,
|
||||
};
|
||||
responses.push(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
@@ -158,10 +159,11 @@ fn add_transform_handles(responses: &mut Vec<Message>) -> [Vec<LayerId>; 8] {
|
||||
for item in &mut transform_handle_paths {
|
||||
let current_path = vec![generate_uuid()];
|
||||
|
||||
let operation = Operation::AddOverlayRect {
|
||||
let operation = Operation::AddRect {
|
||||
path: current_path.clone(),
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Fill::solid(Color::WHITE)),
|
||||
insert_index: -1,
|
||||
};
|
||||
responses.push(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ impl Fsm for SplineToolFsmState {
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
|
||||
tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
|
||||
tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]);
|
||||
let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
let pos = transform.inverse().transform_point2(snapped_position);
|
||||
@@ -216,7 +216,6 @@ impl Fsm for SplineToolFsmState {
|
||||
}
|
||||
(Drawing, Confirm) | (Drawing, Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_spline(tool_data, global_tool_data, false));
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
|
||||
@@ -14,7 +14,6 @@ use graphene::layers::text_layer::FontCache;
|
||||
use graphene::Operation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::Shape;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -217,10 +216,11 @@ fn resize_overlays(overlays: &mut Vec<Vec<LayerId>>, responses: &mut VecDeque<Me
|
||||
let path = vec![generate_uuid()];
|
||||
overlays.push(path.clone());
|
||||
|
||||
let operation = Operation::AddOverlayRect {
|
||||
let operation = Operation::AddRect {
|
||||
path,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Fill::None),
|
||||
insert_index: -1,
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
}
|
||||
@@ -423,19 +423,14 @@ impl Fsm for TextToolFsmState {
|
||||
(Editing, UpdateBounds { new_text }) => {
|
||||
resize_overlays(&mut tool_data.overlays, responses, 1);
|
||||
let text = document.graphene_document.layer(&tool_data.path).unwrap().as_text().unwrap();
|
||||
let mut path = text.bounding_box(&new_text, text.load_face(font_cache)).to_path(0.1);
|
||||
let quad = text.bounding_box(&new_text, text.load_face(font_cache));
|
||||
|
||||
fn glam_to_kurbo(transform: DAffine2) -> kurbo::Affine {
|
||||
kurbo::Affine::new(transform.to_cols_array())
|
||||
}
|
||||
|
||||
path.apply_affine(glam_to_kurbo(document.graphene_document.multiply_transforms(&tool_data.path).unwrap()));
|
||||
|
||||
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
|
||||
let transformed_quad = document.graphene_document.multiply_transforms(&tool_data.path).unwrap() * quad;
|
||||
let bounds = transformed_quad.bounding_box();
|
||||
|
||||
let operation = Operation::SetLayerTransformInViewport {
|
||||
path: tool_data.overlays[0].clone(),
|
||||
transform: transform_from_box(DVec2::new(x0, y0), DVec2::new(x1, y1)),
|
||||
transform: transform_from_box(bounds[0], bounds[1]),
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user