mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
Improve layer positioning in graph upon reordering; improve history system; add selection history (#1945)
* Improve layer positioning * Collapse space when deleting * Improve moving layers in layer panel * Improved transactions * Selection history * Code review * Select previous selection when aborting * Fix crash and artboard select * Add mouse forward/back button selection history bindings Code review * Menu buttons * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
432385343e
commit
9adc640f19
29 changed files with 806 additions and 443 deletions
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
@ -4,7 +4,6 @@
|
|||
// Rust
|
||||
"rust-lang.rust-analyzer",
|
||||
"tamasfe.even-better-toml",
|
||||
"serayuzgur.crates",
|
||||
// Web
|
||||
"dbaeumer.vscode-eslint",
|
||||
"svelte.svelte-vscode",
|
||||
|
@ -16,6 +15,6 @@
|
|||
"mhutchie.git-graph",
|
||||
"waderyan.gitblame",
|
||||
"qezhu.gitlink",
|
||||
"wmaurer.change-case",
|
||||
"wmaurer.change-case"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -37,31 +37,31 @@ pub fn input_mappings() -> Mapping {
|
|||
//
|
||||
// NavigationMessage
|
||||
entry!(PointerMove; refresh_keys=[Control], action_dispatch=NavigationMessage::PointerMove { snap: Control }),
|
||||
entry!(KeyUp(Lmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
||||
entry!(KeyUp(Mmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
||||
entry!(KeyUp(Rmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
||||
entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: true }),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
||||
entry!(KeyUp(MouseMiddle); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
||||
entry!(KeyUp(MouseRight); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: true }),
|
||||
entry!(KeyDown(Escape); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: true }),
|
||||
entry!(KeyDown(Lmb); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: Lmb }),
|
||||
entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: Mmb }),
|
||||
entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: Rmb }),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: MouseLeft }),
|
||||
entry!(KeyDown(MouseMiddle); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: MouseMiddle }),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: MouseRight }),
|
||||
//
|
||||
// ===============
|
||||
// NORMAL PRIORITY
|
||||
// ===============
|
||||
//
|
||||
// Hack to prevent LMB + CTRL (OPTION) + Z combo (this effectively blocks you from making a double undo with AbortTransaction)
|
||||
entry!(KeyDown(KeyZ); modifiers=[Accel, Lmb], action_dispatch=DocumentMessage::Noop),
|
||||
// Hack to prevent Left Click + Accel + Z combo (this effectively blocks you from making a double undo with AbortTransaction)
|
||||
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
|
||||
// NodeGraphMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: false}),
|
||||
entry!(KeyDown(Lmb); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: false, alt_click: false, right_click: false}),
|
||||
entry!(KeyDown(Lmb); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: true, alt_click: false, right_click: false}),
|
||||
entry!(KeyDown(Lmb); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: true, alt_click: false, right_click: false}),
|
||||
entry!(KeyDown(Lmb); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: true, right_click: false}),
|
||||
entry!(KeyDown(Rmb); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: true}),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: false}),
|
||||
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: false, alt_click: false, right_click: false}),
|
||||
entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: true, alt_click: false, right_click: false}),
|
||||
entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: true, alt_click: false, right_click: false}),
|
||||
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: true, right_click: false}),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: true}),
|
||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=NodeGraphMessage::EnterNestedNetwork),
|
||||
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove {shift: Shift}),
|
||||
entry!(KeyUp(Lmb); action_dispatch=NodeGraphMessage::PointerUp),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=NodeGraphMessage::PointerUp),
|
||||
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
|
||||
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
|
||||
entry!(KeyDown(Delete); action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: true }),
|
||||
|
@ -82,8 +82,8 @@ pub fn input_mappings() -> Mapping {
|
|||
//
|
||||
// TransformLayerMessage
|
||||
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||
entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||
entry!(KeyDown(Rmb); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
||||
entry!(KeyDown(Escape); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
||||
entry!(KeyDown(KeyX); action_dispatch=TransformLayerMessage::ConstrainX),
|
||||
entry!(KeyDown(KeyY); action_dispatch=TransformLayerMessage::ConstrainY),
|
||||
|
@ -95,17 +95,17 @@ pub fn input_mappings() -> Mapping {
|
|||
//
|
||||
// SelectToolMessage
|
||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove(SelectToolPointerKeys { axis_align: Shift, snap_angle: Control, center: Alt, duplicate: Alt })),
|
||||
entry!(KeyDown(Lmb); action_dispatch=SelectToolMessage::DragStart { add_to_selection: Shift, select_deepest: Accel }),
|
||||
entry!(KeyUp(Lmb); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Shift }),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { add_to_selection: Shift, select_deepest: Accel }),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Shift }),
|
||||
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
|
||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=SelectToolMessage::EditLayer),
|
||||
entry!(KeyDown(Rmb); action_dispatch=SelectToolMessage::Abort),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=SelectToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=SelectToolMessage::Abort),
|
||||
//
|
||||
// ArtboardToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=ArtboardToolMessage::PointerDown),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=ArtboardToolMessage::PointerDown),
|
||||
entry!(PointerMove; refresh_keys=[Shift, Alt], action_dispatch=ArtboardToolMessage::PointerMove { constrain_axis_or_aspect: Shift, center: Alt }),
|
||||
entry!(KeyUp(Lmb); action_dispatch=ArtboardToolMessage::PointerUp),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=ArtboardToolMessage::PointerUp),
|
||||
entry!(KeyDown(Delete); action_dispatch=ArtboardToolMessage::DeleteSelected),
|
||||
entry!(KeyDown(Backspace); action_dispatch=ArtboardToolMessage::DeleteSelected),
|
||||
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
|
||||
|
@ -132,72 +132,72 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(ArrowRight); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
||||
entry!(KeyDown(Rmb); action_dispatch=ArtboardToolMessage::Abort),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=ArtboardToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=ArtboardToolMessage::Abort),
|
||||
//
|
||||
// NavigateToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin),
|
||||
entry!(KeyDown(Lmb); modifiers=[Alt], action_dispatch=NavigateToolMessage::TiltCanvasBegin),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=NavigateToolMessage::ZoomCanvasBegin),
|
||||
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NavigateToolMessage::TiltCanvasBegin),
|
||||
entry!(PointerMove; refresh_keys=[Control], action_dispatch=NavigateToolMessage::PointerMove { snap: Control }),
|
||||
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::PointerUp { zoom_in: true }),
|
||||
entry!(KeyUp(Lmb); modifiers=[Shift], action_dispatch=NavigateToolMessage::PointerUp { zoom_in: false }),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=NavigateToolMessage::PointerUp { zoom_in: true }),
|
||||
entry!(KeyUp(MouseLeft); modifiers=[Shift], action_dispatch=NavigateToolMessage::PointerUp { zoom_in: false }),
|
||||
//
|
||||
// EyedropperToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::SamplePrimaryColorBegin),
|
||||
entry!(KeyDown(Lmb); modifiers=[Shift], action_dispatch=EyedropperToolMessage::SampleSecondaryColorBegin),
|
||||
entry!(KeyUp(Lmb); action_dispatch=EyedropperToolMessage::SamplePrimaryColorEnd),
|
||||
entry!(KeyUp(Lmb); modifiers=[Shift], action_dispatch=EyedropperToolMessage::SampleSecondaryColorEnd),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=EyedropperToolMessage::SamplePrimaryColorBegin),
|
||||
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=EyedropperToolMessage::SampleSecondaryColorBegin),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=EyedropperToolMessage::SamplePrimaryColorEnd),
|
||||
entry!(KeyUp(MouseLeft); modifiers=[Shift], action_dispatch=EyedropperToolMessage::SampleSecondaryColorEnd),
|
||||
entry!(PointerMove; action_dispatch=EyedropperToolMessage::PointerMove),
|
||||
entry!(KeyDown(Rmb); action_dispatch=EyedropperToolMessage::Abort),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=EyedropperToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=EyedropperToolMessage::Abort),
|
||||
//
|
||||
// TextToolMessage
|
||||
entry!(KeyUp(Lmb); action_dispatch=TextToolMessage::Interact),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=TextToolMessage::Interact),
|
||||
entry!(KeyDown(Escape); action_dispatch=TextToolMessage::Abort),
|
||||
entry!(KeyDown(Enter); modifiers=[Accel], action_dispatch=TextToolMessage::CommitText),
|
||||
//
|
||||
// GradientToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=GradientToolMessage::PointerDown),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=GradientToolMessage::PointerDown),
|
||||
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: Shift }),
|
||||
entry!(KeyUp(Lmb); action_dispatch=GradientToolMessage::PointerUp),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=GradientToolMessage::PointerUp),
|
||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=GradientToolMessage::InsertStop),
|
||||
entry!(KeyDown(Delete); action_dispatch=GradientToolMessage::DeleteStop),
|
||||
entry!(KeyDown(Backspace); action_dispatch=GradientToolMessage::DeleteStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=GradientToolMessage::Abort),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=GradientToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=GradientToolMessage::Abort),
|
||||
//
|
||||
// RectangleToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=RectangleToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=RectangleToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=RectangleToolMessage::Abort),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=RectangleToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=RectangleToolMessage::DragStop),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=RectangleToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=RectangleToolMessage::Abort),
|
||||
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=RectangleToolMessage::PointerMove { center: Alt, lock_ratio: Shift }),
|
||||
//
|
||||
// ImaginateToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=ImaginateToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=ImaginateToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=ImaginateToolMessage::Abort),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=ImaginateToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=ImaginateToolMessage::DragStop),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=ImaginateToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=ImaginateToolMessage::Abort),
|
||||
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=ImaginateToolMessage::Resize { center: Alt, lock_ratio: Shift }),
|
||||
//
|
||||
// EllipseToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=EllipseToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=EllipseToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=EllipseToolMessage::Abort),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=EllipseToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=EllipseToolMessage::DragStop),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=EllipseToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=EllipseToolMessage::Abort),
|
||||
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=EllipseToolMessage::PointerMove { center: Alt, lock_ratio: Shift }),
|
||||
//
|
||||
// PolygonToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=PolygonToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=PolygonToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=PolygonToolMessage::Abort),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PolygonToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=PolygonToolMessage::DragStop),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=PolygonToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=PolygonToolMessage::Abort),
|
||||
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=PolygonToolMessage::PointerMove { center: Alt, lock_ratio: Shift }),
|
||||
//
|
||||
// LineToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=LineToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=LineToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=LineToolMessage::Abort),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=LineToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=LineToolMessage::DragStop),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=LineToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=LineToolMessage::Abort),
|
||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=LineToolMessage::PointerMove { center: Alt, lock_angle: Control, snap_angle: Shift }),
|
||||
//
|
||||
|
@ -206,8 +206,8 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
|
||||
entry!(KeyDown(Delete); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||
entry!(KeyDown(Backspace); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||
entry!(KeyDown(Lmb); action_dispatch=PathToolMessage::MouseDown { ctrl: Control, shift: Shift }),
|
||||
entry!(KeyDown(Rmb); action_dispatch=PathToolMessage::RightClick),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { ctrl: Control, shift: Shift }),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
|
||||
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
|
||||
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
|
||||
entry!(KeyDown(KeyR); action_dispatch=PathToolMessage::GRS { key: KeyR }),
|
||||
|
@ -217,7 +217,7 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop { equidistant: Shift }),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { equidistant: Shift }),
|
||||
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { add_to_selection: Shift }),
|
||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::FlipSmoothSharp),
|
||||
entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
||||
|
@ -247,41 +247,41 @@ pub fn input_mappings() -> Mapping {
|
|||
//
|
||||
// PenToolMessage
|
||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control}),
|
||||
entry!(KeyDown(Lmb); action_dispatch=PenToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=PenToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=PenToolMessage::Confirm),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PenToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=PenToolMessage::DragStop),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=PenToolMessage::Confirm),
|
||||
entry!(KeyDown(Escape); action_dispatch=PenToolMessage::Confirm),
|
||||
entry!(KeyDown(Enter); action_dispatch=PenToolMessage::Confirm),
|
||||
//
|
||||
// FreehandToolMessage
|
||||
entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),
|
||||
entry!(KeyDown(Lmb); action_dispatch=FreehandToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=FreehandToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=FreehandToolMessage::Abort),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=FreehandToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=FreehandToolMessage::DragStop),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=FreehandToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=FreehandToolMessage::Abort),
|
||||
//
|
||||
// SplineToolMessage
|
||||
entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove),
|
||||
entry!(KeyDown(Lmb); action_dispatch=SplineToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=SplineToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=SplineToolMessage::Confirm),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=SplineToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=SplineToolMessage::DragStop),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=SplineToolMessage::Confirm),
|
||||
entry!(KeyDown(Escape); action_dispatch=SplineToolMessage::Confirm),
|
||||
entry!(KeyDown(Enter); action_dispatch=SplineToolMessage::Confirm),
|
||||
//
|
||||
// FillToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::FillPrimaryColor),
|
||||
entry!(KeyDown(Lmb); modifiers=[Shift], action_dispatch=FillToolMessage::FillSecondaryColor),
|
||||
entry!(KeyUp(Lmb); action_dispatch=FillToolMessage::PointerUp),
|
||||
entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::Abort),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=FillToolMessage::FillPrimaryColor),
|
||||
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=FillToolMessage::FillSecondaryColor),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=FillToolMessage::PointerUp),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=FillToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=FillToolMessage::Abort),
|
||||
//
|
||||
// BrushToolMessage
|
||||
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
|
||||
entry!(KeyDown(Lmb); action_dispatch=BrushToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=BrushToolMessage::DragStop),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=BrushToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=BrushToolMessage::DragStop),
|
||||
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD))),
|
||||
entry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD))),
|
||||
entry!(KeyDown(Rmb); action_dispatch=BrushToolMessage::Abort),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=BrushToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=BrushToolMessage::Abort),
|
||||
//
|
||||
// ToolMessage
|
||||
|
@ -323,6 +323,10 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers),
|
||||
entry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
|
||||
entry!(KeyDown(KeyN); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder),
|
||||
entry!(KeyDown(BracketLeft); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepBack),
|
||||
entry!(KeyDown(BracketRight); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepForward),
|
||||
entry!(KeyDown(MouseBack); action_dispatch=DocumentMessage::SelectionStepBack),
|
||||
entry!(KeyDown(MouseForward); action_dispatch=DocumentMessage::SelectionStepForward),
|
||||
entry!(KeyDown(Digit0); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
|
||||
entry!(KeyDown(Digit1); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasTo100Percent),
|
||||
entry!(KeyDown(Digit2); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasTo200Percent),
|
||||
|
@ -371,11 +375,11 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(Digit9); action_dispatch=TransformLayerMessage::TypeDigit { digit: 9 }),
|
||||
//
|
||||
// NavigationMessage
|
||||
entry!(KeyDown(Mmb); modifiers=[Alt], action_dispatch=NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }),
|
||||
entry!(KeyDown(Mmb); modifiers=[Shift], action_dispatch=NavigationMessage::BeginCanvasZoom),
|
||||
entry!(KeyDown(Lmb); modifiers=[Shift, Space], action_dispatch=NavigationMessage::BeginCanvasZoom),
|
||||
entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::BeginCanvasPan),
|
||||
entry!(KeyDown(Lmb); modifiers=[Space], action_dispatch=NavigationMessage::BeginCanvasPan),
|
||||
entry!(KeyDown(MouseMiddle); modifiers=[Alt], action_dispatch=NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }),
|
||||
entry!(KeyDown(MouseMiddle); modifiers=[Shift], action_dispatch=NavigationMessage::BeginCanvasZoom),
|
||||
entry!(KeyDown(MouseLeft); modifiers=[Shift, Space], action_dispatch=NavigationMessage::BeginCanvasZoom),
|
||||
entry!(KeyDown(MouseMiddle); action_dispatch=NavigationMessage::BeginCanvasPan),
|
||||
entry!(KeyDown(MouseLeft); modifiers=[Space], action_dispatch=NavigationMessage::BeginCanvasPan),
|
||||
entry!(KeyDown(NumpadAdd); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),
|
||||
entry!(KeyDown(Equal); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),
|
||||
entry!(KeyDown(Minus); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }),
|
||||
|
|
|
@ -202,9 +202,11 @@ pub enum Key {
|
|||
Command,
|
||||
/// "Ctrl" on Windows/Linux, "Cmd" on Mac
|
||||
Accel,
|
||||
Lmb,
|
||||
Rmb,
|
||||
Mmb,
|
||||
MouseLeft,
|
||||
MouseRight,
|
||||
MouseMiddle,
|
||||
MouseBack,
|
||||
MouseForward,
|
||||
|
||||
// This has to be the last element in the enum
|
||||
NumKeys,
|
||||
|
|
|
@ -90,7 +90,7 @@ impl MouseState {
|
|||
pub fn finish_transaction(&self, drag_start: DVec2, responses: &mut VecDeque<Message>) {
|
||||
match drag_start.distance(self.position) <= DRAG_THRESHOLD {
|
||||
true => responses.add(DocumentMessage::AbortTransaction),
|
||||
false => responses.add(DocumentMessage::CommitTransaction),
|
||||
false => responses.add(DocumentMessage::EndTransaction),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +144,8 @@ pub enum MouseButton {
|
|||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Back,
|
||||
Forward,
|
||||
}
|
||||
|
||||
pub const NUMBER_OF_MOUSE_BUTTONS: usize = 3;
|
||||
pub const NUMBER_OF_MOUSE_BUTTONS: usize = 5; // Should be the number of variants in MouseButton
|
||||
|
|
|
@ -45,6 +45,8 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for
|
|||
MouseKeys::LEFT => MouseButton::Left,
|
||||
MouseKeys::RIGHT => MouseButton::Right,
|
||||
MouseKeys::MIDDLE => MouseButton::Middle,
|
||||
MouseKeys::BACK => MouseButton::Back,
|
||||
MouseKeys::FORWARD => MouseButton::Forward,
|
||||
_ => unimplemented!(),
|
||||
}));
|
||||
}
|
||||
|
@ -115,7 +117,15 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for
|
|||
|
||||
impl InputPreprocessorMessageHandler {
|
||||
fn translate_mouse_event(&mut self, mut new_state: MouseState, allow_first_button_down: bool, responses: &mut VecDeque<Message>) {
|
||||
for (bit_flag, key) in [(MouseKeys::LEFT, Key::Lmb), (MouseKeys::RIGHT, Key::Rmb), (MouseKeys::MIDDLE, Key::Mmb)] {
|
||||
let click_mappings = [
|
||||
(MouseKeys::LEFT, Key::MouseLeft),
|
||||
(MouseKeys::RIGHT, Key::MouseRight),
|
||||
(MouseKeys::MIDDLE, Key::MouseMiddle),
|
||||
(MouseKeys::BACK, Key::MouseBack),
|
||||
(MouseKeys::FORWARD, Key::MouseForward),
|
||||
];
|
||||
|
||||
for (bit_flag, key) in click_mappings {
|
||||
// Calculate the intersection between the two key states
|
||||
let old_down = self.mouse.mouse_keys & bit_flag == bit_flag;
|
||||
let new_down = new_state.mouse_keys & bit_flag == bit_flag;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use super::utility_types::misc::SnappingState;
|
||||
use super::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
|
@ -32,17 +31,12 @@ pub enum DocumentMessage {
|
|||
PropertiesPanel(PropertiesPanelMessage),
|
||||
|
||||
// Messages
|
||||
AbortTransaction,
|
||||
AlignSelectedLayers {
|
||||
axis: AlignAxis,
|
||||
aggregate: AlignAggregate,
|
||||
},
|
||||
BackupDocument {
|
||||
network_interface: NodeNetworkInterface,
|
||||
},
|
||||
ClearArtboards,
|
||||
ClearLayersPanel,
|
||||
CommitTransaction,
|
||||
InsertBooleanOperation {
|
||||
operation: graphene_core::vector::misc::BooleanOperation,
|
||||
},
|
||||
|
@ -151,6 +145,10 @@ pub enum DocumentMessage {
|
|||
view_mode: ViewMode,
|
||||
},
|
||||
StartTransaction,
|
||||
EndTransaction,
|
||||
CommitTransaction,
|
||||
AbortTransaction,
|
||||
AddTransaction,
|
||||
ToggleLayerExpansion {
|
||||
id: NodeId,
|
||||
},
|
||||
|
@ -158,12 +156,13 @@ pub enum DocumentMessage {
|
|||
ToggleOverlaysVisibility,
|
||||
ToggleSnapping,
|
||||
Undo,
|
||||
UndoFinished,
|
||||
UngroupSelectedLayers,
|
||||
UngroupLayer {
|
||||
layer: LayerNodeIdentifier,
|
||||
},
|
||||
PTZUpdate,
|
||||
SelectionStepBack,
|
||||
SelectionStepForward,
|
||||
ZoomCanvasTo100Percent,
|
||||
ZoomCanvasTo200Percent,
|
||||
ZoomCanvasToFitAll,
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::node_graph::utility_types::Transform;
|
|||
use super::utility_types::clipboards::Clipboard;
|
||||
use super::utility_types::error::EditorError;
|
||||
use super::utility_types::misc::{SnappingOptions, SnappingState, GET_SNAP_BOX_FUNCTIONS, GET_SNAP_GEOMETRY_FUNCTIONS};
|
||||
use super::utility_types::network_interface::NodeNetworkInterface;
|
||||
use super::utility_types::network_interface::{NodeNetworkInterface, TransactionStatus};
|
||||
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
|
||||
use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH};
|
||||
use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
|
||||
|
@ -144,9 +144,6 @@ pub struct DocumentMessageHandler {
|
|||
/// Hash of the document snapshot that was most recently auto-saved to the IndexedDB storage that will reopen when the editor is reloaded.
|
||||
#[serde(skip)]
|
||||
auto_saved_hash: Option<u64>,
|
||||
/// Disallow aborting transactions whilst undoing to avoid #559.
|
||||
#[serde(skip)]
|
||||
undo_in_progress: bool,
|
||||
/// The ID of the layer at the start of a range selection in the Layers panel.
|
||||
/// If the user clicks or Ctrl-clicks one layer, it becomes the start of the range selection and then Shift-clicking another layer selects all layers between the start and end.
|
||||
#[serde(skip)]
|
||||
|
@ -186,7 +183,6 @@ impl Default for DocumentMessageHandler {
|
|||
document_redo_history: VecDeque::new(),
|
||||
saved_hash: None,
|
||||
auto_saved_hash: None,
|
||||
undo_in_progress: false,
|
||||
layer_range_selection_reference: None,
|
||||
}
|
||||
}
|
||||
|
@ -259,17 +255,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
let mut graph_operation_message_handler = GraphOperationMessageHandler {};
|
||||
graph_operation_message_handler.process_message(message, responses, data);
|
||||
}
|
||||
|
||||
// Messages
|
||||
DocumentMessage::AbortTransaction => {
|
||||
if !self.undo_in_progress {
|
||||
self.undo(ipp, responses);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
}
|
||||
DocumentMessage::AlignSelectedLayers { axis, aggregate } => {
|
||||
self.backup(responses);
|
||||
|
||||
let axis = match axis {
|
||||
AlignAxis::X => DVec2::X,
|
||||
AlignAxis::Y => DVec2::Y,
|
||||
|
@ -283,6 +269,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
AlignAggregate::Max => combined_box[1],
|
||||
AlignAggregate::Center => (combined_box[0] + combined_box[1]) / 2.,
|
||||
};
|
||||
|
||||
let mut added_transaction = false;
|
||||
for layer in self.network_interface.selected_nodes(&[]).unwrap().selected_unlocked_layers(&self.network_interface) {
|
||||
let Some(bbox) = self.metadata().bounding_box_viewport(layer) else {
|
||||
continue;
|
||||
|
@ -293,6 +281,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
_ => (bbox[0] + bbox[1]) / 2.,
|
||||
};
|
||||
let translation = (aggregated - center) * axis;
|
||||
if !added_transaction {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
added_transaction = true;
|
||||
}
|
||||
responses.add(GraphOperationMessage::TransformChange {
|
||||
layer,
|
||||
transform: DAffine2::from_translation(translation),
|
||||
|
@ -301,9 +293,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
});
|
||||
}
|
||||
}
|
||||
DocumentMessage::BackupDocument { network_interface } => self.backup_with_document(network_interface, responses),
|
||||
DocumentMessage::ClearArtboards => {
|
||||
self.backup(responses);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(GraphOperationMessage::ClearArtboards);
|
||||
}
|
||||
DocumentMessage::ClearLayersPanel => {
|
||||
|
@ -317,9 +308,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
layout_target: LayoutTarget::LayersPanelOptions,
|
||||
});
|
||||
}
|
||||
DocumentMessage::CommitTransaction => (),
|
||||
DocumentMessage::InsertBooleanOperation { operation } => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
let Some(parent) = self.network_interface.deepest_common_ancestor(&[], false) else {
|
||||
// Cancel grouping layers across different artboards
|
||||
|
@ -353,7 +343,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
|
||||
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), self.network_interface.selected_nodes(&[]).unwrap(), parent);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(GraphOperationMessage::NewCustomLayer {
|
||||
id,
|
||||
nodes: Vec::new(),
|
||||
|
@ -386,7 +376,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
let calculated_insert_index =
|
||||
DocumentMessageHandler::get_calculated_insert_index(self.network_interface.document_metadata(), self.network_interface.selected_nodes(&[]).unwrap(), parent);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
|
||||
responses.add(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: Clipboard::Internal,
|
||||
|
@ -430,7 +420,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
DocumentMessage::FlipSelectedLayers { flip_axis } => {
|
||||
self.backup(responses);
|
||||
let scale = match flip_axis {
|
||||
FlipAxis::X => DVec2::new(-1., 1.),
|
||||
FlipAxis::Y => DVec2::new(1., -1.),
|
||||
|
@ -438,7 +427,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() {
|
||||
let center = (max + min) / 2.;
|
||||
let bbox_trans = DAffine2::from_translation(-center);
|
||||
let mut added_transaction = false;
|
||||
for layer in self.network_interface.selected_nodes(&[]).unwrap().selected_unlocked_layers(&self.network_interface) {
|
||||
if !added_transaction {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
added_transaction = true;
|
||||
}
|
||||
responses.add(GraphOperationMessage::TransformChange {
|
||||
layer,
|
||||
transform: DAffine2::from_scale(scale),
|
||||
|
@ -481,7 +475,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
DocumentMessage::GroupSelectedLayers => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
let Some(parent) = self.network_interface.deepest_common_ancestor(&self.selection_network_path, false) else {
|
||||
// Cancel grouping layers across different artboards
|
||||
|
@ -525,7 +519,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
let random_bits = generate_uuid();
|
||||
let random_value = ((random_bits >> 11) as f64).copysign(f64::from_bits(random_bits & (1 << 63)));
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
// Set a random seed input
|
||||
responses.add(NodeGraphMessage::SetInputValue {
|
||||
node_id: *imaginate_node.last().unwrap(),
|
||||
|
@ -546,7 +540,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
parent,
|
||||
insert_index,
|
||||
} => {
|
||||
self.backup(responses);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(GraphOperationMessage::NewSvg {
|
||||
id,
|
||||
svg,
|
||||
|
@ -554,6 +548,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
parent,
|
||||
insert_index,
|
||||
});
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
}
|
||||
DocumentMessage::MoveSelectedLayersTo { parent, insert_index } => {
|
||||
if !self.selection_network_path.is_empty() {
|
||||
|
@ -561,8 +556,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
return;
|
||||
}
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Disallow trying to insert into self.
|
||||
if self
|
||||
.network_interface
|
||||
|
@ -625,6 +618,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
for (layer_index, (layer_to_move, insert_offset)) in layers_to_move_with_insert_offset.into_iter().enumerate() {
|
||||
let calculated_insert_index = insert_index + layer_index - insert_offset;
|
||||
responses.add(NodeGraphMessage::MoveLayerToStack {
|
||||
|
@ -660,7 +654,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
resize,
|
||||
resize_opposite_corner,
|
||||
} => {
|
||||
self.backup(responses);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
let opposite_corner = ipp.keyboard.key(resize_opposite_corner);
|
||||
let delta = DVec2::new(delta_x, delta_y);
|
||||
|
@ -740,7 +734,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
|
||||
let transform = center_in_viewport_layerspace * fit_image_size;
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
let image_frame = ImageFrame { image, ..Default::default() };
|
||||
|
||||
|
@ -770,6 +764,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });
|
||||
}
|
||||
DocumentMessage::Redo => {
|
||||
if self.network_interface.transaction_status() != TransactionStatus::Finished {
|
||||
return;
|
||||
}
|
||||
responses.add(SelectToolMessage::Abort);
|
||||
responses.add(DocumentMessage::DocumentHistoryForward);
|
||||
responses.add(ToolMessage::Redo);
|
||||
|
@ -951,10 +948,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
}
|
||||
}
|
||||
DocumentMessage::SetOpacityForSelectedLayers { opacity } => {
|
||||
self.backup(responses);
|
||||
let opacity = opacity.clamp(0., 1.);
|
||||
|
||||
let mut added_transaction = false;
|
||||
for layer in self.network_interface.selected_nodes(&[]).unwrap().selected_layers_except_artboards(&self.network_interface) {
|
||||
if !added_transaction {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
added_transaction = true;
|
||||
}
|
||||
responses.add(GraphOperationMessage::OpacitySet { layer, opacity });
|
||||
}
|
||||
}
|
||||
|
@ -975,7 +975,47 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
self.view_mode = view_mode;
|
||||
responses.add_front(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
DocumentMessage::StartTransaction => self.backup(responses),
|
||||
// Note: A transaction should never be started in a scope that mutates the network interface, since it will only be run after that scope ends.
|
||||
DocumentMessage::StartTransaction => {
|
||||
self.network_interface.start_transaction();
|
||||
let network_interface_clone = self.network_interface.clone();
|
||||
self.document_undo_history.push_back(network_interface_clone);
|
||||
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
|
||||
self.document_undo_history.pop_front();
|
||||
}
|
||||
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
}
|
||||
// Commits the transaction if the network was mutated since the transaction started, otherwise it aborts the transaction
|
||||
DocumentMessage::EndTransaction => match self.network_interface.transaction_status() {
|
||||
TransactionStatus::Started => {
|
||||
responses.add_front(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
TransactionStatus::Modified => {
|
||||
responses.add_front(DocumentMessage::CommitTransaction);
|
||||
}
|
||||
TransactionStatus::Finished => {}
|
||||
},
|
||||
DocumentMessage::CommitTransaction => {
|
||||
if self.network_interface.transaction_status() == TransactionStatus::Finished {
|
||||
return;
|
||||
}
|
||||
self.network_interface.finish_transaction();
|
||||
self.document_redo_history.clear();
|
||||
}
|
||||
DocumentMessage::AbortTransaction => {
|
||||
if self.network_interface.transaction_status() == TransactionStatus::Finished {
|
||||
return;
|
||||
}
|
||||
self.network_interface.finish_transaction();
|
||||
self.undo(ipp, responses);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
DocumentMessage::AddTransaction => {
|
||||
// Reverse order since they are added to the front
|
||||
responses.add_front(DocumentMessage::CommitTransaction);
|
||||
responses.add_front(DocumentMessage::StartTransaction);
|
||||
}
|
||||
DocumentMessage::ToggleLayerExpansion { id } => {
|
||||
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
|
||||
if self.collapsed.0.contains(&layer) {
|
||||
|
@ -1000,22 +1040,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
DocumentMessage::Undo => {
|
||||
self.undo_in_progress = true;
|
||||
if self.network_interface.transaction_status() != TransactionStatus::Finished {
|
||||
return;
|
||||
}
|
||||
responses.add(ToolMessage::PreUndo);
|
||||
responses.add(DocumentMessage::DocumentHistoryBackward);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(DocumentMessage::UndoFinished);
|
||||
responses.add(ToolMessage::Undo);
|
||||
}
|
||||
DocumentMessage::UndoFinished => {
|
||||
self.undo_in_progress = false;
|
||||
}
|
||||
DocumentMessage::UngroupSelectedLayers => {
|
||||
if !self.selection_network_path.is_empty() {
|
||||
log::error!("Ungrouping selected layers is only supported for the Document Network");
|
||||
return;
|
||||
}
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
let folder_paths = self.network_interface.folders_sorted_by_most_nested(&self.selection_network_path);
|
||||
for folder in folder_paths {
|
||||
|
@ -1092,6 +1130,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
})
|
||||
}
|
||||
}
|
||||
DocumentMessage::SelectionStepBack => {
|
||||
self.network_interface.selection_step_back(&self.selection_network_path);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
}
|
||||
DocumentMessage::SelectionStepForward => {
|
||||
self.network_interface.selection_step_forward(&self.selection_network_path);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
}
|
||||
DocumentMessage::ZoomCanvasTo100Percent => {
|
||||
responses.add_front(NavigationMessage::CanvasZoomSet { zoom_factor: 1. });
|
||||
}
|
||||
|
@ -1128,6 +1174,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
ToggleOverlaysVisibility,
|
||||
ToggleSnapping,
|
||||
Undo,
|
||||
SelectionStepForward,
|
||||
SelectionStepBack,
|
||||
ZoomCanvasTo100Percent,
|
||||
ZoomCanvasTo200Percent,
|
||||
ZoomCanvasToFitAll,
|
||||
|
@ -1357,33 +1405,6 @@ impl DocumentMessageHandler {
|
|||
structure_section.as_slice().into()
|
||||
}
|
||||
|
||||
/// Places a document into the history system
|
||||
fn backup_with_document(&mut self, network_interface: NodeNetworkInterface, responses: &mut VecDeque<Message>) {
|
||||
self.document_redo_history.clear();
|
||||
self.document_undo_history.push_back(network_interface);
|
||||
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
|
||||
self.document_undo_history.pop_front();
|
||||
}
|
||||
|
||||
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
}
|
||||
|
||||
/// Copies the entire document into the history system
|
||||
pub fn backup(&mut self, responses: &mut VecDeque<Message>) {
|
||||
let network_interface_clone = self.network_interface.clone();
|
||||
|
||||
self.backup_with_document(network_interface_clone, responses);
|
||||
}
|
||||
|
||||
// TODO: Is this now redundant?
|
||||
/// Push a message backing up the document in its current state
|
||||
pub fn backup_nonmut(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.add(DocumentMessage::BackupDocument {
|
||||
network_interface: self.network_interface.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn undo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let Some(previous_network) = self.undo(ipp, responses) else { return };
|
||||
|
||||
|
@ -1405,6 +1426,9 @@ impl DocumentMessageHandler {
|
|||
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
network_interface.set_document_to_viewport_transform(transform);
|
||||
|
||||
// Ensure document structure is loaded so that updating the selected nodes has the correct metadata
|
||||
network_interface.load_structure();
|
||||
|
||||
let previous_network = std::mem::replace(&mut self.network_interface, network_interface);
|
||||
|
||||
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
|
||||
|
@ -1483,7 +1507,7 @@ impl DocumentMessageHandler {
|
|||
.unwrap_or_else(|| self.network_interface.all_artboards().iter().next().copied().unwrap_or(LayerNodeIdentifier::ROOT_PARENT))
|
||||
}
|
||||
|
||||
pub fn get_calculated_insert_index(metadata: &DocumentMetadata, selected_nodes: &SelectedNodes, parent: LayerNodeIdentifier) -> usize {
|
||||
pub fn get_calculated_insert_index(metadata: &DocumentMetadata, selected_nodes: SelectedNodes, parent: LayerNodeIdentifier) -> usize {
|
||||
parent
|
||||
.children(metadata)
|
||||
.enumerate()
|
||||
|
@ -1778,7 +1802,8 @@ impl DocumentMessageHandler {
|
|||
|
||||
pub fn update_layers_panel_options_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||
// Get an iterator over the selected layers (excluding artboards which don't have an opacity or blend mode).
|
||||
let selected_layers_except_artboards = self.network_interface.selected_nodes(&[]).unwrap().selected_layers_except_artboards(&self.network_interface);
|
||||
let selected_nodes = self.network_interface.selected_nodes(&[]).unwrap();
|
||||
let selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&self.network_interface);
|
||||
|
||||
// Look up the current opacity and blend mode of the selected layers (if any), and split the iterator into the first tuple and the rest.
|
||||
let mut opacity_and_blend_mode = selected_layers_except_artboards.map(|layer| {
|
||||
|
@ -1823,7 +1848,7 @@ impl DocumentMessageHandler {
|
|||
MenuListEntry::new(format!("{blend_mode:?}"))
|
||||
.label(blend_mode.to_string())
|
||||
.on_update(move |_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode }.into())
|
||||
.on_commit(|_| DocumentMessage::StartTransaction.into())
|
||||
.on_commit(|_| DocumentMessage::AddTransaction.into())
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
|
@ -1916,9 +1941,8 @@ impl DocumentMessageHandler {
|
|||
}
|
||||
|
||||
pub fn selected_layers_reorder(&mut self, relative_index_offset: isize, responses: &mut VecDeque<Message>) {
|
||||
self.backup(responses);
|
||||
|
||||
let mut selected_layers = self.network_interface.selected_nodes(&[]).unwrap().selected_layers(self.metadata());
|
||||
let selected_nodes = self.network_interface.selected_nodes(&[]).unwrap();
|
||||
let mut selected_layers = selected_nodes.selected_layers(self.metadata());
|
||||
|
||||
let first_or_last_selected_layer = match relative_index_offset.signum() {
|
||||
-1 => selected_layers.next(),
|
||||
|
|
|
@ -217,7 +217,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||
let tree = match usvg::Tree::from_str(&svg, &usvg::Options::default()) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
responses.add(DocumentMessage::DocumentHistoryBackward);
|
||||
responses.add(DialogMessage::DisplayDialogError {
|
||||
title: "SVG parsing failed".to_string(),
|
||||
description: e.to_string(),
|
||||
|
|
|
@ -294,7 +294,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
|||
NavigationMessage::EndCanvasPTZWithClick { commit_key } => {
|
||||
self.finish_operation_with_click = false;
|
||||
|
||||
let abort_transform = commit_key == Key::Rmb;
|
||||
let abort_transform = commit_key == Key::MouseRight;
|
||||
responses.add(NavigationMessage::EndCanvasPTZ { abort_transform });
|
||||
}
|
||||
NavigationMessage::FitViewportToBounds {
|
||||
|
|
|
@ -42,15 +42,17 @@ pub struct NodeGraphMessageHandler {
|
|||
begin_dragging: bool,
|
||||
/// Stored in node graph coordinates
|
||||
box_selection_start: Option<DVec2>,
|
||||
/// Restore the selection before box selection if it is aborted
|
||||
selection_before_pointer_down: Vec<NodeId>,
|
||||
/// If the grip icon is held during a drag, then shift without pushing other nodes
|
||||
shift_without_push: bool,
|
||||
disconnecting: Option<InputConnector>,
|
||||
initial_disconnecting: bool,
|
||||
/// Node to select on pointer up if multiple nodes are selected and they were not dragged.
|
||||
select_if_not_dragged: Option<NodeId>,
|
||||
/// The start of the dragged line that cannot be moved, stored in node graph coordinates
|
||||
/// The start of the dragged line (cannot be moved), stored in node graph coordinates
|
||||
pub wire_in_progress_from_connector: Option<DVec2>,
|
||||
/// The end point of the dragged line that can be moved, stored in node graph coordinates
|
||||
/// The end point of the dragged line (cannot be moved), stored in node graph coordinates
|
||||
pub wire_in_progress_to_connector: Option<DVec2>,
|
||||
/// State for the context menu popups.
|
||||
pub context_menu: Option<ContextMenuInformation>,
|
||||
|
@ -58,6 +60,8 @@ pub struct NodeGraphMessageHandler {
|
|||
pub deselect_on_pointer_up: Option<usize>,
|
||||
/// Adds the auto panning functionality to the node graph when dragging a node or selection box to the edge of the viewport.
|
||||
auto_panning: AutoPanning,
|
||||
/// The node to preview on mouse up if alt-clicked
|
||||
preview_on_mouse_up: Option<NodeId>,
|
||||
}
|
||||
|
||||
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
|
||||
|
@ -143,7 +147,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
let node_template = document_node_type.default_node_template();
|
||||
self.context_menu = None;
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(NodeGraphMessage::InsertNode {
|
||||
node_id,
|
||||
node_template: node_template.clone(),
|
||||
|
@ -206,7 +210,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
log::error!("Could not get selected nodes in DeleteSelectedNodes");
|
||||
return;
|
||||
};
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(),
|
||||
delete_children,
|
||||
|
@ -227,7 +231,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
let nodes = network_interface.copy_nodes(©_ids, selection_network_path).collect::<Vec<_>>();
|
||||
|
||||
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect::<HashMap<_, _>>();
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids: new_ids.clone() });
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||
nodes: new_ids.values().cloned().collect(),
|
||||
|
@ -260,8 +264,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
return;
|
||||
};
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let Some(mut input) = node.inputs.get(input_index).cloned() else {
|
||||
log::error!("Could not find input {input_index} in NodeGraphMessage::ExposeInput");
|
||||
return;
|
||||
|
@ -274,6 +276,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
return;
|
||||
}
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, input_index),
|
||||
input,
|
||||
|
@ -306,7 +310,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
return;
|
||||
}
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
let new_ids: HashMap<_, _> = data.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
|
||||
responses.add(NodeGraphMessage::AddNodes {
|
||||
|
@ -333,12 +337,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
|
||||
let node_graph_point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);
|
||||
|
||||
// Toggle visibility of clicked node and return
|
||||
if let Some(clicked_visibility) = network_interface.visibility_from_click(click, selection_network_path) {
|
||||
responses.add(NodeGraphMessage::ToggleVisibility { node_id: clicked_visibility });
|
||||
return;
|
||||
}
|
||||
|
||||
if network_interface.grip_from_click(click, selection_network_path).is_some() {
|
||||
self.shift_without_push = true;
|
||||
}
|
||||
|
@ -350,9 +348,30 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
|
||||
// Create the add node popup on right click, then exit
|
||||
if right_click {
|
||||
// Abort dragging a node
|
||||
if self.drag_start.is_some() {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
self.drag_start = None;
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||
nodes: self.selection_before_pointer_down.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Abort a box selection
|
||||
if self.box_selection_start.is_some() {
|
||||
self.box_selection_start = None;
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||
nodes: self.selection_before_pointer_down.clone(),
|
||||
});
|
||||
responses.add(FrontendMessage::UpdateBox { box_selection: None });
|
||||
return;
|
||||
}
|
||||
// Abort dragging a wire
|
||||
if self.wire_in_progress_from_connector.is_some() {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -390,6 +409,11 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
return;
|
||||
}
|
||||
|
||||
self.selection_before_pointer_down = network_interface
|
||||
.selected_nodes(selection_network_path)
|
||||
.map(|selected_nodes| selected_nodes.selected_nodes().cloned().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
// If the user is clicking on the create nodes list or context menu, break here
|
||||
if let Some(context_menu) = &self.context_menu {
|
||||
let context_menu_viewport = network_metadata
|
||||
|
@ -416,7 +440,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
|
||||
// Since the user is clicking elsewhere in the graph, ensure the add nodes list is closed
|
||||
if !right_click && self.context_menu.is_some() {
|
||||
if self.context_menu.is_some() {
|
||||
self.context_menu = None;
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
|
@ -426,16 +450,22 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||
}
|
||||
|
||||
// Toggle visibility of clicked node and return
|
||||
if let Some(clicked_visibility) = network_interface.visibility_from_click(click, selection_network_path) {
|
||||
responses.add(NodeGraphMessage::ToggleVisibility { node_id: clicked_visibility });
|
||||
return;
|
||||
}
|
||||
|
||||
// Alt-click sets the clicked node as previewed
|
||||
if alt_click {
|
||||
if let Some(clicked_node) = clicked_id {
|
||||
responses.add(NodeGraphMessage::TogglePreview { node_id: clicked_node });
|
||||
return;
|
||||
self.preview_on_mouse_up = Some(clicked_node);
|
||||
}
|
||||
}
|
||||
|
||||
// Begin moving an existing wire
|
||||
if let Some(clicked_input) = &clicked_input {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
self.initial_disconnecting = true;
|
||||
self.disconnecting = Some(clicked_input.clone());
|
||||
|
||||
|
@ -451,6 +481,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
|
||||
// Begin creating a new wire
|
||||
if let Some(clicked_output) = clicked_output {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
self.initial_disconnecting = false;
|
||||
// Disconnect vertical output wire from an already-connected layer
|
||||
if let OutputConnector::Node { node_id, .. } = clicked_output {
|
||||
|
@ -513,6 +544,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
if modified_selected {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: updated_selected })
|
||||
}
|
||||
// Start the transaction after setting the node, since when the transactions ends it aborts any changes after this
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -558,7 +591,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
self.wire_in_progress_to_connector = Some(point);
|
||||
// Disconnect if the wire was previously connected to an input
|
||||
if let Some(disconnecting) = &self.disconnecting {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let mut disconnect_root_node = false;
|
||||
if let Previewing::Yes { root_node_to_restore } = network_interface.previewing(selection_network_path) {
|
||||
if root_node_to_restore.is_some() && *disconnecting == InputConnector::Export(0) {
|
||||
|
@ -615,8 +647,28 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
} else if let Some(drag_start) = &mut self.drag_start {
|
||||
if self.begin_dragging {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
self.begin_dragging = false;
|
||||
if ipp.keyboard.get(crate::messages::tool::tool_messages::tool_prelude::Key::Alt as usize) {
|
||||
responses.add(NodeGraphMessage::DuplicateSelectedNodes);
|
||||
// Duplicating sets a 2x2 offset, so shift the nodes back to the original position
|
||||
responses.add(NodeGraphMessage::ShiftSelectedNodes {
|
||||
direction: Direction::Up,
|
||||
rubber_band: false,
|
||||
});
|
||||
responses.add(NodeGraphMessage::ShiftSelectedNodes {
|
||||
direction: Direction::Up,
|
||||
rubber_band: false,
|
||||
});
|
||||
responses.add(NodeGraphMessage::ShiftSelectedNodes {
|
||||
direction: Direction::Left,
|
||||
rubber_band: false,
|
||||
});
|
||||
responses.add(NodeGraphMessage::ShiftSelectedNodes {
|
||||
direction: Direction::Left,
|
||||
rubber_band: false,
|
||||
});
|
||||
self.preview_on_mouse_up = None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut graph_delta = IVec2::new(((point.x - drag_start.start_x) / 24.).round() as i32, ((point.y - drag_start.start_y) / 24.).round() as i32);
|
||||
|
@ -684,6 +736,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
return;
|
||||
};
|
||||
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
|
||||
if let Some(preview_node) = self.preview_on_mouse_up {
|
||||
responses.add(NodeGraphMessage::TogglePreview { node_id: preview_node });
|
||||
self.preview_on_mouse_up = None;
|
||||
}
|
||||
if let Some(node_to_deselect) = self.deselect_on_pointer_up {
|
||||
let mut new_selected_nodes = selected_nodes.selected_nodes_ref().clone();
|
||||
new_selected_nodes.remove(node_to_deselect);
|
||||
|
@ -896,8 +954,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
.enumerate()
|
||||
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||
{
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
responses.add(NodeGraphMessage::InsertNodeBetween {
|
||||
node_id: selected_node_id,
|
||||
input_connector: overlapping_wire.wire_end.clone(),
|
||||
|
@ -1066,7 +1122,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
log::error!("Could not get selected nodes in NodeGraphMessage::ToggleSelectedAsLayersOrNodes");
|
||||
return;
|
||||
};
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
for node_id in selected_nodes.selected_nodes() {
|
||||
responses.add(NodeGraphMessage::SetToNodeOrLayer {
|
||||
node_id: *node_id,
|
||||
|
@ -1096,6 +1152,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
NodeGraphMessage::SetDisplayName { node_id, alias } => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(NodeGraphMessage::SetDisplayNameImpl { node_id, alias });
|
||||
// Does not add a history step if the name was not changed
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
|
@ -1104,7 +1162,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
network_interface.set_display_name(&node_id, alias, selection_network_path);
|
||||
}
|
||||
NodeGraphMessage::TogglePreview { node_id } => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(NodeGraphMessage::TogglePreviewImpl { node_id });
|
||||
responses.add(NodeGraphMessage::UpdateActionButtons);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
@ -1122,7 +1180,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
// If any of the selected layers are locked, show them all. Otherwise, hide them all.
|
||||
let locked = !node_ids.iter().all(|node_id| network_interface.is_locked(node_id, selection_network_path));
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
for node_id in &node_ids {
|
||||
responses.add(NodeGraphMessage::SetLocked { node_id: *node_id, locked });
|
||||
|
@ -1138,7 +1196,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
|
||||
let locked = !node_metadata.persistent_metadata.locked;
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(NodeGraphMessage::SetLocked { node_id, locked });
|
||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] })
|
||||
}
|
||||
|
@ -1159,7 +1217,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
// If any of the selected nodes are hidden, show them all. Otherwise, hide them all.
|
||||
let visible = !node_ids.iter().all(|node_id| network.nodes.get(node_id).is_some_and(|node| node.visible));
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
for node_id in &node_ids {
|
||||
responses.add(NodeGraphMessage::SetVisibility { node_id: *node_id, visible });
|
||||
}
|
||||
|
@ -1177,7 +1235,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
|
||||
let visible = !node.visible;
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
|
||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] });
|
||||
}
|
||||
|
@ -1935,6 +1993,7 @@ impl Default for NodeGraphMessageHandler {
|
|||
begin_dragging: false,
|
||||
shift_without_push: false,
|
||||
box_selection_start: None,
|
||||
selection_before_pointer_down: Vec::new(),
|
||||
disconnecting: None,
|
||||
initial_disconnecting: false,
|
||||
select_if_not_dragged: None,
|
||||
|
@ -1943,6 +2002,7 @@ impl Default for NodeGraphMessageHandler {
|
|||
context_menu: None,
|
||||
deselect_on_pointer_up: None,
|
||||
auto_panning: Default::default(),
|
||||
preview_on_mouse_up: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ fn update_value<T>(value: impl Fn(&T) -> TaggedValue + 'static + Send + Sync, no
|
|||
}
|
||||
|
||||
fn commit_value<T>(_: &T) -> Message {
|
||||
DocumentMessage::StartTransaction.into()
|
||||
DocumentMessage::AddTransaction.into()
|
||||
}
|
||||
|
||||
fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType, exposed: bool) -> WidgetHolder {
|
||||
|
|
|
@ -13,7 +13,7 @@ use graphene_std::vector::{PointId, VectorModificationType};
|
|||
use interpreted_executor::{dynamic_executor::ResolvedDocumentNodeTypes, node_registry::NODE_REGISTRY};
|
||||
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
|
||||
/// All network modifications should be done through this API, so the fields cannot be public. However, all fields within this struct can be public since it it not possible to have a public mutable reference.
|
||||
|
@ -31,6 +31,9 @@ pub struct NodeNetworkInterface {
|
|||
/// All input/output types based on the compiled network.
|
||||
#[serde(skip)]
|
||||
pub resolved_types: ResolvedDocumentNodeTypes,
|
||||
/// Disallow aborting transactions whilst undoing to avoid #559.
|
||||
#[serde(skip)]
|
||||
transaction_status: TransactionStatus,
|
||||
}
|
||||
|
||||
impl Clone for NodeNetworkInterface {
|
||||
|
@ -40,6 +43,7 @@ impl Clone for NodeNetworkInterface {
|
|||
network_metadata: self.network_metadata.clone(),
|
||||
document_metadata: Default::default(),
|
||||
resolved_types: Default::default(),
|
||||
transaction_status: TransactionStatus::Finished,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,13 +82,26 @@ impl NodeNetworkInterface {
|
|||
&self.document_metadata
|
||||
}
|
||||
|
||||
pub fn transaction_status(&self) -> TransactionStatus {
|
||||
self.transaction_status
|
||||
}
|
||||
|
||||
/// Get the selected nodes for the network at the network_path
|
||||
pub fn selected_nodes(&self, network_path: &[NodeId]) -> Option<&SelectedNodes> {
|
||||
pub fn selected_nodes(&self, network_path: &[NodeId]) -> Option<SelectedNodes> {
|
||||
let Some(network_metadata) = self.network_metadata(network_path) else {
|
||||
log::error!("Could not get nested network_metadata in selected_nodes");
|
||||
return None;
|
||||
};
|
||||
Some(&network_metadata.transient_metadata.selected_nodes)
|
||||
|
||||
Some(
|
||||
network_metadata
|
||||
.persistent_metadata
|
||||
.selection_undo_history
|
||||
.back()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.filtered_selected_nodes(network_metadata.persistent_metadata.node_metadata.keys().cloned().collect()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the network which the encapsulating node of the currently viewed network is part of. Will always be None in the document network.
|
||||
|
@ -323,7 +340,6 @@ impl NodeNetworkInterface {
|
|||
}
|
||||
|
||||
// Ensure a chain node has a selected downstream layer, and set absolute nodes to a chain if there is a downstream layer
|
||||
|
||||
let downstream_layer = self.downstream_layer(node_id, network_path);
|
||||
if downstream_layer.map_or(true, |downstream_layer| new_ids.keys().all(|key| *key != downstream_layer.to_node())) {
|
||||
let Some(position) = self.position(node_id, network_path) else {
|
||||
|
@ -373,7 +389,7 @@ impl NodeNetworkInterface {
|
|||
}
|
||||
|
||||
/// Create a node template from an existing node.
|
||||
pub fn create_node_template(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<NodeTemplate> {
|
||||
pub fn create_node_template(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<NodeTemplate> {
|
||||
let Some(network) = self.network(network_path) else {
|
||||
log::error!("Could not get network in create_node_template");
|
||||
return None;
|
||||
|
@ -421,13 +437,13 @@ impl NodeNetworkInterface {
|
|||
|
||||
pub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> {
|
||||
let Some(network) = self.network(network_path) else {
|
||||
log::error!("Could not get network in input");
|
||||
log::error!("Could not get network in input_from_connector");
|
||||
return None;
|
||||
};
|
||||
match input_connector {
|
||||
InputConnector::Node { node_id, input_index } => {
|
||||
let Some(node) = network.nodes.get(node_id) else {
|
||||
log::error!("Could not get node {node_id} in input");
|
||||
log::error!("Could not get node {node_id} in input_from_connector");
|
||||
return None;
|
||||
};
|
||||
node.inputs.get(*input_index)
|
||||
|
@ -681,11 +697,7 @@ impl NodeNetworkInterface {
|
|||
let input_type = self.input_type(&InputConnector::node(encapsulating_node_id, *import_index), &encapsulating_path);
|
||||
let data_type = FrontendGraphDataType::with_type(&input_type);
|
||||
|
||||
let import_name = if import_name.is_empty() {
|
||||
TaggedValue::from_type(&input_type).ty().to_string()
|
||||
} else {
|
||||
import_name
|
||||
};
|
||||
let import_name = if import_name.is_empty() { input_type.clone().nested_type().to_string() } else { import_name };
|
||||
|
||||
let connected_to = self
|
||||
.outward_wires(network_path)
|
||||
|
@ -773,7 +785,7 @@ impl NodeNetworkInterface {
|
|||
} else {
|
||||
input_type
|
||||
.clone()
|
||||
.map(|input_type| TaggedValue::from_type(&input_type).ty().to_string())
|
||||
.map(|input_type| input_type.nested_type().to_string())
|
||||
.unwrap_or(format!("Export {}", export_index + 1))
|
||||
};
|
||||
|
||||
|
@ -867,8 +879,9 @@ impl NodeNetworkInterface {
|
|||
{
|
||||
let number_of_outputs = self.number_of_outputs(&upstream_node_from_input, network_path);
|
||||
|
||||
// A node is a sole dependent if all outputs are sole dependents
|
||||
// A node is a sole dependent if all outputs are sole dependents, and there are no dead ends
|
||||
let mut all_outputs_are_sole_dependents = true;
|
||||
let mut dead_ends = 0;
|
||||
|
||||
for output_index in 0..number_of_outputs {
|
||||
let downstream_connections = {
|
||||
|
@ -899,25 +912,31 @@ impl NodeNetworkInterface {
|
|||
log::error!("Could not get outward wires in upstream_nodes_below_layer");
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut has_downstream_connections = false;
|
||||
for output_index in 0..number_of_outputs {
|
||||
let Some(downstream_connections) = outward_wires.get(&OutputConnector::node(*downstream_node_id, output_index)) else {
|
||||
log::error!("Could not get outward wires in upstream_nodes_below_layer");
|
||||
continue;
|
||||
};
|
||||
if !downstream_connections.is_empty() {
|
||||
has_downstream_connections = true;
|
||||
}
|
||||
stack.extend(downstream_connections.clone());
|
||||
}
|
||||
if !has_downstream_connections {
|
||||
dead_ends += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
InputConnector::Export(_) => current_output_is_sole_dependent = false,
|
||||
}
|
||||
}
|
||||
if !current_output_is_sole_dependent {
|
||||
if !current_output_is_sole_dependent || dead_ends != 0 {
|
||||
all_outputs_are_sole_dependents = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if all_outputs_are_sole_dependents {
|
||||
if all_outputs_are_sole_dependents && dead_ends == 0 {
|
||||
sole_dependents.insert(upstream_node_from_input);
|
||||
} else {
|
||||
upstream_chain_can_be_added = false;
|
||||
|
@ -1391,6 +1410,7 @@ impl NodeNetworkInterface {
|
|||
network_metadata,
|
||||
document_metadata: DocumentMetadata::default(),
|
||||
resolved_types: ResolvedDocumentNodeTypes::default(),
|
||||
transaction_status: TransactionStatus::Finished,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1480,6 +1500,20 @@ impl NodeNetworkInterface {
|
|||
// Public mutable getters for data that involves transient network metadata
|
||||
// Mutable methods never recalculate the transient metadata, they only unload it. Loading metadata should only be done by the getter.
|
||||
impl NodeNetworkInterface {
|
||||
pub fn start_transaction(&mut self) {
|
||||
self.transaction_status = TransactionStatus::Started;
|
||||
}
|
||||
|
||||
pub fn transaction_modified(&mut self) {
|
||||
if self.transaction_status == TransactionStatus::Started {
|
||||
self.transaction_status = TransactionStatus::Modified;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish_transaction(&mut self) {
|
||||
self.transaction_status = TransactionStatus::Finished;
|
||||
}
|
||||
|
||||
/// Mutably get the selected nodes for the network at the network_path. Every time they are mutated, the transient metadata for the top of the stack gets unloaded.
|
||||
pub fn selected_nodes_mut(&mut self, network_path: &[NodeId]) -> Option<&mut SelectedNodes> {
|
||||
self.unload_stack_dependents(network_path);
|
||||
|
@ -1487,7 +1521,38 @@ impl NodeNetworkInterface {
|
|||
log::error!("Could not get nested network_metadata in selected_nodes");
|
||||
return None;
|
||||
};
|
||||
Some(&mut network_metadata.transient_metadata.selected_nodes)
|
||||
|
||||
let last_selection_state = network_metadata.persistent_metadata.selection_undo_history.back().cloned().unwrap_or_default();
|
||||
|
||||
network_metadata.persistent_metadata.selection_undo_history.push_back(last_selection_state);
|
||||
network_metadata.persistent_metadata.selection_redo_history.clear();
|
||||
|
||||
if network_metadata.persistent_metadata.selection_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
|
||||
network_metadata.persistent_metadata.selection_undo_history.pop_front();
|
||||
}
|
||||
network_metadata.persistent_metadata.selection_undo_history.back_mut()
|
||||
}
|
||||
|
||||
pub fn selection_step_back(&mut self, network_path: &[NodeId]) {
|
||||
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
||||
log::error!("Could not get nested network_metadata in selection_step_back");
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(selection_state) = network_metadata.persistent_metadata.selection_undo_history.pop_back() {
|
||||
network_metadata.persistent_metadata.selection_redo_history.push_front(selection_state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selection_step_forward(&mut self, network_path: &[NodeId]) {
|
||||
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
||||
log::error!("Could not get nested network_metadata in selection_step_forward");
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(selection_state) = network_metadata.persistent_metadata.selection_redo_history.pop_front() {
|
||||
network_metadata.persistent_metadata.selection_undo_history.push_back(selection_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn stack_dependents(&mut self, network_path: &[NodeId]) -> Option<&HashMap<NodeId, LayerOwner>> {
|
||||
|
@ -1616,6 +1681,7 @@ impl NodeNetworkInterface {
|
|||
break;
|
||||
}
|
||||
if !sole_dependents.contains(¤t_node) {
|
||||
let mut has_outward_wire = false;
|
||||
for output_index in 0..self.number_of_outputs(¤t_node, network_path) {
|
||||
let Some(outward_wires) = self.outward_wires(network_path) else {
|
||||
log::error!("Cannot load outward wires in load_stack_dependents");
|
||||
|
@ -1626,12 +1692,16 @@ impl NodeNetworkInterface {
|
|||
continue;
|
||||
};
|
||||
for downstream_input in outward_wires {
|
||||
has_outward_wire = true;
|
||||
match downstream_input {
|
||||
InputConnector::Node { node_id, .. } => stack.push(*node_id),
|
||||
InputConnector::Export(_) => is_sole_dependent = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
if !has_outward_wire {
|
||||
is_sole_dependent = false;
|
||||
}
|
||||
}
|
||||
if !is_sole_dependent {
|
||||
break;
|
||||
|
@ -2384,20 +2454,6 @@ impl NodeNetworkInterface {
|
|||
document_metadata.document_to_viewport = transform;
|
||||
}
|
||||
|
||||
pub fn vector_modify(&mut self, node_id: &NodeId, modification_type: VectorModificationType) {
|
||||
let Some(node) = self.network_mut(&[]).unwrap().nodes.get_mut(node_id) else {
|
||||
log::error!("Could not get node in vector_modification");
|
||||
return;
|
||||
};
|
||||
|
||||
let mut value = node.inputs.get_mut(1).and_then(|input| input.as_value_mut());
|
||||
let Some(TaggedValue::VectorModification(ref mut modification)) = value.as_deref_mut() else {
|
||||
panic!("Path node does not have modification input");
|
||||
};
|
||||
|
||||
modification.modify(&modification_type);
|
||||
}
|
||||
|
||||
pub fn is_eligible_to_be_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
let input_count = self.number_of_inputs(node_id, network_path);
|
||||
let output_count = self.number_of_outputs(node_id, network_path);
|
||||
|
@ -2830,6 +2886,22 @@ impl NodeNetworkInterface {
|
|||
self.unload_import_export_ports(network_path);
|
||||
}
|
||||
|
||||
pub fn vector_modify(&mut self, node_id: &NodeId, modification_type: VectorModificationType) {
|
||||
let Some(node) = self.network_mut(&[]).unwrap().nodes.get_mut(node_id) else {
|
||||
log::error!("Could not get node in vector_modification");
|
||||
return;
|
||||
};
|
||||
{
|
||||
let mut value = node.inputs.get_mut(1).and_then(|input| input.as_value_mut());
|
||||
let Some(TaggedValue::VectorModification(ref mut modification)) = value.as_deref_mut() else {
|
||||
panic!("Path node does not have modification input");
|
||||
};
|
||||
|
||||
modification.modify(&modification_type);
|
||||
}
|
||||
self.transaction_modified();
|
||||
}
|
||||
|
||||
/// Inserts a new export at insert index. If the insert index is -1 it is inserted at the end. The output_name is used by the encapsulating node.
|
||||
pub fn add_export(&mut self, default_value: TaggedValue, insert_index: isize, output_name: String, network_path: &[NodeId]) {
|
||||
// Set the parent node (if it exists) to be a non layer if it is no longer eligible to be a layer
|
||||
|
@ -2851,6 +2923,8 @@ impl NodeNetworkInterface {
|
|||
network.exports.insert(insert_index as usize, input);
|
||||
}
|
||||
|
||||
self.transaction_modified();
|
||||
|
||||
// There will not be an encapsulating node if the network is the document network
|
||||
if let Some(encapsulating_node_metadata) = self.encapsulating_node_metadata_mut(network_path) {
|
||||
if insert_index == -1 {
|
||||
|
@ -2904,6 +2978,8 @@ impl NodeNetworkInterface {
|
|||
node.inputs.insert(insert_index as usize, input);
|
||||
}
|
||||
|
||||
self.transaction_modified();
|
||||
|
||||
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
|
||||
log::error!("Could not get node_metadata in insert_input");
|
||||
return;
|
||||
|
@ -3023,6 +3099,18 @@ impl NodeNetworkInterface {
|
|||
}
|
||||
};
|
||||
|
||||
if old_input == new_input {
|
||||
return;
|
||||
};
|
||||
|
||||
// Ensure the network is not cyclic
|
||||
if !network.is_acyclic() {
|
||||
self.set_input(input_connector, old_input, network_path);
|
||||
return;
|
||||
}
|
||||
|
||||
self.transaction_modified();
|
||||
|
||||
// Ensure layer is toggled to non layer if it is no longer eligible to be a layer
|
||||
if let InputConnector::Node { node_id, .. } = &input_connector {
|
||||
if !self.is_eligible_to_be_layer(node_id, network_path) && self.is_layer(node_id, network_path) {
|
||||
|
@ -3231,7 +3319,9 @@ impl NodeNetworkInterface {
|
|||
log::error!("Network not found in insert_node");
|
||||
return;
|
||||
};
|
||||
|
||||
network.nodes.insert(node_id, node_template.document_node);
|
||||
self.transaction_modified();
|
||||
|
||||
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
||||
log::error!("Network not found in insert_node");
|
||||
|
@ -3262,7 +3352,9 @@ impl NodeNetworkInterface {
|
|||
log::error!("Network not found in insert_node");
|
||||
return;
|
||||
};
|
||||
|
||||
network.nodes.insert(node_id, node_template.document_node);
|
||||
self.transaction_modified();
|
||||
|
||||
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
||||
log::error!("Network not found in insert_node");
|
||||
|
@ -3348,23 +3440,18 @@ impl NodeNetworkInterface {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Disconnect all inputs of the node to be deleted
|
||||
let Some(network) = self.network(network_path) else {
|
||||
log::error!("Could not get nested network in delete_nodes");
|
||||
continue;
|
||||
};
|
||||
let Some(number_of_inputs) = network.nodes.get(delete_node_id).map(|node| node.inputs.len()) else {
|
||||
log::error!("Could not get number of inputs for node {delete_node_id} when removing references");
|
||||
continue;
|
||||
};
|
||||
for input_index in 0..number_of_inputs {
|
||||
for input_index in 0..self.number_of_inputs(delete_node_id, network_path) {
|
||||
self.disconnect_input(&InputConnector::node(*delete_node_id, input_index), network_path);
|
||||
}
|
||||
|
||||
let Some(network) = self.network_mut(network_path) else {
|
||||
log::error!("Could not get nested network in delete_nodes");
|
||||
continue;
|
||||
};
|
||||
// TODO: Ensure node to delete is fully disconnected from the network
|
||||
network.nodes.remove(delete_node_id);
|
||||
self.transaction_modified();
|
||||
|
||||
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
||||
log::error!("Could not get nested network_metadata in delete_nodes");
|
||||
continue;
|
||||
|
@ -3384,8 +3471,8 @@ impl NodeNetworkInterface {
|
|||
selected_nodes.retain_selected_nodes(|node_id| !nodes_to_delete.contains(node_id));
|
||||
}
|
||||
|
||||
/// Removes all references to the node with the given id from the network, and reconnects the input to the node below (or the next layer below if the node to be deleted is layer) if `reconnect` is true.
|
||||
pub fn remove_references_from_network(&mut self, deleting_node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
/// Removes all references to the node with the given id from the network, and reconnects the input to the node below.
|
||||
pub fn remove_references_from_network(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
// TODO: Add more logic to support retaining preview when removing references. Since there are so many edge cases/possible crashes, for now the preview is ended.
|
||||
self.stop_previewing(network_path);
|
||||
|
||||
|
@ -3394,62 +3481,74 @@ impl NodeNetworkInterface {
|
|||
return false;
|
||||
};
|
||||
|
||||
let mut reconnect_to_input: Option<NodeInput> = None;
|
||||
|
||||
// Check whether the being-deleted node's first (primary) input is a node
|
||||
if let Some(node) = network.nodes.get(deleting_node_id) {
|
||||
// Reconnect to the upstream node. If the layer or first upstream layer node if the deleting node is a layer
|
||||
if self.is_layer(deleting_node_id, network_path) {
|
||||
if let Some(upstream_layer_id) = self
|
||||
.upstream_flow_back_from_nodes(vec![*deleting_node_id], network_path, FlowType::PrimaryFlow)
|
||||
.skip(1) // Skip the node to delete
|
||||
.find(|node_id| self.is_layer(node_id, network_path))
|
||||
{
|
||||
reconnect_to_input = Some(NodeInput::node(upstream_layer_id, 0));
|
||||
}
|
||||
}
|
||||
// If the node is not a layer or an upstream layer is not found, reconnect to the first upstream node
|
||||
if reconnect_to_input.is_none() && (matches!(node.inputs.first(), Some(NodeInput::Node { .. }) | Some(NodeInput::Network { .. }))) {
|
||||
reconnect_to_input = Some(node.inputs[0].clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect all upstream references
|
||||
let number_of_outputs = self.number_of_outputs(deleting_node_id, network_path);
|
||||
let mut reconnect_to_input = network.nodes.get(node_id).and_then(|node| {
|
||||
node.inputs
|
||||
.iter()
|
||||
.find(|input| input.is_exposed_to_frontend(network_path.is_empty()))
|
||||
.filter(|input| matches!(input, NodeInput::Node { .. } | NodeInput::Network { .. }))
|
||||
.cloned()
|
||||
});
|
||||
// Get all upstream references
|
||||
let number_of_outputs = self.number_of_outputs(node_id, network_path);
|
||||
let Some(all_outward_wires) = self.outward_wires(network_path) else {
|
||||
log::error!("Could not get outward wires in remove_references_from_network");
|
||||
return false;
|
||||
};
|
||||
let mut downstream_inputs_to_disconnect = Vec::new();
|
||||
for output_index in 0..number_of_outputs {
|
||||
if let Some(outward_wires) = all_outward_wires.get(&OutputConnector::node(*deleting_node_id, output_index)) {
|
||||
if let Some(outward_wires) = all_outward_wires.get(&OutputConnector::node(*node_id, output_index)) {
|
||||
downstream_inputs_to_disconnect.extend(outward_wires.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for input_to_disconnect in &downstream_inputs_to_disconnect {
|
||||
// Prevent reconnecting export to import until https://github.com/GraphiteEditor/Graphite/issues/1762 is solved
|
||||
if matches!(reconnect_to_input, Some(NodeInput::Network { .. })) && matches!(input_to_disconnect, InputConnector::Export(_)) {
|
||||
self.disconnect_input(input_to_disconnect, network_path);
|
||||
} else if let Some(reconnect_input) = reconnect_to_input.take() {
|
||||
let original_position = reconnect_input.as_node().and_then(|downstream_node| self.position(&downstream_node, network_path));
|
||||
let original_downstream_position = input_to_disconnect.node_id().and_then(|downstream_id| self.position(&downstream_id, network_path));
|
||||
let mut reconnect_node = None;
|
||||
|
||||
self.set_input(input_to_disconnect, reconnect_input.clone(), network_path);
|
||||
if let (Some(original_position), Some(original_downstream_position)) = (original_position, original_downstream_position) {
|
||||
// Recalculate stack position (to keep layer in same place) if the upstream node is a layer in a chain
|
||||
if reconnect_input
|
||||
.as_node()
|
||||
.is_some_and(|upstream_node| self.is_stack(&upstream_node, network_path) && self.is_layer(&upstream_node, network_path))
|
||||
{
|
||||
let offset = (original_position.y - original_downstream_position.y - 3).max(0) as u32;
|
||||
self.set_stack_position(&reconnect_input.as_node().unwrap(), offset, network_path);
|
||||
}
|
||||
for downstream_input in &downstream_inputs_to_disconnect {
|
||||
self.disconnect_input(downstream_input, network_path);
|
||||
// Prevent reconnecting export to import until https://github.com/GraphiteEditor/Graphite/issues/1762 is solved
|
||||
if !(matches!(reconnect_to_input, Some(NodeInput::Network { .. })) && matches!(downstream_input, InputConnector::Export(_))) {
|
||||
if let Some(reconnect_input) = reconnect_to_input.take() {
|
||||
// Get the reconnect node position only if it is in a stack
|
||||
reconnect_node = reconnect_input.as_node().and_then(|node_id| if self.is_stack(&node_id, network_path) { Some(node_id) } else { None });
|
||||
self.disconnect_input(&InputConnector::node(*node_id, 0), network_path);
|
||||
self.set_input(downstream_input, reconnect_input, network_path);
|
||||
}
|
||||
} else {
|
||||
self.disconnect_input(input_to_disconnect, network_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Shift the reconnected node up to collapse space
|
||||
if let Some(reconnect_node) = &reconnect_node {
|
||||
let Some(reconnected_node_position) = self.position(reconnect_node, network_path) else {
|
||||
log::error!("Could not get reconnected node position in remove_references_from_network");
|
||||
return false;
|
||||
};
|
||||
let Some(disconnected_node_position) = self.position(node_id, network_path) else {
|
||||
log::error!("Could not get disconnected node position in remove_references_from_network");
|
||||
return false;
|
||||
};
|
||||
let max_shift_distance = reconnected_node_position.y - disconnected_node_position.y;
|
||||
|
||||
let upstream_nodes = self.upstream_flow_back_from_nodes(vec![*reconnect_node], network_path, FlowType::PrimaryFlow).collect::<Vec<_>>();
|
||||
|
||||
// Select the reconnect node to move to ensure the shifting works correctly
|
||||
let Some(selected_nodes) = self.selected_nodes_mut(network_path) else {
|
||||
log::error!("Could not get selected nodes in remove_references_from_network");
|
||||
return false;
|
||||
};
|
||||
|
||||
let old_selected_nodes = selected_nodes.replace_with(upstream_nodes);
|
||||
|
||||
// Shift up until there is either a collision or the disconnected node position is reached
|
||||
let mut current_shift_distance = 0;
|
||||
while self.check_collision_with_stack_dependents(reconnect_node, -1, network_path).is_empty() && max_shift_distance > current_shift_distance {
|
||||
self.shift_selected_nodes(Direction::Up, false, network_path);
|
||||
current_shift_distance += 1;
|
||||
}
|
||||
|
||||
let _ = self.selected_nodes_mut(network_path).unwrap().replace_with(old_selected_nodes);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -3502,6 +3601,10 @@ impl NodeNetworkInterface {
|
|||
return;
|
||||
};
|
||||
|
||||
if node_metadata.persistent_metadata.display_name == display_name {
|
||||
return;
|
||||
}
|
||||
|
||||
node_metadata.persistent_metadata.display_name.clone_from(&display_name);
|
||||
|
||||
// Keep the alias in sync with the `ToArtboard` name input
|
||||
|
@ -3526,6 +3629,7 @@ impl NodeNetworkInterface {
|
|||
to_artboard.inputs[label_index] = label_input;
|
||||
}
|
||||
|
||||
self.transaction_modified();
|
||||
self.try_unload_layer_width(node_id, network_path);
|
||||
self.unload_node_click_targets(node_id, network_path);
|
||||
}
|
||||
|
@ -3541,6 +3645,7 @@ impl NodeNetworkInterface {
|
|||
};
|
||||
|
||||
node.visible = is_visible;
|
||||
self.transaction_modified();
|
||||
}
|
||||
|
||||
pub fn set_locked(&mut self, node_id: &NodeId, network_path: &[NodeId], locked: bool) {
|
||||
|
@ -3550,6 +3655,7 @@ impl NodeNetworkInterface {
|
|||
};
|
||||
|
||||
node_metadata.persistent_metadata.locked = locked;
|
||||
self.transaction_modified();
|
||||
}
|
||||
|
||||
pub fn set_to_node_or_layer(&mut self, node_id: &NodeId, network_path: &[NodeId], is_layer: bool) {
|
||||
|
@ -3652,6 +3758,7 @@ impl NodeNetworkInterface {
|
|||
self.try_set_upstream_to_chain(&InputConnector::node(*node_id, 0), network_path);
|
||||
}
|
||||
|
||||
self.transaction_modified();
|
||||
self.unload_upstream_node_click_targets(vec![*node_id], network_path);
|
||||
self.unload_all_nodes_bounding_box(network_path);
|
||||
self.unload_import_export_ports(network_path);
|
||||
|
@ -3751,10 +3858,19 @@ impl NodeNetworkInterface {
|
|||
log::error!("Could not get node_metadata for node {node_id}");
|
||||
return;
|
||||
};
|
||||
|
||||
if let NodeTypePersistentMetadata::Node(node_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
||||
if node_metadata.position == NodePosition::Absolute(position) {
|
||||
return;
|
||||
}
|
||||
node_metadata.position = NodePosition::Absolute(position);
|
||||
self.transaction_modified();
|
||||
} else if let NodeTypePersistentMetadata::Layer(layer_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
||||
if layer_metadata.position == LayerPosition::Absolute(position) {
|
||||
return;
|
||||
}
|
||||
layer_metadata.position = LayerPosition::Absolute(position);
|
||||
self.transaction_modified();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3765,7 +3881,11 @@ impl NodeNetworkInterface {
|
|||
return;
|
||||
};
|
||||
if let NodeTypePersistentMetadata::Layer(layer_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
||||
if layer_metadata.position == LayerPosition::Stack(y_offset) {
|
||||
return;
|
||||
}
|
||||
layer_metadata.position = LayerPosition::Stack(y_offset);
|
||||
self.transaction_modified();
|
||||
} else {
|
||||
log::error!("Could not set stack position for non layer node {node_id}");
|
||||
}
|
||||
|
@ -3793,7 +3913,11 @@ impl NodeNetworkInterface {
|
|||
};
|
||||
// Set any absolute nodes to chain positioning
|
||||
if let NodeTypePersistentMetadata::Node(NodePersistentMetadata { position }) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
||||
if *position == NodePosition::Chain {
|
||||
return;
|
||||
}
|
||||
*position = NodePosition::Chain;
|
||||
self.transaction_modified();
|
||||
}
|
||||
// If there is an upstream layer then stop breaking the chain
|
||||
else {
|
||||
|
@ -3904,6 +4028,9 @@ impl NodeNetworkInterface {
|
|||
/// Used when moving layer by the layer panel, does not run any pushing logic. Moves all sole dependents of the layer as well.
|
||||
/// Ensure that the layer is absolute position.
|
||||
pub fn shift_absolute_node_position(&mut self, layer: &NodeId, shift: IVec2, network_path: &[NodeId]) {
|
||||
if shift == IVec2::ZERO {
|
||||
return;
|
||||
}
|
||||
let mut nodes_to_shift = self.upstream_nodes_below_layer(layer, network_path);
|
||||
nodes_to_shift.insert(*layer);
|
||||
|
||||
|
@ -3925,6 +4052,7 @@ impl NodeNetworkInterface {
|
|||
}
|
||||
}
|
||||
}
|
||||
self.transaction_modified();
|
||||
self.unload_upstream_node_click_targets(vec![*layer], network_path);
|
||||
}
|
||||
|
||||
|
@ -3933,39 +4061,48 @@ impl NodeNetworkInterface {
|
|||
log::error!("Could not get selected nodes in shift_selected_nodes");
|
||||
return;
|
||||
};
|
||||
|
||||
for node_id in node_ids.clone() {
|
||||
if self.is_layer(&node_id, network_path) {
|
||||
if let Some(owned_nodes) = self.owned_nodes(&node_id, network_path) {
|
||||
for owned_node in owned_nodes {
|
||||
node_ids.remove(owned_node);
|
||||
}
|
||||
};
|
||||
if !shift_without_push {
|
||||
for node_id in node_ids.clone() {
|
||||
if self.is_layer(&node_id, network_path) {
|
||||
if let Some(owned_nodes) = self.owned_nodes(&node_id, network_path) {
|
||||
for owned_node in owned_nodes {
|
||||
node_ids.remove(owned_node);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut filtered_node_ids = HashSet::new();
|
||||
for selected_node in &node_ids {
|
||||
for selected_node in &node_ids.clone() {
|
||||
// Deselect chain nodes upstream from a selected layer
|
||||
if self.is_chain(selected_node, network_path)
|
||||
&& self
|
||||
.downstream_layer(selected_node, network_path)
|
||||
.is_some_and(|downstream_layer| node_ids.contains(&downstream_layer.to_node()))
|
||||
{
|
||||
continue;
|
||||
node_ids.remove(selected_node);
|
||||
}
|
||||
filtered_node_ids.insert(*selected_node);
|
||||
}
|
||||
|
||||
// If shifting up without a push, cancel the shift if there is a stack node that cannot move up
|
||||
if direction == Direction::Up && shift_without_push {
|
||||
for node_id in &filtered_node_ids {
|
||||
for node_id in &node_ids {
|
||||
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
|
||||
log::error!("Could not get node metadata for node {node_id} in shift_selected_nodes");
|
||||
return;
|
||||
};
|
||||
if let NodeTypePersistentMetadata::Layer(layer_metadata) = &node_metadata.persistent_metadata.node_type_metadata {
|
||||
if let LayerPosition::Stack(offset) = layer_metadata.position {
|
||||
// If the upstream layer is selected, then skip
|
||||
let Some(outward_wires) = self.outward_wires(network_path).and_then(|outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0))) else {
|
||||
log::error!("Could not get outward wires in shift_selected_nodes");
|
||||
return;
|
||||
};
|
||||
if let Some(upstream_node) = outward_wires.first() {
|
||||
if node_ids.contains(&upstream_node.node_id().expect("Stack layer should have downstream layer")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Offset cannot be negative, so cancel the shift
|
||||
if offset == 0 {
|
||||
return;
|
||||
|
@ -3975,7 +4112,7 @@ impl NodeNetworkInterface {
|
|||
}
|
||||
}
|
||||
|
||||
let mut node_ids_with_position = filtered_node_ids
|
||||
let mut node_ids_with_position = node_ids
|
||||
.iter()
|
||||
.filter_map(|&node_id| {
|
||||
let Some(position) = self.position(&node_id, network_path) else {
|
||||
|
@ -3986,7 +4123,7 @@ impl NodeNetworkInterface {
|
|||
})
|
||||
.collect::<Vec<(NodeId, i32)>>();
|
||||
|
||||
if node_ids_with_position.len() != filtered_node_ids.len() {
|
||||
if node_ids_with_position.len() != node_ids.len() {
|
||||
log::error!("Could not get position for all nodes in shift_selected_nodes");
|
||||
return;
|
||||
}
|
||||
|
@ -4066,6 +4203,7 @@ impl NodeNetworkInterface {
|
|||
if let TransientMetadata::Loaded(stack_dependents) = &mut network_metadata.transient_metadata.stack_dependents {
|
||||
if let Some(LayerOwner::None(offset)) = stack_dependents.get_mut(node_id) {
|
||||
*offset += shift_sign;
|
||||
self.transaction_modified();
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -4181,6 +4319,7 @@ impl NodeNetworkInterface {
|
|||
match layer_owner {
|
||||
LayerOwner::None(offset) => {
|
||||
*offset += shift_sign;
|
||||
self.transaction_modified();
|
||||
}
|
||||
LayerOwner::Layer(_) => {
|
||||
log::error!("Node being shifted with a push should not be owned");
|
||||
|
@ -4211,7 +4350,7 @@ impl NodeNetworkInterface {
|
|||
|
||||
fn check_collision_with_stack_dependents(&mut self, node_id: &NodeId, shift_sign: i32, network_path: &[NodeId]) -> Vec<(NodeId, LayerOwner)> {
|
||||
self.try_load_all_node_click_targets(network_path);
|
||||
|
||||
self.try_load_stack_dependents(network_path);
|
||||
let Some(stack_dependents) = self.try_get_stack_dependents(network_path) else {
|
||||
log::error!("Could not load stack dependents in shift_selected_nodes");
|
||||
return Vec::new();
|
||||
|
@ -4290,6 +4429,7 @@ impl NodeNetworkInterface {
|
|||
if let NodeTypePersistentMetadata::Layer(layer_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
||||
if let LayerPosition::Absolute(layer_position) = &mut layer_metadata.position {
|
||||
*layer_position += shift;
|
||||
self.transaction_modified();
|
||||
} else if let LayerPosition::Stack(y_offset) = &mut layer_metadata.position {
|
||||
let shifted_y_offset = *y_offset as i32 + shift.y;
|
||||
// A layer can only be shifted to a positive y_offset
|
||||
|
@ -4302,14 +4442,19 @@ impl NodeNetworkInterface {
|
|||
if shift.x != 0 {
|
||||
log::error!("Stack layer {node_id} cannot be shifted horizontally.");
|
||||
}
|
||||
|
||||
*y_offset = shifted_y_offset.max(0) as u32;
|
||||
let new_y_offset = shifted_y_offset.max(0) as u32;
|
||||
if *y_offset == new_y_offset {
|
||||
return;
|
||||
}
|
||||
*y_offset = new_y_offset;
|
||||
self.transaction_modified();
|
||||
}
|
||||
// Unload click targets for all upstream nodes, since they may have been derived from the node that was shifted
|
||||
self.unload_upstream_node_click_targets(vec![*node_id], network_path);
|
||||
} else if let NodeTypePersistentMetadata::Node(node_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
||||
if let NodePosition::Absolute(node_metadata) = &mut node_metadata.position {
|
||||
*node_metadata += shift;
|
||||
self.transaction_modified();
|
||||
// Unload click targets for all upstream nodes, since they may have been derived from the node that was shifted
|
||||
self.unload_upstream_node_click_targets(vec![*node_id], network_path);
|
||||
|
||||
|
@ -4335,6 +4480,12 @@ impl NodeNetworkInterface {
|
|||
// TODO: Run the auto layout system to make space for the new nodes
|
||||
/// Disconnect the layers primary output and the input to the last non layer node feeding into it through primary flow, reconnects, then moves the layer to the new layer and stack index
|
||||
pub fn move_layer_to_stack(&mut self, layer: LayerNodeIdentifier, mut parent: LayerNodeIdentifier, mut insert_index: usize, network_path: &[NodeId]) {
|
||||
// Prevent moving an artboard anywhere but to the ROOT_PARENT child stack
|
||||
if self.is_artboard(&layer.to_node(), network_path) && parent != LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Artboard can only be moved to the root parent stack");
|
||||
return;
|
||||
}
|
||||
|
||||
// A layer is considered to be the height of that layer plus the height to the upstream layer sibling
|
||||
// If a non artboard layer is attempted to be connected to the exports, and there is already an artboard connected, then connect the layer to the artboard.
|
||||
if let Some(first_layer) = LayerNodeIdentifier::ROOT_PARENT.children(&self.document_metadata).next() {
|
||||
|
@ -4352,16 +4503,6 @@ impl NodeNetworkInterface {
|
|||
return;
|
||||
};
|
||||
|
||||
let previous_upstream_node = self.upstream_flow_back_from_nodes(vec![layer.to_node()], network_path, FlowType::PrimaryFlow).nth(1);
|
||||
let mut height_below_layer = 0;
|
||||
|
||||
if let Some(previous_upstream_node) = previous_upstream_node {
|
||||
let Some(previous_upstream_node_position) = self.position(&previous_upstream_node, network_path) else {
|
||||
log::error!("Could not get previous upstream node position in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
height_below_layer = (previous_upstream_node_position.y - layer_to_move_position.y - 3).max(0) as u32;
|
||||
}
|
||||
let mut lowest_upstream_node_height = 0;
|
||||
for upstream_node in self
|
||||
.upstream_flow_back_from_nodes(vec![layer.to_node()], network_path, FlowType::LayerChildrenUpstreamFlow)
|
||||
|
@ -4374,9 +4515,6 @@ impl NodeNetworkInterface {
|
|||
lowest_upstream_node_height = lowest_upstream_node_height.max((upstream_node_position.y - layer_to_move_position.y).max(0) as u32);
|
||||
}
|
||||
|
||||
// Height under the layer to move, which should be retained after the move
|
||||
let layer_to_move_height = height_below_layer.max(lowest_upstream_node_height);
|
||||
|
||||
// If the moved layer is a child of the new parent, then get its index after the disconnect
|
||||
if let Some(moved_layer_previous_index) = parent.children(&self.document_metadata).position(|child| child == layer) {
|
||||
// Adjust the insert index if the layer's previous index is less than the insert index
|
||||
|
@ -4387,9 +4525,6 @@ impl NodeNetworkInterface {
|
|||
|
||||
// Disconnect layer to move
|
||||
self.remove_references_from_network(&layer.to_node(), network_path);
|
||||
self.disconnect_input(&InputConnector::node(layer.to_node(), 0), network_path);
|
||||
|
||||
// TODO: Collapse space between parent and second child if top of stack is moved using the layout system
|
||||
|
||||
let post_node = ModifyInputsContext::get_post_node_with_index(self, parent, insert_index);
|
||||
|
||||
|
@ -4415,8 +4550,124 @@ impl NodeNetworkInterface {
|
|||
return;
|
||||
};
|
||||
|
||||
// Get the height of the downstream node if inserting into a stack
|
||||
let mut downstream_height = 0;
|
||||
let inserting_into_stack =
|
||||
!(post_node.input_index() == 1 || matches!(post_node, InputConnector::Export(_)) || !post_node.node_id().is_some_and(|post_node_id| self.is_layer(&post_node_id, network_path)));
|
||||
if inserting_into_stack {
|
||||
if let Some(downstream_node) = post_node.node_id() {
|
||||
let Some(downstream_node_position) = self.position(&downstream_node, network_path) else {
|
||||
log::error!("Could not get downstream node position in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
let mut lowest_y_position = downstream_node_position.y + 3;
|
||||
|
||||
for bottom_position in self.upstream_nodes_below_layer(&downstream_node, network_path).iter().filter_map(|node_id| {
|
||||
let is_layer = self.is_layer(node_id, network_path);
|
||||
self.position(node_id, network_path).map(|position| position.y + if is_layer { 3 } else { 2 })
|
||||
}) {
|
||||
lowest_y_position = lowest_y_position.max(bottom_position);
|
||||
}
|
||||
downstream_height = lowest_y_position - (downstream_node_position.y + 3);
|
||||
}
|
||||
}
|
||||
|
||||
let mut highest_y_position = layer_to_move_position.y;
|
||||
let mut lowest_y_position = layer_to_move_position.y;
|
||||
|
||||
for (bottom_position, top_position) in self.upstream_nodes_below_layer(&layer.to_node(), network_path).iter().filter_map(|node_id| {
|
||||
let is_layer = self.is_layer(node_id, network_path);
|
||||
let bottom_position = self.position(node_id, network_path).map(|position| position.y + if is_layer { 3 } else { 2 });
|
||||
let top_position = self.position(node_id, network_path).map(|position| if is_layer { position.y - 1 } else { position.y });
|
||||
bottom_position.zip(top_position)
|
||||
}) {
|
||||
highest_y_position = highest_y_position.min(top_position);
|
||||
lowest_y_position = lowest_y_position.max(bottom_position);
|
||||
}
|
||||
let height_above_layer = layer_to_move_position.y - highest_y_position + downstream_height;
|
||||
let height_below_layer = lowest_y_position - layer_to_move_position.y - 3;
|
||||
|
||||
// If there is an upstream node in the new location for the layer, create space for the moved layer by shifting the upstream node down
|
||||
if let Some(upstream_node_id) = post_node_input.as_node() {
|
||||
// Select the layer to move to ensure the shifting works correctly
|
||||
let Some(selected_nodes) = self.selected_nodes_mut(network_path) else {
|
||||
log::error!("Could not get selected nodes in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
let old_selected_nodes = selected_nodes.replace_with(vec![upstream_node_id]);
|
||||
|
||||
// Create the minimum amount space for the moved layer
|
||||
for _ in 0..3 {
|
||||
self.vertical_shift_with_push(&upstream_node_id, 1, &mut HashSet::new(), network_path);
|
||||
}
|
||||
|
||||
let Some(stack_position) = self.position(&upstream_node_id, network_path) else {
|
||||
log::error!("Could not get stack position in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
|
||||
let current_gap = stack_position.y - (after_move_post_layer_position.y + 2);
|
||||
let target_gap = 1 + height_above_layer + 2 + height_below_layer + 1;
|
||||
|
||||
for _ in 0..(target_gap - current_gap).max(0) {
|
||||
self.vertical_shift_with_push(&upstream_node_id, 1, &mut HashSet::new(), network_path);
|
||||
}
|
||||
|
||||
let _ = self.selected_nodes_mut(network_path).unwrap().replace_with(old_selected_nodes);
|
||||
}
|
||||
|
||||
// If inserting into a stack with a parent, ensure the parent stack has enough space for the child stack
|
||||
if parent != LayerNodeIdentifier::ROOT_PARENT {
|
||||
if let Some(upstream_sibling) = parent.next_sibling(&self.document_metadata) {
|
||||
let Some(parent_position) = self.position(&parent.to_node(), network_path) else {
|
||||
log::error!("Could not get parent position in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
let last_child = parent.last_child(&self.document_metadata).unwrap_or(parent);
|
||||
|
||||
let Some(mut last_child_position) = self.position(&last_child.to_node(), network_path) else {
|
||||
log::error!("Could not get last child position in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
|
||||
if self.is_layer(&last_child.to_node(), network_path) {
|
||||
last_child_position.y += 3;
|
||||
} else {
|
||||
last_child_position.y += 2;
|
||||
}
|
||||
|
||||
// If inserting below the current last child, then the last child is layer to move
|
||||
if post_node.node_id() == Some(last_child.to_node()) {
|
||||
last_child_position += height_above_layer + 3 + height_below_layer;
|
||||
}
|
||||
|
||||
let Some(upstream_sibling_position) = self.position(&upstream_sibling.to_node(), network_path) else {
|
||||
log::error!("Could not get upstream sibling position in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
|
||||
let target_gap = last_child_position.y - parent_position.y + 3;
|
||||
let current_gap = upstream_sibling_position.y - parent_position.y;
|
||||
|
||||
let upstream_nodes = self
|
||||
.upstream_flow_back_from_nodes(vec![upstream_sibling.to_node()], network_path, FlowType::UpstreamFlow)
|
||||
.collect::<Vec<_>>();
|
||||
let Some(selected_nodes) = self.selected_nodes_mut(network_path) else {
|
||||
log::error!("Could not get selected nodes in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
let old_selected_nodes = selected_nodes.replace_with(upstream_nodes);
|
||||
|
||||
for _ in 0..(target_gap - current_gap).max(0) {
|
||||
self.shift_selected_nodes(Direction::Down, true, network_path);
|
||||
}
|
||||
|
||||
let _ = self.selected_nodes_mut(network_path).unwrap().replace_with(old_selected_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
// Connect the layer to a parent layer/node at the top of the stack, or a non layer node midway down the stack
|
||||
if post_node.input_index() == 1 || matches!(post_node, InputConnector::Export(_)) || !post_node.node_id().is_some_and(|post_node_id| self.is_layer(&post_node_id, network_path)) {
|
||||
if !inserting_into_stack {
|
||||
match post_node_input {
|
||||
// Create a new stack
|
||||
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
|
||||
|
@ -4426,17 +4677,17 @@ impl NodeNetworkInterface {
|
|||
let shift = final_layer_position - previous_layer_position;
|
||||
self.shift_absolute_node_position(&layer.to_node(), shift, network_path);
|
||||
}
|
||||
// Move to the top of a stack
|
||||
// Move to the top of a stack.
|
||||
NodeInput::Node { node_id, .. } => {
|
||||
let Some(top_of_stack_position) = self.position(&node_id, network_path) else {
|
||||
log::error!("Could not get top of stack position in move_layer_to_stack");
|
||||
let Some(stack_top_position) = self.position(&node_id, network_path) else {
|
||||
log::error!("Could not get stack x position in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
let shift = top_of_stack_position - previous_layer_position;
|
||||
|
||||
let final_layer_position = IVec2::new(stack_top_position.x, after_move_post_layer_position.y + 3 + height_above_layer);
|
||||
let shift = final_layer_position - previous_layer_position;
|
||||
self.shift_absolute_node_position(&layer.to_node(), shift, network_path);
|
||||
self.shift_absolute_node_position(&node_id, IVec2::new(0, layer_to_move_height as i32 + 3), network_path);
|
||||
self.insert_node_between(&layer.to_node(), &post_node, 0, network_path);
|
||||
self.set_stack_position_calculated_offset(&node_id, &layer.to_node(), network_path);
|
||||
}
|
||||
NodeInput::Network { .. } => {
|
||||
log::error!("Cannot move post node to parent which connects to the imports")
|
||||
|
@ -4447,42 +4698,16 @@ impl NodeNetworkInterface {
|
|||
// Move to the bottom of the stack
|
||||
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
|
||||
// TODO: Calculate height of bottom layer by getting height of upstream nodes instead of setting to 3
|
||||
let offset = after_move_post_layer_position - previous_layer_position + IVec2::new(0, 3);
|
||||
let offset = after_move_post_layer_position - previous_layer_position + IVec2::new(0, 3 + height_above_layer);
|
||||
self.shift_absolute_node_position(&layer.to_node(), offset, network_path);
|
||||
self.create_wire(&OutputConnector::node(layer.to_node(), 0), &post_node, network_path);
|
||||
self.set_stack_position_calculated_offset(&layer.to_node(), &post_node.node_id().unwrap(), network_path);
|
||||
}
|
||||
// Insert into the stack
|
||||
NodeInput::Node { node_id: upstream_node_id, .. } => {
|
||||
let Some(upstream_node_metadata) = self.node_metadata(&upstream_node_id, network_path) else {
|
||||
log::error!("Could not get upstream node metadata in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
let NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { position, .. }) = &upstream_node_metadata.persistent_metadata.node_type_metadata else {
|
||||
log::error!("Could not get upstream node metadata in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
// TODO: Max with height of upstream chain
|
||||
let LayerPosition::Stack(post_node_height) = position.clone() else {
|
||||
log::error!("Could not get vertical offset 3 in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(upstream_node_position) = self.position(&upstream_node_id, network_path) else {
|
||||
log::error!("Could not get upstream node position in move_layer_to_stack");
|
||||
return;
|
||||
};
|
||||
self.set_absolute_position(&upstream_node_id, upstream_node_position, network_path);
|
||||
|
||||
let offset = after_move_post_layer_position - previous_layer_position + IVec2::new(0, 3 + post_node_height as i32);
|
||||
self.shift_absolute_node_position(&layer.to_node(), offset, network_path);
|
||||
let upstream_offset = layer_to_move_height as i32 + 3;
|
||||
self.shift_absolute_node_position(&upstream_node_id, IVec2::new(0, upstream_offset), network_path);
|
||||
|
||||
NodeInput::Node { .. } => {
|
||||
let final_layer_position = after_move_post_layer_position + IVec2::new(0, 3 + height_above_layer);
|
||||
let shift = final_layer_position - previous_layer_position;
|
||||
self.shift_absolute_node_position(&layer.to_node(), shift, network_path);
|
||||
self.insert_node_between(&layer.to_node(), &post_node, 0, network_path);
|
||||
|
||||
self.set_stack_position_calculated_offset(&layer.to_node(), &post_node.node_id().unwrap(), network_path);
|
||||
self.set_stack_position_calculated_offset(&upstream_node_id, &layer.to_node(), network_path);
|
||||
}
|
||||
NodeInput::Network { .. } => {
|
||||
log::error!("Cannot move post node to parent which connects to the imports")
|
||||
|
@ -4809,14 +5034,14 @@ impl NodeNetworkMetadata {
|
|||
|
||||
for segment in nested_path {
|
||||
network_metadata = network_metadata
|
||||
.and_then(|network| network.persistent_metadata.node_metadata.get_mut(segment))
|
||||
.and_then(|network: &mut NodeNetworkMetadata| network.persistent_metadata.node_metadata.get_mut(segment))
|
||||
.and_then(|node| node.persistent_metadata.network_metadata.as_mut());
|
||||
}
|
||||
network_metadata
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct NodeNetworkPersistentMetadata {
|
||||
/// Node metadata must exist for every document node in the network
|
||||
#[serde(serialize_with = "graphene_std::vector::serialize_hashmap", deserialize_with = "graphene_std::vector::deserialize_hashmap")]
|
||||
|
@ -4826,6 +5051,12 @@ pub struct NodeNetworkPersistentMetadata {
|
|||
pub previewing: Previewing,
|
||||
// Stores the transform and navigation state for the network
|
||||
pub navigation_metadata: NavigationMetadata,
|
||||
/// Stack of selection snapshots for previous history states.
|
||||
#[serde(default)]
|
||||
pub selection_undo_history: VecDeque<SelectedNodes>,
|
||||
/// Stack of selection snapshots for future history states.
|
||||
#[serde(default)]
|
||||
pub selection_redo_history: VecDeque<SelectedNodes>,
|
||||
}
|
||||
|
||||
/// This is the same as Option, but more clear in the context of having cached metadata either being loaded or unloaded
|
||||
|
@ -5107,3 +5338,11 @@ pub struct NodeTemplate {
|
|||
pub document_node: DocumentNode,
|
||||
pub persistent_node_metadata: DocumentNodePersistentMetadata,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum TransactionStatus {
|
||||
Started,
|
||||
Modified,
|
||||
#[default]
|
||||
Finished,
|
||||
}
|
||||
|
|
|
@ -140,6 +140,14 @@ impl SelectedNodes {
|
|||
pub fn clear_selected_nodes(&mut self) {
|
||||
self.0 = Vec::new();
|
||||
}
|
||||
|
||||
pub fn replace_with(&mut self, new: Vec<NodeId>) -> Vec<NodeId> {
|
||||
std::mem::replace(&mut self.0, new)
|
||||
}
|
||||
|
||||
pub fn filtered_selected_nodes(&self, node_ids: std::collections::HashSet<NodeId>) -> SelectedNodes {
|
||||
SelectedNodes(self.0.iter().filter(|node_id| node_ids.contains(node_id)).cloned().collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
||||
|
|
|
@ -190,6 +190,20 @@ impl LayoutHolder for MenuBarMessageHandler {
|
|||
..MenuBarEntry::default()
|
||||
},
|
||||
],
|
||||
vec![
|
||||
MenuBarEntry {
|
||||
label: "Previous Selection".into(),
|
||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectionStepBack),
|
||||
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectionStepBack.into()),
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
MenuBarEntry {
|
||||
label: "Next Selection".into(),
|
||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectionStepForward),
|
||||
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectionStepForward.into()),
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
],
|
||||
vec![MenuBarEntry {
|
||||
label: "Delete Selected".into(),
|
||||
icon: Some("Trash".into()),
|
||||
|
|
|
@ -595,10 +595,14 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
if let Ok(data) = serde_json::from_str::<Vec<CopyBufferEntry>>(&data) {
|
||||
let parent = document.new_layer_parent(false);
|
||||
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let mut added_nodes = false;
|
||||
|
||||
for entry in data.into_iter().rev() {
|
||||
if !added_nodes {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
added_nodes = true;
|
||||
}
|
||||
document.load_layer_resources(responses);
|
||||
let new_ids: HashMap<_, _> = entry.nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
|
||||
let layer = LayerNodeIdentifier::new_unchecked(new_ids[&NodeId(0)]);
|
||||
|
|
|
@ -45,11 +45,8 @@ impl Pivot {
|
|||
|
||||
/// Recomputes the pivot position and transform.
|
||||
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
|
||||
let mut layers = document
|
||||
.network_interface
|
||||
.selected_nodes(&[])
|
||||
.unwrap()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface);
|
||||
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
|
||||
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
|
||||
let Some(first) = layers.next() else {
|
||||
// If no layers are selected then we revert things back to default
|
||||
self.normalized_pivot = DVec2::splat(0.5);
|
||||
|
|
|
@ -146,8 +146,6 @@ impl ArtboardToolData {
|
|||
}
|
||||
|
||||
fn select_artboard(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> bool {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
if let Some(intersection) = Self::hovered_artboard(document, input) {
|
||||
self.selected_artboard = Some(intersection);
|
||||
|
||||
|
@ -238,9 +236,7 @@ impl Fsm for ArtboardToolFsmState {
|
|||
tool_data.drag_start = to_document.transform_point2(input.mouse.position);
|
||||
tool_data.drag_current = to_document.transform_point2(input.mouse.position);
|
||||
|
||||
if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let state = if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) {
|
||||
tool_data.start_resizing(selected_edges, document, input);
|
||||
tool_data.get_snap_candidates(document, input);
|
||||
ArtboardToolFsmState::ResizingBounds
|
||||
|
@ -258,7 +254,9 @@ impl Fsm for ArtboardToolFsmState {
|
|||
tool_data.drag_current = snapped.snapped_point_document;
|
||||
|
||||
ArtboardToolFsmState::Drawing
|
||||
}
|
||||
};
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
state
|
||||
}
|
||||
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
|
||||
let from_center = input.keyboard.get(center as usize);
|
||||
|
@ -427,32 +425,15 @@ impl Fsm for ArtboardToolFsmState {
|
|||
|
||||
state
|
||||
}
|
||||
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
|
||||
(ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds | ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
ArtboardToolFsmState::Ready { hovered }
|
||||
}
|
||||
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
ArtboardToolFsmState::Ready { hovered }
|
||||
}
|
||||
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
ArtboardToolFsmState::Ready { hovered }
|
||||
|
|
|
@ -381,7 +381,9 @@ impl Fsm for BrushToolFsmState {
|
|||
|
||||
(BrushToolFsmState::Drawing, BrushToolMessage::DragStop) => {
|
||||
if !tool_data.strokes.is_empty() {
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
tool_data.strokes.clear();
|
||||
|
||||
|
|
|
@ -96,9 +96,8 @@ impl Fsm for FillToolFsmState {
|
|||
_ => return self,
|
||||
};
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(GraphOperationMessage::FillSet { layer: layer_identifier, fill });
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
|
||||
FillToolFsmState::Filling
|
||||
}
|
||||
|
|
|
@ -254,7 +254,7 @@ impl Fsm for FreehandToolFsmState {
|
|||
if tool_data.dragged {
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::DocumentHistoryBackward);
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
}
|
||||
|
||||
tool_data.end_point = None;
|
||||
|
|
|
@ -284,6 +284,8 @@ impl Fsm for GradientToolFsmState {
|
|||
return self;
|
||||
}
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
// Remove the selected point
|
||||
match selected_gradient.dragging {
|
||||
GradientDragTarget::Start => selected_gradient.gradient.stops.0.remove(0),
|
||||
|
@ -326,8 +328,8 @@ impl Fsm for GradientToolFsmState {
|
|||
(_, GradientToolMessage::InsertStop) => {
|
||||
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_visible_layers(&document.network_interface) {
|
||||
let Some(mut gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
||||
// TODO: This transform is incorrect. I think this is since it is based on the Footprint which has not been updated yet
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
|
||||
let mouse = input.mouse.position;
|
||||
let (start, end) = (transform.transform_point2(gradient.start), transform.transform_point2(gradient.end));
|
||||
|
||||
|
@ -338,7 +340,7 @@ impl Fsm for GradientToolFsmState {
|
|||
if distance < (SELECTION_THRESHOLD * 2.) {
|
||||
// Try and insert the new stop
|
||||
if let Some(index) = gradient.insert_stop(mouse, transform) {
|
||||
document.backup_nonmut(responses);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
let mut selected_gradient = SelectedGradient::new(gradient, layer, document);
|
||||
|
||||
|
@ -366,7 +368,6 @@ impl Fsm for GradientToolFsmState {
|
|||
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_visible_layers(&document.network_interface) {
|
||||
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
|
||||
// Check for dragging step
|
||||
for (index, (pos, _)) in gradient.stops.0.iter().enumerate() {
|
||||
let pos = transform.transform_point2(gradient.start.lerp(gradient.end, *pos));
|
||||
|
@ -395,8 +396,8 @@ impl Fsm for GradientToolFsmState {
|
|||
}
|
||||
}
|
||||
}
|
||||
if dragging {
|
||||
document.backup_nonmut(responses);
|
||||
|
||||
let gradient_state = if dragging {
|
||||
GradientToolFsmState::Drawing
|
||||
} else {
|
||||
let selected_layer = document.click(input);
|
||||
|
@ -409,8 +410,6 @@ impl Fsm for GradientToolFsmState {
|
|||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
||||
}
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Use the already existing gradient if it exists
|
||||
let gradient = if let Some(gradient) = get_gradient(layer, &document.network_interface) {
|
||||
gradient.clone()
|
||||
|
@ -433,7 +432,9 @@ impl Fsm for GradientToolFsmState {
|
|||
} else {
|
||||
GradientToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
};
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
gradient_state
|
||||
}
|
||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
||||
if let Some(selected_gradient) = &mut tool_data.selected_gradient {
|
||||
|
@ -473,6 +474,11 @@ impl Fsm for GradientToolFsmState {
|
|||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
|
||||
input.mouse.finish_transaction(tool_data.drag_start, responses);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
if let Some(selected_layer) = document.click(input) {
|
||||
if let Some(gradient) = get_gradient(selected_layer, &document.network_interface) {
|
||||
tool_data.selected_gradient = Some(SelectedGradient::new(gradient, selected_layer, document));
|
||||
}
|
||||
}
|
||||
|
||||
GradientToolFsmState::Ready
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ impl Fsm for ImaginateToolFsmState {
|
|||
match (self, event) {
|
||||
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
|
||||
shape_data.start(document, input);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
// responses.add(DocumentMessage::AddTransaction);
|
||||
//shape_data.layer = Some(LayerNodeIdentifier::new(NodeId(generate_uuid()), &document.network_interface));
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
|
|
|
@ -289,20 +289,25 @@ impl PathToolData {
|
|||
}
|
||||
|
||||
fn end_insertion(&mut self, shape_editor: &mut ShapeState, responses: &mut VecDeque<Message>, kind: InsertEndKind) -> PathToolFsmState {
|
||||
let mut commit_transaction = false;
|
||||
match self.segment.as_mut() {
|
||||
None => {
|
||||
warn!("Segment was `None` before `end_insertion`")
|
||||
}
|
||||
Some(closed_segment) => {
|
||||
if let InsertEndKind::Add { shift } = kind {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
closed_segment.adjusted_insert_and_select(shape_editor, responses, shift);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
commit_transaction = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.segment = None;
|
||||
if commit_transaction {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
@ -323,15 +328,18 @@ impl PathToolData {
|
|||
|
||||
// Select the first point within the threshold (in pixels)
|
||||
if let Some(selected_points) = shape_editor.change_point_selection(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD, add_to_selection) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
if let Some(selected_points) = selected_points {
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
self.start_dragging_point(selected_points, input, document, shape_editor, responses);
|
||||
self.start_dragging_point(selected_points, input, document, shape_editor);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
PathToolFsmState::Dragging
|
||||
}
|
||||
// We didn't find a point nearby, so now we'll try to add a point into the closest path segment
|
||||
else if let Some(closed_segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SELECTION_TOLERANCE) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
if direct_insert_without_sliding {
|
||||
self.start_insertion(responses, closed_segment);
|
||||
self.end_insertion(shape_editor, responses, InsertEndKind::Add { shift: add_to_selection })
|
||||
|
@ -341,6 +349,7 @@ impl PathToolData {
|
|||
}
|
||||
// We didn't find a segment path, so consider selecting the nearest shape instead
|
||||
else if let Some(layer) = document.click(input) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
if add_to_selection {
|
||||
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
||||
} else {
|
||||
|
@ -361,16 +370,7 @@ impl PathToolData {
|
|||
}
|
||||
}
|
||||
|
||||
fn start_dragging_point(
|
||||
&mut self,
|
||||
selected_points: SelectedPointsInfo,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
document: &DocumentMessageHandler,
|
||||
shape_editor: &mut ShapeState,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
fn start_dragging_point(&mut self, selected_points: SelectedPointsInfo, input: &InputPreprocessorMessageHandler, document: &DocumentMessageHandler, shape_editor: &mut ShapeState) {
|
||||
let mut manipulators = HashMap::with_hasher(NoHashBuilder);
|
||||
let mut unselected = Vec::new();
|
||||
for (&layer, state) in &shape_editor.selected_shape_state {
|
||||
|
@ -587,7 +587,6 @@ impl Fsm for PathToolFsmState {
|
|||
PathToolFsmState::Ready
|
||||
}
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::Escape | PathToolMessage::RightClick) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
@ -621,6 +620,7 @@ impl Fsm for PathToolFsmState {
|
|||
}
|
||||
}
|
||||
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
PathToolFsmState::Ready
|
||||
|
@ -629,7 +629,7 @@ impl Fsm for PathToolFsmState {
|
|||
// Delete key
|
||||
(_, PathToolMessage::Delete) => {
|
||||
// Delete the selected points and clean up overlays
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
shape_editor.delete_selected_points(document, responses);
|
||||
responses.add(PathToolMessage::SelectionChanged);
|
||||
|
||||
|
@ -689,14 +689,14 @@ impl Fsm for PathToolFsmState {
|
|||
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
responses.add(PathToolMessage::SelectionChanged);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::ManipulatorMakeHandlesFree) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, _) => PathToolFsmState::Ready,
|
||||
|
|
|
@ -630,6 +630,7 @@ impl Fsm for PenToolFsmState {
|
|||
state
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
tool_data.layer = None;
|
||||
tool_data.handle_end = None;
|
||||
tool_data.latest_points.clear();
|
||||
|
@ -639,6 +640,8 @@ impl Fsm for PenToolFsmState {
|
|||
PenToolFsmState::Ready
|
||||
}
|
||||
(_, PenToolMessage::Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
self
|
||||
|
|
|
@ -479,7 +479,7 @@ impl Fsm for SelectToolFsmState {
|
|||
}
|
||||
// Only highlight layers if the viewport is not being panned (middle mouse button is pressed)
|
||||
// TODO: Don't use `Key::Mmb` directly, instead take it as a variable from the input mappings list like in all other places
|
||||
else if !input.keyboard.get(Key::Mmb as usize) {
|
||||
else if !input.keyboard.get(Key::MouseMiddle as usize) {
|
||||
// Get the layer the user is hovering over
|
||||
let click = document.click(input);
|
||||
let not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes(&[]).unwrap().selected_layers_contains(hovered_layer, document.metadata()));
|
||||
|
@ -649,7 +649,6 @@ impl Fsm for SelectToolFsmState {
|
|||
}
|
||||
|
||||
if let Some(intersection) = intersection {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data.layer_selected_on_start = Some(intersection);
|
||||
selected = intersection_list;
|
||||
|
@ -659,6 +658,8 @@ impl Fsm for SelectToolFsmState {
|
|||
_ => drag_deepest_manipulation(responses, selected, tool_data, document),
|
||||
}
|
||||
tool_data.get_snap_candidates(document, input);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
SelectToolFsmState::Dragging
|
||||
} else {
|
||||
// Deselect all layers if using shallowest selection behavior
|
||||
|
@ -894,8 +895,8 @@ impl Fsm for SelectToolFsmState {
|
|||
}
|
||||
(SelectToolFsmState::Dragging, SelectToolMessage::Enter) => {
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
true => DocumentMessage::AbortTransaction,
|
||||
false => DocumentMessage::EndTransaction,
|
||||
};
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
responses.add_front(response);
|
||||
|
@ -905,6 +906,8 @@ impl Fsm for SelectToolFsmState {
|
|||
}
|
||||
(SelectToolFsmState::Dragging, SelectToolMessage::DragStop { remove_from_selection }) => {
|
||||
// Deselect layer if not snap dragging
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
|
||||
if !tool_data.has_dragged && input.keyboard.key(remove_from_selection) && tool_data.layer_selected_on_start.is_none() {
|
||||
let quad = tool_data.selection_quad();
|
||||
let intersection = document.intersect_quad(quad, input);
|
||||
|
@ -950,7 +953,6 @@ impl Fsm for SelectToolFsmState {
|
|||
tool_data.has_dragged = false;
|
||||
tool_data.layer_selected_on_start = None;
|
||||
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
tool_data.select_single_layer = None;
|
||||
|
||||
|
@ -959,8 +961,8 @@ impl Fsm for SelectToolFsmState {
|
|||
}
|
||||
(SelectToolFsmState::ResizingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
true => DocumentMessage::AbortTransaction,
|
||||
false => DocumentMessage::EndTransaction,
|
||||
};
|
||||
responses.add(response);
|
||||
|
||||
|
@ -975,8 +977,8 @@ impl Fsm for SelectToolFsmState {
|
|||
}
|
||||
(SelectToolFsmState::RotatingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
true => DocumentMessage::AbortTransaction,
|
||||
false => DocumentMessage::EndTransaction,
|
||||
};
|
||||
responses.add(response);
|
||||
|
||||
|
@ -989,8 +991,8 @@ impl Fsm for SelectToolFsmState {
|
|||
}
|
||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
true => DocumentMessage::AbortTransaction,
|
||||
false => DocumentMessage::EndTransaction,
|
||||
};
|
||||
responses.add(response);
|
||||
|
||||
|
@ -1005,7 +1007,6 @@ impl Fsm for SelectToolFsmState {
|
|||
let current_selected: HashSet<_> = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect();
|
||||
if new_selected != current_selected {
|
||||
tool_data.layers_dragging = new_selected.into_iter().collect();
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||
nodes: tool_data
|
||||
.layers_dragging
|
||||
|
@ -1027,7 +1028,8 @@ impl Fsm for SelectToolFsmState {
|
|||
SelectToolFsmState::Ready { selection }
|
||||
}
|
||||
(SelectToolFsmState::Ready { .. }, SelectToolMessage::Enter) => {
|
||||
let mut selected_layers = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata());
|
||||
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
|
||||
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
|
||||
|
||||
if let Some(layer) = selected_layers.next() {
|
||||
// Check that only one layer is selected
|
||||
|
@ -1041,8 +1043,8 @@ impl Fsm for SelectToolFsmState {
|
|||
SelectToolFsmState::Ready { selection }
|
||||
}
|
||||
(SelectToolFsmState::Dragging, SelectToolMessage::Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
responses.add(DocumentMessage::Undo);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
let selection = tool_data.nested_selection_behavior;
|
||||
|
@ -1200,11 +1202,9 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec
|
|||
}
|
||||
|
||||
fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) {
|
||||
tool_data.layers_dragging.append(&mut vec![document.find_deepest(&selected).unwrap_or(LayerNodeIdentifier::new(
|
||||
document.network_interface.root_node(&[]).expect("Root node should exist when dragging layers").node_id,
|
||||
&document.network_interface,
|
||||
&[],
|
||||
))]);
|
||||
tool_data.layers_dragging.append(&mut vec![document
|
||||
.find_deepest(&selected)
|
||||
.unwrap_or(LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()).next().expect("Child should exist when dragging deepest"))]);
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||
nodes: tool_data
|
||||
.layers_dragging
|
||||
|
|
|
@ -228,6 +228,8 @@ impl Fsm for SplineToolFsmState {
|
|||
SplineToolFsmState::Drawing
|
||||
}
|
||||
(SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
|
||||
let Some(layer) = tool_data.layer else {
|
||||
return SplineToolFsmState::Ready;
|
||||
};
|
||||
|
@ -279,7 +281,7 @@ impl Fsm for SplineToolFsmState {
|
|||
(SplineToolFsmState::Drawing, SplineToolMessage::Confirm | SplineToolMessage::Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
update_spline(document, tool_data, false, responses);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
|
|
|
@ -259,7 +259,7 @@ impl TextToolData {
|
|||
self.layer = layer;
|
||||
self.load_layer_text_node(document);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
self.set_editing(true, font_cache, document, responses);
|
||||
|
||||
|
@ -282,7 +282,7 @@ impl TextToolData {
|
|||
}
|
||||
// Create new text
|
||||
else if let Some(editing_text) = self.editing_text.as_ref().filter(|_| state == TextToolFsmState::Ready) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
self.layer = LayerNodeIdentifier::new_unchecked(NodeId(generate_uuid()));
|
||||
|
||||
|
@ -320,7 +320,8 @@ impl TextToolData {
|
|||
}
|
||||
|
||||
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
|
||||
let mut selected_layers = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata());
|
||||
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
|
||||
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
|
||||
let layer = selected_layers.next()?;
|
||||
|
||||
// Check that only one layer is selected
|
||||
|
|
|
@ -23,7 +23,7 @@ pub trait EditorTestUtils {
|
|||
fn move_mouse(&mut self, x: f64, y: f64);
|
||||
fn mousedown(&mut self, state: EditorMouseState);
|
||||
fn mouseup(&mut self, state: EditorMouseState);
|
||||
fn lmb_mousedown(&mut self, x: f64, y: f64);
|
||||
fn left_mousedown(&mut self, x: f64, y: f64);
|
||||
fn input(&mut self, message: InputPreprocessorMessage);
|
||||
fn select_tool(&mut self, typ: ToolType);
|
||||
fn select_primary_color(&mut self, color: Color);
|
||||
|
@ -63,7 +63,7 @@ impl EditorTestUtils for Editor {
|
|||
fn drag_tool(&mut self, typ: ToolType, x1: f64, y1: f64, x2: f64, y2: f64) {
|
||||
self.select_tool(typ);
|
||||
self.move_mouse(x1, y1);
|
||||
self.lmb_mousedown(x1, y1);
|
||||
self.left_mousedown(x1, y1);
|
||||
self.move_mouse(x2, y2);
|
||||
self.mouseup(EditorMouseState {
|
||||
editor_position: (x2, y2).into(),
|
||||
|
@ -91,7 +91,7 @@ impl EditorTestUtils for Editor {
|
|||
self.handle_message(InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys });
|
||||
}
|
||||
|
||||
fn lmb_mousedown(&mut self, x: f64, y: f64) {
|
||||
fn left_mousedown(&mut self, x: f64, y: f64) {
|
||||
self.mousedown(EditorMouseState {
|
||||
editor_position: (x, y).into(),
|
||||
mouse_keys: MouseKeys::LEFT,
|
||||
|
|
|
@ -182,6 +182,12 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
}
|
||||
|
||||
function onPointerUp(e: PointerEvent) {
|
||||
// Don't let the browser navigate back or forward when using the buttons on some mice
|
||||
// TODO: This works in Chrome but not in Firefox
|
||||
// TODO: Possible workaround: use the browser's history API to block navigation:
|
||||
// TODO: <https://stackoverflow.com/questions/57102502/preventing-mouse-fourth-and-fifth-buttons-from-navigating-back-forward-in-browse>
|
||||
if (e.button === 3 || e.button === 4) e.preventDefault();
|
||||
|
||||
if (!e.buttons) viewportPointerInteractionOngoing = false;
|
||||
|
||||
if (textToolInteractiveInputElement) return;
|
||||
|
@ -198,9 +204,11 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
|
||||
// `e.buttons` is always 0 in the `mouseup` event, so we have to convert from `e.button` instead
|
||||
let buttons = 1;
|
||||
if (e.button === 0) buttons = 1; // LMB
|
||||
if (e.button === 1) buttons = 4; // MMB
|
||||
if (e.button === 2) buttons = 2; // RMB
|
||||
if (e.button === 0) buttons = 1; // Left
|
||||
if (e.button === 2) buttons = 2; // Right
|
||||
if (e.button === 1) buttons = 4; // Middle
|
||||
if (e.button === 3) buttons = 8; // Back
|
||||
if (e.button === 4) buttons = 16; // Forward
|
||||
|
||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||
editor.handle.onDoubleClick(e.clientX, e.clientY, buttons, modifiers);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue