mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-08 00:05: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
|
||||||
"rust-lang.rust-analyzer",
|
"rust-lang.rust-analyzer",
|
||||||
"tamasfe.even-better-toml",
|
"tamasfe.even-better-toml",
|
||||||
"serayuzgur.crates",
|
|
||||||
// Web
|
// Web
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"svelte.svelte-vscode",
|
"svelte.svelte-vscode",
|
||||||
|
@ -16,6 +15,6 @@
|
||||||
"mhutchie.git-graph",
|
"mhutchie.git-graph",
|
||||||
"waderyan.gitblame",
|
"waderyan.gitblame",
|
||||||
"qezhu.gitlink",
|
"qezhu.gitlink",
|
||||||
"wmaurer.change-case",
|
"wmaurer.change-case"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,31 +37,31 @@ pub fn input_mappings() -> Mapping {
|
||||||
//
|
//
|
||||||
// NavigationMessage
|
// NavigationMessage
|
||||||
entry!(PointerMove; refresh_keys=[Control], action_dispatch=NavigationMessage::PointerMove { snap: Control }),
|
entry!(PointerMove; refresh_keys=[Control], action_dispatch=NavigationMessage::PointerMove { snap: Control }),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
entry!(KeyUp(MouseLeft); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
||||||
entry!(KeyUp(Mmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
entry!(KeyUp(MouseMiddle); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
||||||
entry!(KeyUp(Rmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
entry!(KeyUp(MouseRight); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: true }),
|
entry!(KeyDown(MouseRight); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: true }),
|
||||||
entry!(KeyDown(Escape); 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(MouseLeft); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: MouseLeft }),
|
||||||
entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: Mmb }),
|
entry!(KeyDown(MouseMiddle); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: MouseMiddle }),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: Rmb }),
|
entry!(KeyDown(MouseRight); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: MouseRight }),
|
||||||
//
|
//
|
||||||
// ===============
|
// ===============
|
||||||
// NORMAL PRIORITY
|
// NORMAL PRIORITY
|
||||||
// ===============
|
// ===============
|
||||||
//
|
//
|
||||||
// Hack to prevent LMB + CTRL (OPTION) + Z combo (this effectively blocks you from making a double undo with AbortTransaction)
|
// Hack to prevent Left Click + Accel + Z combo (this effectively blocks you from making a double undo with AbortTransaction)
|
||||||
entry!(KeyDown(KeyZ); modifiers=[Accel, Lmb], action_dispatch=DocumentMessage::Noop),
|
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
|
||||||
// NodeGraphMessage
|
// NodeGraphMessage
|
||||||
entry!(KeyDown(Lmb); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: false}),
|
entry!(KeyDown(MouseLeft); 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(MouseLeft); 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(MouseLeft); 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(MouseLeft); 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(MouseLeft); 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(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!(DoubleClick(MouseButton::Left); action_dispatch=NodeGraphMessage::EnterNestedNetwork),
|
||||||
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove {shift: Shift}),
|
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(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
|
||||||
entry!(KeyDown(Backspace); 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 }),
|
entry!(KeyDown(Delete); action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: true }),
|
||||||
|
@ -82,8 +82,8 @@ pub fn input_mappings() -> Mapping {
|
||||||
//
|
//
|
||||||
// TransformLayerMessage
|
// TransformLayerMessage
|
||||||
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||||
entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
entry!(KeyDown(MouseLeft); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
entry!(KeyDown(MouseRight); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
||||||
entry!(KeyDown(Escape); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
entry!(KeyDown(Escape); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
||||||
entry!(KeyDown(KeyX); action_dispatch=TransformLayerMessage::ConstrainX),
|
entry!(KeyDown(KeyX); action_dispatch=TransformLayerMessage::ConstrainX),
|
||||||
entry!(KeyDown(KeyY); action_dispatch=TransformLayerMessage::ConstrainY),
|
entry!(KeyDown(KeyY); action_dispatch=TransformLayerMessage::ConstrainY),
|
||||||
|
@ -95,17 +95,17 @@ pub fn input_mappings() -> Mapping {
|
||||||
//
|
//
|
||||||
// SelectToolMessage
|
// SelectToolMessage
|
||||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove(SelectToolPointerKeys { axis_align: Shift, snap_angle: Control, center: Alt, duplicate: Alt })),
|
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!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { add_to_selection: Shift, select_deepest: Accel }),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Shift }),
|
entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Shift }),
|
||||||
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
|
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
|
||||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=SelectToolMessage::EditLayer),
|
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),
|
entry!(KeyDown(Escape); action_dispatch=SelectToolMessage::Abort),
|
||||||
//
|
//
|
||||||
// ArtboardToolMessage
|
// 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!(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(Delete); action_dispatch=ArtboardToolMessage::DeleteSelected),
|
||||||
entry!(KeyDown(Backspace); 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 }),
|
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=[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); 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(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),
|
entry!(KeyDown(Escape); action_dispatch=ArtboardToolMessage::Abort),
|
||||||
//
|
//
|
||||||
// NavigateToolMessage
|
// NavigateToolMessage
|
||||||
entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin),
|
entry!(KeyDown(MouseLeft); action_dispatch=NavigateToolMessage::ZoomCanvasBegin),
|
||||||
entry!(KeyDown(Lmb); modifiers=[Alt], action_dispatch=NavigateToolMessage::TiltCanvasBegin),
|
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NavigateToolMessage::TiltCanvasBegin),
|
||||||
entry!(PointerMove; refresh_keys=[Control], action_dispatch=NavigateToolMessage::PointerMove { snap: Control }),
|
entry!(PointerMove; refresh_keys=[Control], action_dispatch=NavigateToolMessage::PointerMove { snap: Control }),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::PointerUp { zoom_in: true }),
|
entry!(KeyUp(MouseLeft); action_dispatch=NavigateToolMessage::PointerUp { zoom_in: true }),
|
||||||
entry!(KeyUp(Lmb); modifiers=[Shift], action_dispatch=NavigateToolMessage::PointerUp { zoom_in: false }),
|
entry!(KeyUp(MouseLeft); modifiers=[Shift], action_dispatch=NavigateToolMessage::PointerUp { zoom_in: false }),
|
||||||
//
|
//
|
||||||
// EyedropperToolMessage
|
// EyedropperToolMessage
|
||||||
entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::SamplePrimaryColorBegin),
|
entry!(KeyDown(MouseLeft); action_dispatch=EyedropperToolMessage::SamplePrimaryColorBegin),
|
||||||
entry!(KeyDown(Lmb); modifiers=[Shift], action_dispatch=EyedropperToolMessage::SampleSecondaryColorBegin),
|
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=EyedropperToolMessage::SampleSecondaryColorBegin),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=EyedropperToolMessage::SamplePrimaryColorEnd),
|
entry!(KeyUp(MouseLeft); action_dispatch=EyedropperToolMessage::SamplePrimaryColorEnd),
|
||||||
entry!(KeyUp(Lmb); modifiers=[Shift], action_dispatch=EyedropperToolMessage::SampleSecondaryColorEnd),
|
entry!(KeyUp(MouseLeft); modifiers=[Shift], action_dispatch=EyedropperToolMessage::SampleSecondaryColorEnd),
|
||||||
entry!(PointerMove; action_dispatch=EyedropperToolMessage::PointerMove),
|
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),
|
entry!(KeyDown(Escape); action_dispatch=EyedropperToolMessage::Abort),
|
||||||
//
|
//
|
||||||
// TextToolMessage
|
// TextToolMessage
|
||||||
entry!(KeyUp(Lmb); action_dispatch=TextToolMessage::Interact),
|
entry!(KeyUp(MouseLeft); action_dispatch=TextToolMessage::Interact),
|
||||||
entry!(KeyDown(Escape); action_dispatch=TextToolMessage::Abort),
|
entry!(KeyDown(Escape); action_dispatch=TextToolMessage::Abort),
|
||||||
entry!(KeyDown(Enter); modifiers=[Accel], action_dispatch=TextToolMessage::CommitText),
|
entry!(KeyDown(Enter); modifiers=[Accel], action_dispatch=TextToolMessage::CommitText),
|
||||||
//
|
//
|
||||||
// GradientToolMessage
|
// 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!(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!(DoubleClick(MouseButton::Left); action_dispatch=GradientToolMessage::InsertStop),
|
||||||
entry!(KeyDown(Delete); action_dispatch=GradientToolMessage::DeleteStop),
|
entry!(KeyDown(Delete); action_dispatch=GradientToolMessage::DeleteStop),
|
||||||
entry!(KeyDown(Backspace); 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),
|
entry!(KeyDown(Escape); action_dispatch=GradientToolMessage::Abort),
|
||||||
//
|
//
|
||||||
// RectangleToolMessage
|
// RectangleToolMessage
|
||||||
entry!(KeyDown(Lmb); action_dispatch=RectangleToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=RectangleToolMessage::DragStart),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=RectangleToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=RectangleToolMessage::DragStop),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=RectangleToolMessage::Abort),
|
entry!(KeyDown(MouseRight); action_dispatch=RectangleToolMessage::Abort),
|
||||||
entry!(KeyDown(Escape); 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 }),
|
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=RectangleToolMessage::PointerMove { center: Alt, lock_ratio: Shift }),
|
||||||
//
|
//
|
||||||
// ImaginateToolMessage
|
// ImaginateToolMessage
|
||||||
entry!(KeyDown(Lmb); action_dispatch=ImaginateToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=ImaginateToolMessage::DragStart),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=ImaginateToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=ImaginateToolMessage::DragStop),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=ImaginateToolMessage::Abort),
|
entry!(KeyDown(MouseRight); action_dispatch=ImaginateToolMessage::Abort),
|
||||||
entry!(KeyDown(Escape); 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 }),
|
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=ImaginateToolMessage::Resize { center: Alt, lock_ratio: Shift }),
|
||||||
//
|
//
|
||||||
// EllipseToolMessage
|
// EllipseToolMessage
|
||||||
entry!(KeyDown(Lmb); action_dispatch=EllipseToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=EllipseToolMessage::DragStart),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=EllipseToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=EllipseToolMessage::DragStop),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=EllipseToolMessage::Abort),
|
entry!(KeyDown(MouseRight); action_dispatch=EllipseToolMessage::Abort),
|
||||||
entry!(KeyDown(Escape); 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 }),
|
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=EllipseToolMessage::PointerMove { center: Alt, lock_ratio: Shift }),
|
||||||
//
|
//
|
||||||
// PolygonToolMessage
|
// PolygonToolMessage
|
||||||
entry!(KeyDown(Lmb); action_dispatch=PolygonToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=PolygonToolMessage::DragStart),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=PolygonToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=PolygonToolMessage::DragStop),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=PolygonToolMessage::Abort),
|
entry!(KeyDown(MouseRight); action_dispatch=PolygonToolMessage::Abort),
|
||||||
entry!(KeyDown(Escape); 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 }),
|
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=PolygonToolMessage::PointerMove { center: Alt, lock_ratio: Shift }),
|
||||||
//
|
//
|
||||||
// LineToolMessage
|
// LineToolMessage
|
||||||
entry!(KeyDown(Lmb); action_dispatch=LineToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=LineToolMessage::DragStart),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=LineToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=LineToolMessage::DragStop),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=LineToolMessage::Abort),
|
entry!(KeyDown(MouseRight); action_dispatch=LineToolMessage::Abort),
|
||||||
entry!(KeyDown(Escape); 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 }),
|
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(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
|
||||||
entry!(KeyDown(Delete); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::BreakPath),
|
entry!(KeyDown(Delete); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||||
entry!(KeyDown(Backspace); 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(MouseLeft); action_dispatch=PathToolMessage::MouseDown { ctrl: Control, shift: Shift }),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=PathToolMessage::RightClick),
|
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
|
||||||
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
|
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
|
||||||
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
|
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
|
||||||
entry!(KeyDown(KeyR); action_dispatch=PathToolMessage::GRS { key: KeyR }),
|
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], action_dispatch=PathToolMessage::SelectAllAnchors),
|
||||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints),
|
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||||
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
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!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { add_to_selection: Shift }),
|
||||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::FlipSmoothSharp),
|
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::FlipSmoothSharp),
|
||||||
entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
||||||
|
@ -247,41 +247,41 @@ pub fn input_mappings() -> Mapping {
|
||||||
//
|
//
|
||||||
// PenToolMessage
|
// PenToolMessage
|
||||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control}),
|
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!(KeyDown(MouseLeft); action_dispatch=PenToolMessage::DragStart),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=PenToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=PenToolMessage::DragStop),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=PenToolMessage::Confirm),
|
entry!(KeyDown(MouseRight); action_dispatch=PenToolMessage::Confirm),
|
||||||
entry!(KeyDown(Escape); action_dispatch=PenToolMessage::Confirm),
|
entry!(KeyDown(Escape); action_dispatch=PenToolMessage::Confirm),
|
||||||
entry!(KeyDown(Enter); action_dispatch=PenToolMessage::Confirm),
|
entry!(KeyDown(Enter); action_dispatch=PenToolMessage::Confirm),
|
||||||
//
|
//
|
||||||
// FreehandToolMessage
|
// FreehandToolMessage
|
||||||
entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),
|
entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),
|
||||||
entry!(KeyDown(Lmb); action_dispatch=FreehandToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=FreehandToolMessage::DragStart),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=FreehandToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=FreehandToolMessage::DragStop),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=FreehandToolMessage::Abort),
|
entry!(KeyDown(MouseRight); action_dispatch=FreehandToolMessage::Abort),
|
||||||
entry!(KeyDown(Escape); action_dispatch=FreehandToolMessage::Abort),
|
entry!(KeyDown(Escape); action_dispatch=FreehandToolMessage::Abort),
|
||||||
//
|
//
|
||||||
// SplineToolMessage
|
// SplineToolMessage
|
||||||
entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove),
|
entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove),
|
||||||
entry!(KeyDown(Lmb); action_dispatch=SplineToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=SplineToolMessage::DragStart),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=SplineToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=SplineToolMessage::DragStop),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=SplineToolMessage::Confirm),
|
entry!(KeyDown(MouseRight); action_dispatch=SplineToolMessage::Confirm),
|
||||||
entry!(KeyDown(Escape); action_dispatch=SplineToolMessage::Confirm),
|
entry!(KeyDown(Escape); action_dispatch=SplineToolMessage::Confirm),
|
||||||
entry!(KeyDown(Enter); action_dispatch=SplineToolMessage::Confirm),
|
entry!(KeyDown(Enter); action_dispatch=SplineToolMessage::Confirm),
|
||||||
//
|
//
|
||||||
// FillToolMessage
|
// FillToolMessage
|
||||||
entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::FillPrimaryColor),
|
entry!(KeyDown(MouseLeft); action_dispatch=FillToolMessage::FillPrimaryColor),
|
||||||
entry!(KeyDown(Lmb); modifiers=[Shift], action_dispatch=FillToolMessage::FillSecondaryColor),
|
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=FillToolMessage::FillSecondaryColor),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=FillToolMessage::PointerUp),
|
entry!(KeyUp(MouseLeft); action_dispatch=FillToolMessage::PointerUp),
|
||||||
entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::Abort),
|
entry!(KeyDown(MouseRight); action_dispatch=FillToolMessage::Abort),
|
||||||
entry!(KeyDown(Escape); action_dispatch=FillToolMessage::Abort),
|
entry!(KeyDown(Escape); action_dispatch=FillToolMessage::Abort),
|
||||||
//
|
//
|
||||||
// BrushToolMessage
|
// BrushToolMessage
|
||||||
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
|
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
|
||||||
entry!(KeyDown(Lmb); action_dispatch=BrushToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=BrushToolMessage::DragStart),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=BrushToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=BrushToolMessage::DragStop),
|
||||||
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD))),
|
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(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),
|
entry!(KeyDown(Escape); action_dispatch=BrushToolMessage::Abort),
|
||||||
//
|
//
|
||||||
// ToolMessage
|
// ToolMessage
|
||||||
|
@ -323,6 +323,10 @@ pub fn input_mappings() -> Mapping {
|
||||||
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers),
|
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers),
|
||||||
entry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
|
entry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
|
||||||
entry!(KeyDown(KeyN); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder),
|
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(Digit0); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
|
||||||
entry!(KeyDown(Digit1); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasTo100Percent),
|
entry!(KeyDown(Digit1); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasTo100Percent),
|
||||||
entry!(KeyDown(Digit2); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasTo200Percent),
|
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 }),
|
entry!(KeyDown(Digit9); action_dispatch=TransformLayerMessage::TypeDigit { digit: 9 }),
|
||||||
//
|
//
|
||||||
// NavigationMessage
|
// NavigationMessage
|
||||||
entry!(KeyDown(Mmb); modifiers=[Alt], action_dispatch=NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }),
|
entry!(KeyDown(MouseMiddle); modifiers=[Alt], action_dispatch=NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }),
|
||||||
entry!(KeyDown(Mmb); modifiers=[Shift], action_dispatch=NavigationMessage::BeginCanvasZoom),
|
entry!(KeyDown(MouseMiddle); modifiers=[Shift], action_dispatch=NavigationMessage::BeginCanvasZoom),
|
||||||
entry!(KeyDown(Lmb); modifiers=[Shift, Space], action_dispatch=NavigationMessage::BeginCanvasZoom),
|
entry!(KeyDown(MouseLeft); modifiers=[Shift, Space], action_dispatch=NavigationMessage::BeginCanvasZoom),
|
||||||
entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::BeginCanvasPan),
|
entry!(KeyDown(MouseMiddle); action_dispatch=NavigationMessage::BeginCanvasPan),
|
||||||
entry!(KeyDown(Lmb); modifiers=[Space], 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(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(Equal); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),
|
||||||
entry!(KeyDown(Minus); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomDecrease { 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,
|
Command,
|
||||||
/// "Ctrl" on Windows/Linux, "Cmd" on Mac
|
/// "Ctrl" on Windows/Linux, "Cmd" on Mac
|
||||||
Accel,
|
Accel,
|
||||||
Lmb,
|
MouseLeft,
|
||||||
Rmb,
|
MouseRight,
|
||||||
Mmb,
|
MouseMiddle,
|
||||||
|
MouseBack,
|
||||||
|
MouseForward,
|
||||||
|
|
||||||
// This has to be the last element in the enum
|
// This has to be the last element in the enum
|
||||||
NumKeys,
|
NumKeys,
|
||||||
|
|
|
@ -90,7 +90,7 @@ impl MouseState {
|
||||||
pub fn finish_transaction(&self, drag_start: DVec2, responses: &mut VecDeque<Message>) {
|
pub fn finish_transaction(&self, drag_start: DVec2, responses: &mut VecDeque<Message>) {
|
||||||
match drag_start.distance(self.position) <= DRAG_THRESHOLD {
|
match drag_start.distance(self.position) <= DRAG_THRESHOLD {
|
||||||
true => responses.add(DocumentMessage::AbortTransaction),
|
true => responses.add(DocumentMessage::AbortTransaction),
|
||||||
false => responses.add(DocumentMessage::CommitTransaction),
|
false => responses.add(DocumentMessage::EndTransaction),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,6 +144,8 @@ pub enum MouseButton {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Middle,
|
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::LEFT => MouseButton::Left,
|
||||||
MouseKeys::RIGHT => MouseButton::Right,
|
MouseKeys::RIGHT => MouseButton::Right,
|
||||||
MouseKeys::MIDDLE => MouseButton::Middle,
|
MouseKeys::MIDDLE => MouseButton::Middle,
|
||||||
|
MouseKeys::BACK => MouseButton::Back,
|
||||||
|
MouseKeys::FORWARD => MouseButton::Forward,
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -115,7 +117,15 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for
|
||||||
|
|
||||||
impl InputPreprocessorMessageHandler {
|
impl InputPreprocessorMessageHandler {
|
||||||
fn translate_mouse_event(&mut self, mut new_state: MouseState, allow_first_button_down: bool, responses: &mut VecDeque<Message>) {
|
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
|
// Calculate the intersection between the two key states
|
||||||
let old_down = self.mouse.mouse_keys & bit_flag == bit_flag;
|
let old_down = self.mouse.mouse_keys & bit_flag == bit_flag;
|
||||||
let new_down = new_state.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::misc::SnappingState;
|
||||||
use super::utility_types::network_interface::NodeNetworkInterface;
|
|
||||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
|
@ -32,17 +31,12 @@ pub enum DocumentMessage {
|
||||||
PropertiesPanel(PropertiesPanelMessage),
|
PropertiesPanel(PropertiesPanelMessage),
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
AbortTransaction,
|
|
||||||
AlignSelectedLayers {
|
AlignSelectedLayers {
|
||||||
axis: AlignAxis,
|
axis: AlignAxis,
|
||||||
aggregate: AlignAggregate,
|
aggregate: AlignAggregate,
|
||||||
},
|
},
|
||||||
BackupDocument {
|
|
||||||
network_interface: NodeNetworkInterface,
|
|
||||||
},
|
|
||||||
ClearArtboards,
|
ClearArtboards,
|
||||||
ClearLayersPanel,
|
ClearLayersPanel,
|
||||||
CommitTransaction,
|
|
||||||
InsertBooleanOperation {
|
InsertBooleanOperation {
|
||||||
operation: graphene_core::vector::misc::BooleanOperation,
|
operation: graphene_core::vector::misc::BooleanOperation,
|
||||||
},
|
},
|
||||||
|
@ -151,6 +145,10 @@ pub enum DocumentMessage {
|
||||||
view_mode: ViewMode,
|
view_mode: ViewMode,
|
||||||
},
|
},
|
||||||
StartTransaction,
|
StartTransaction,
|
||||||
|
EndTransaction,
|
||||||
|
CommitTransaction,
|
||||||
|
AbortTransaction,
|
||||||
|
AddTransaction,
|
||||||
ToggleLayerExpansion {
|
ToggleLayerExpansion {
|
||||||
id: NodeId,
|
id: NodeId,
|
||||||
},
|
},
|
||||||
|
@ -158,12 +156,13 @@ pub enum DocumentMessage {
|
||||||
ToggleOverlaysVisibility,
|
ToggleOverlaysVisibility,
|
||||||
ToggleSnapping,
|
ToggleSnapping,
|
||||||
Undo,
|
Undo,
|
||||||
UndoFinished,
|
|
||||||
UngroupSelectedLayers,
|
UngroupSelectedLayers,
|
||||||
UngroupLayer {
|
UngroupLayer {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
},
|
},
|
||||||
PTZUpdate,
|
PTZUpdate,
|
||||||
|
SelectionStepBack,
|
||||||
|
SelectionStepForward,
|
||||||
ZoomCanvasTo100Percent,
|
ZoomCanvasTo100Percent,
|
||||||
ZoomCanvasTo200Percent,
|
ZoomCanvasTo200Percent,
|
||||||
ZoomCanvasToFitAll,
|
ZoomCanvasToFitAll,
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::node_graph::utility_types::Transform;
|
||||||
use super::utility_types::clipboards::Clipboard;
|
use super::utility_types::clipboards::Clipboard;
|
||||||
use super::utility_types::error::EditorError;
|
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::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 super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
|
||||||
use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH};
|
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};
|
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.
|
/// 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)]
|
#[serde(skip)]
|
||||||
auto_saved_hash: Option<u64>,
|
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.
|
/// 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.
|
/// 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)]
|
#[serde(skip)]
|
||||||
|
@ -186,7 +183,6 @@ impl Default for DocumentMessageHandler {
|
||||||
document_redo_history: VecDeque::new(),
|
document_redo_history: VecDeque::new(),
|
||||||
saved_hash: None,
|
saved_hash: None,
|
||||||
auto_saved_hash: None,
|
auto_saved_hash: None,
|
||||||
undo_in_progress: false,
|
|
||||||
layer_range_selection_reference: None,
|
layer_range_selection_reference: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,17 +255,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
let mut graph_operation_message_handler = GraphOperationMessageHandler {};
|
let mut graph_operation_message_handler = GraphOperationMessageHandler {};
|
||||||
graph_operation_message_handler.process_message(message, responses, data);
|
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 } => {
|
DocumentMessage::AlignSelectedLayers { axis, aggregate } => {
|
||||||
self.backup(responses);
|
|
||||||
|
|
||||||
let axis = match axis {
|
let axis = match axis {
|
||||||
AlignAxis::X => DVec2::X,
|
AlignAxis::X => DVec2::X,
|
||||||
AlignAxis::Y => DVec2::Y,
|
AlignAxis::Y => DVec2::Y,
|
||||||
|
@ -283,6 +269,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
AlignAggregate::Max => combined_box[1],
|
AlignAggregate::Max => combined_box[1],
|
||||||
AlignAggregate::Center => (combined_box[0] + combined_box[1]) / 2.,
|
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) {
|
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 {
|
let Some(bbox) = self.metadata().bounding_box_viewport(layer) else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -293,6 +281,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
_ => (bbox[0] + bbox[1]) / 2.,
|
_ => (bbox[0] + bbox[1]) / 2.,
|
||||||
};
|
};
|
||||||
let translation = (aggregated - center) * axis;
|
let translation = (aggregated - center) * axis;
|
||||||
|
if !added_transaction {
|
||||||
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
added_transaction = true;
|
||||||
|
}
|
||||||
responses.add(GraphOperationMessage::TransformChange {
|
responses.add(GraphOperationMessage::TransformChange {
|
||||||
layer,
|
layer,
|
||||||
transform: DAffine2::from_translation(translation),
|
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 => {
|
DocumentMessage::ClearArtboards => {
|
||||||
self.backup(responses);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(GraphOperationMessage::ClearArtboards);
|
responses.add(GraphOperationMessage::ClearArtboards);
|
||||||
}
|
}
|
||||||
DocumentMessage::ClearLayersPanel => {
|
DocumentMessage::ClearLayersPanel => {
|
||||||
|
@ -317,9 +308,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
layout_target: LayoutTarget::LayersPanelOptions,
|
layout_target: LayoutTarget::LayersPanelOptions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
DocumentMessage::CommitTransaction => (),
|
|
||||||
DocumentMessage::InsertBooleanOperation { operation } => {
|
DocumentMessage::InsertBooleanOperation { operation } => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
let Some(parent) = self.network_interface.deepest_common_ancestor(&[], false) else {
|
let Some(parent) = self.network_interface.deepest_common_ancestor(&[], false) else {
|
||||||
// Cancel grouping layers across different artboards
|
// 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);
|
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 {
|
responses.add(GraphOperationMessage::NewCustomLayer {
|
||||||
id,
|
id,
|
||||||
nodes: Vec::new(),
|
nodes: Vec::new(),
|
||||||
|
@ -386,7 +376,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
let calculated_insert_index =
|
let calculated_insert_index =
|
||||||
DocumentMessageHandler::get_calculated_insert_index(self.network_interface.document_metadata(), self.network_interface.selected_nodes(&[]).unwrap(), parent);
|
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::Copy { clipboard: Clipboard::Internal });
|
||||||
responses.add(PortfolioMessage::PasteIntoFolder {
|
responses.add(PortfolioMessage::PasteIntoFolder {
|
||||||
clipboard: Clipboard::Internal,
|
clipboard: Clipboard::Internal,
|
||||||
|
@ -430,7 +420,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
}
|
}
|
||||||
DocumentMessage::FlipSelectedLayers { flip_axis } => {
|
DocumentMessage::FlipSelectedLayers { flip_axis } => {
|
||||||
self.backup(responses);
|
|
||||||
let scale = match flip_axis {
|
let scale = match flip_axis {
|
||||||
FlipAxis::X => DVec2::new(-1., 1.),
|
FlipAxis::X => DVec2::new(-1., 1.),
|
||||||
FlipAxis::Y => 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() {
|
if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() {
|
||||||
let center = (max + min) / 2.;
|
let center = (max + min) / 2.;
|
||||||
let bbox_trans = DAffine2::from_translation(-center);
|
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) {
|
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 {
|
responses.add(GraphOperationMessage::TransformChange {
|
||||||
layer,
|
layer,
|
||||||
transform: DAffine2::from_scale(scale),
|
transform: DAffine2::from_scale(scale),
|
||||||
|
@ -481,7 +475,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
DocumentMessage::GroupSelectedLayers => {
|
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 {
|
let Some(parent) = self.network_interface.deepest_common_ancestor(&self.selection_network_path, false) else {
|
||||||
// Cancel grouping layers across different artboards
|
// Cancel grouping layers across different artboards
|
||||||
|
@ -525,7 +519,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
let random_bits = generate_uuid();
|
let random_bits = generate_uuid();
|
||||||
let random_value = ((random_bits >> 11) as f64).copysign(f64::from_bits(random_bits & (1 << 63)));
|
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
|
// Set a random seed input
|
||||||
responses.add(NodeGraphMessage::SetInputValue {
|
responses.add(NodeGraphMessage::SetInputValue {
|
||||||
node_id: *imaginate_node.last().unwrap(),
|
node_id: *imaginate_node.last().unwrap(),
|
||||||
|
@ -546,7 +540,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
parent,
|
parent,
|
||||||
insert_index,
|
insert_index,
|
||||||
} => {
|
} => {
|
||||||
self.backup(responses);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
responses.add(GraphOperationMessage::NewSvg {
|
responses.add(GraphOperationMessage::NewSvg {
|
||||||
id,
|
id,
|
||||||
svg,
|
svg,
|
||||||
|
@ -554,6 +548,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
parent,
|
parent,
|
||||||
insert_index,
|
insert_index,
|
||||||
});
|
});
|
||||||
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
}
|
}
|
||||||
DocumentMessage::MoveSelectedLayersTo { parent, insert_index } => {
|
DocumentMessage::MoveSelectedLayersTo { parent, insert_index } => {
|
||||||
if !self.selection_network_path.is_empty() {
|
if !self.selection_network_path.is_empty() {
|
||||||
|
@ -561,8 +556,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
|
|
||||||
// Disallow trying to insert into self.
|
// Disallow trying to insert into self.
|
||||||
if self
|
if self
|
||||||
.network_interface
|
.network_interface
|
||||||
|
@ -625,6 +618,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
for (layer_index, (layer_to_move, insert_offset)) in layers_to_move_with_insert_offset.into_iter().enumerate() {
|
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;
|
let calculated_insert_index = insert_index + layer_index - insert_offset;
|
||||||
responses.add(NodeGraphMessage::MoveLayerToStack {
|
responses.add(NodeGraphMessage::MoveLayerToStack {
|
||||||
|
@ -660,7 +654,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
resize,
|
resize,
|
||||||
resize_opposite_corner,
|
resize_opposite_corner,
|
||||||
} => {
|
} => {
|
||||||
self.backup(responses);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
let opposite_corner = ipp.keyboard.key(resize_opposite_corner);
|
let opposite_corner = ipp.keyboard.key(resize_opposite_corner);
|
||||||
let delta = DVec2::new(delta_x, delta_y);
|
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;
|
let transform = center_in_viewport_layerspace * fit_image_size;
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
let image_frame = ImageFrame { image, ..Default::default() };
|
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 });
|
responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });
|
||||||
}
|
}
|
||||||
DocumentMessage::Redo => {
|
DocumentMessage::Redo => {
|
||||||
|
if self.network_interface.transaction_status() != TransactionStatus::Finished {
|
||||||
|
return;
|
||||||
|
}
|
||||||
responses.add(SelectToolMessage::Abort);
|
responses.add(SelectToolMessage::Abort);
|
||||||
responses.add(DocumentMessage::DocumentHistoryForward);
|
responses.add(DocumentMessage::DocumentHistoryForward);
|
||||||
responses.add(ToolMessage::Redo);
|
responses.add(ToolMessage::Redo);
|
||||||
|
@ -951,10 +948,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentMessage::SetOpacityForSelectedLayers { opacity } => {
|
DocumentMessage::SetOpacityForSelectedLayers { opacity } => {
|
||||||
self.backup(responses);
|
|
||||||
let opacity = opacity.clamp(0., 1.);
|
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) {
|
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 });
|
responses.add(GraphOperationMessage::OpacitySet { layer, opacity });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -975,7 +975,47 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
self.view_mode = view_mode;
|
self.view_mode = view_mode;
|
||||||
responses.add_front(NodeGraphMessage::RunDocumentGraph);
|
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 } => {
|
DocumentMessage::ToggleLayerExpansion { id } => {
|
||||||
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
|
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
|
||||||
if self.collapsed.0.contains(&layer) {
|
if self.collapsed.0.contains(&layer) {
|
||||||
|
@ -1000,22 +1040,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
}
|
}
|
||||||
DocumentMessage::Undo => {
|
DocumentMessage::Undo => {
|
||||||
self.undo_in_progress = true;
|
if self.network_interface.transaction_status() != TransactionStatus::Finished {
|
||||||
|
return;
|
||||||
|
}
|
||||||
responses.add(ToolMessage::PreUndo);
|
responses.add(ToolMessage::PreUndo);
|
||||||
responses.add(DocumentMessage::DocumentHistoryBackward);
|
responses.add(DocumentMessage::DocumentHistoryBackward);
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
responses.add(DocumentMessage::UndoFinished);
|
|
||||||
responses.add(ToolMessage::Undo);
|
responses.add(ToolMessage::Undo);
|
||||||
}
|
}
|
||||||
DocumentMessage::UndoFinished => {
|
|
||||||
self.undo_in_progress = false;
|
|
||||||
}
|
|
||||||
DocumentMessage::UngroupSelectedLayers => {
|
DocumentMessage::UngroupSelectedLayers => {
|
||||||
if !self.selection_network_path.is_empty() {
|
if !self.selection_network_path.is_empty() {
|
||||||
log::error!("Ungrouping selected layers is only supported for the Document Network");
|
log::error!("Ungrouping selected layers is only supported for the Document Network");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
let folder_paths = self.network_interface.folders_sorted_by_most_nested(&self.selection_network_path);
|
let folder_paths = self.network_interface.folders_sorted_by_most_nested(&self.selection_network_path);
|
||||||
for folder in folder_paths {
|
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 => {
|
DocumentMessage::ZoomCanvasTo100Percent => {
|
||||||
responses.add_front(NavigationMessage::CanvasZoomSet { zoom_factor: 1. });
|
responses.add_front(NavigationMessage::CanvasZoomSet { zoom_factor: 1. });
|
||||||
}
|
}
|
||||||
|
@ -1128,6 +1174,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
ToggleOverlaysVisibility,
|
ToggleOverlaysVisibility,
|
||||||
ToggleSnapping,
|
ToggleSnapping,
|
||||||
Undo,
|
Undo,
|
||||||
|
SelectionStepForward,
|
||||||
|
SelectionStepBack,
|
||||||
ZoomCanvasTo100Percent,
|
ZoomCanvasTo100Percent,
|
||||||
ZoomCanvasTo200Percent,
|
ZoomCanvasTo200Percent,
|
||||||
ZoomCanvasToFitAll,
|
ZoomCanvasToFitAll,
|
||||||
|
@ -1357,33 +1405,6 @@ impl DocumentMessageHandler {
|
||||||
structure_section.as_slice().into()
|
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>) {
|
pub fn undo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
let Some(previous_network) = self.undo(ipp, responses) else { return };
|
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);
|
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||||
network_interface.set_document_to_viewport_transform(transform);
|
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);
|
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
|
// 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))
|
.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
|
parent
|
||||||
.children(metadata)
|
.children(metadata)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
@ -1778,7 +1802,8 @@ impl DocumentMessageHandler {
|
||||||
|
|
||||||
pub fn update_layers_panel_options_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
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).
|
// 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.
|
// 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| {
|
let mut opacity_and_blend_mode = selected_layers_except_artboards.map(|layer| {
|
||||||
|
@ -1823,7 +1848,7 @@ impl DocumentMessageHandler {
|
||||||
MenuListEntry::new(format!("{blend_mode:?}"))
|
MenuListEntry::new(format!("{blend_mode:?}"))
|
||||||
.label(blend_mode.to_string())
|
.label(blend_mode.to_string())
|
||||||
.on_update(move |_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode }.into())
|
.on_update(move |_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode }.into())
|
||||||
.on_commit(|_| DocumentMessage::StartTransaction.into())
|
.on_commit(|_| DocumentMessage::AddTransaction.into())
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
|
@ -1916,9 +1941,8 @@ impl DocumentMessageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected_layers_reorder(&mut self, relative_index_offset: isize, responses: &mut VecDeque<Message>) {
|
pub fn selected_layers_reorder(&mut self, relative_index_offset: isize, responses: &mut VecDeque<Message>) {
|
||||||
self.backup(responses);
|
let selected_nodes = self.network_interface.selected_nodes(&[]).unwrap();
|
||||||
|
let mut selected_layers = selected_nodes.selected_layers(self.metadata());
|
||||||
let mut selected_layers = self.network_interface.selected_nodes(&[]).unwrap().selected_layers(self.metadata());
|
|
||||||
|
|
||||||
let first_or_last_selected_layer = match relative_index_offset.signum() {
|
let first_or_last_selected_layer = match relative_index_offset.signum() {
|
||||||
-1 => selected_layers.next(),
|
-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()) {
|
let tree = match usvg::Tree::from_str(&svg, &usvg::Options::default()) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
responses.add(DocumentMessage::DocumentHistoryBackward);
|
|
||||||
responses.add(DialogMessage::DisplayDialogError {
|
responses.add(DialogMessage::DisplayDialogError {
|
||||||
title: "SVG parsing failed".to_string(),
|
title: "SVG parsing failed".to_string(),
|
||||||
description: e.to_string(),
|
description: e.to_string(),
|
||||||
|
|
|
@ -294,7 +294,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||||
NavigationMessage::EndCanvasPTZWithClick { commit_key } => {
|
NavigationMessage::EndCanvasPTZWithClick { commit_key } => {
|
||||||
self.finish_operation_with_click = false;
|
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 });
|
responses.add(NavigationMessage::EndCanvasPTZ { abort_transform });
|
||||||
}
|
}
|
||||||
NavigationMessage::FitViewportToBounds {
|
NavigationMessage::FitViewportToBounds {
|
||||||
|
|
|
@ -42,15 +42,17 @@ pub struct NodeGraphMessageHandler {
|
||||||
begin_dragging: bool,
|
begin_dragging: bool,
|
||||||
/// Stored in node graph coordinates
|
/// Stored in node graph coordinates
|
||||||
box_selection_start: Option<DVec2>,
|
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
|
/// If the grip icon is held during a drag, then shift without pushing other nodes
|
||||||
shift_without_push: bool,
|
shift_without_push: bool,
|
||||||
disconnecting: Option<InputConnector>,
|
disconnecting: Option<InputConnector>,
|
||||||
initial_disconnecting: bool,
|
initial_disconnecting: bool,
|
||||||
/// Node to select on pointer up if multiple nodes are selected and they were not dragged.
|
/// Node to select on pointer up if multiple nodes are selected and they were not dragged.
|
||||||
select_if_not_dragged: Option<NodeId>,
|
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>,
|
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>,
|
pub wire_in_progress_to_connector: Option<DVec2>,
|
||||||
/// State for the context menu popups.
|
/// State for the context menu popups.
|
||||||
pub context_menu: Option<ContextMenuInformation>,
|
pub context_menu: Option<ContextMenuInformation>,
|
||||||
|
@ -58,6 +60,8 @@ pub struct NodeGraphMessageHandler {
|
||||||
pub deselect_on_pointer_up: Option<usize>,
|
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.
|
/// 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,
|
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.
|
/// 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();
|
let node_template = document_node_type.default_node_template();
|
||||||
self.context_menu = None;
|
self.context_menu = None;
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::InsertNode {
|
responses.add(NodeGraphMessage::InsertNode {
|
||||||
node_id,
|
node_id,
|
||||||
node_template: node_template.clone(),
|
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");
|
log::error!("Could not get selected nodes in DeleteSelectedNodes");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::DeleteNodes {
|
responses.add(NodeGraphMessage::DeleteNodes {
|
||||||
node_ids: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(),
|
node_ids: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(),
|
||||||
delete_children,
|
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 nodes = network_interface.copy_nodes(©_ids, selection_network_path).collect::<Vec<_>>();
|
||||||
|
|
||||||
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect::<HashMap<_, _>>();
|
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::AddNodes { nodes, new_ids: new_ids.clone() });
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||||
nodes: new_ids.values().cloned().collect(),
|
nodes: new_ids.values().cloned().collect(),
|
||||||
|
@ -260,8 +264,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
|
|
||||||
let Some(mut input) = node.inputs.get(input_index).cloned() else {
|
let Some(mut input) = node.inputs.get(input_index).cloned() else {
|
||||||
log::error!("Could not find input {input_index} in NodeGraphMessage::ExposeInput");
|
log::error!("Could not find input {input_index} in NodeGraphMessage::ExposeInput");
|
||||||
return;
|
return;
|
||||||
|
@ -274,6 +276,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SetInput {
|
responses.add(NodeGraphMessage::SetInput {
|
||||||
input_connector: InputConnector::node(node_id, input_index),
|
input_connector: InputConnector::node(node_id, input_index),
|
||||||
input,
|
input,
|
||||||
|
@ -306,7 +310,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
let new_ids: HashMap<_, _> = data.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
|
let new_ids: HashMap<_, _> = data.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
|
||||||
responses.add(NodeGraphMessage::AddNodes {
|
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);
|
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() {
|
if network_interface.grip_from_click(click, selection_network_path).is_some() {
|
||||||
self.shift_without_push = true;
|
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
|
// Create the add node popup on right click, then exit
|
||||||
if right_click {
|
if right_click {
|
||||||
|
// Abort dragging a node
|
||||||
if self.drag_start.is_some() {
|
if self.drag_start.is_some() {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
self.drag_start = None;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,6 +409,11 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
return;
|
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 the user is clicking on the create nodes list or context menu, break here
|
||||||
if let Some(context_menu) = &self.context_menu {
|
if let Some(context_menu) = &self.context_menu {
|
||||||
let context_menu_viewport = network_metadata
|
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
|
// 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.context_menu = None;
|
||||||
self.wire_in_progress_from_connector = None;
|
self.wire_in_progress_from_connector = None;
|
||||||
self.wire_in_progress_to_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 });
|
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
|
// Alt-click sets the clicked node as previewed
|
||||||
if alt_click {
|
if alt_click {
|
||||||
if let Some(clicked_node) = clicked_id {
|
if let Some(clicked_node) = clicked_id {
|
||||||
responses.add(NodeGraphMessage::TogglePreview { node_id: clicked_node });
|
self.preview_on_mouse_up = Some(clicked_node);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin moving an existing wire
|
// Begin moving an existing wire
|
||||||
if let Some(clicked_input) = &clicked_input {
|
if let Some(clicked_input) = &clicked_input {
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
self.initial_disconnecting = true;
|
self.initial_disconnecting = true;
|
||||||
self.disconnecting = Some(clicked_input.clone());
|
self.disconnecting = Some(clicked_input.clone());
|
||||||
|
|
||||||
|
@ -451,6 +481,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
|
|
||||||
// Begin creating a new wire
|
// Begin creating a new wire
|
||||||
if let Some(clicked_output) = clicked_output {
|
if let Some(clicked_output) = clicked_output {
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
self.initial_disconnecting = false;
|
self.initial_disconnecting = false;
|
||||||
// Disconnect vertical output wire from an already-connected layer
|
// Disconnect vertical output wire from an already-connected layer
|
||||||
if let OutputConnector::Node { node_id, .. } = clicked_output {
|
if let OutputConnector::Node { node_id, .. } = clicked_output {
|
||||||
|
@ -513,6 +544,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
if modified_selected {
|
if modified_selected {
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: updated_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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -558,7 +591,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
self.wire_in_progress_to_connector = Some(point);
|
self.wire_in_progress_to_connector = Some(point);
|
||||||
// Disconnect if the wire was previously connected to an input
|
// Disconnect if the wire was previously connected to an input
|
||||||
if let Some(disconnecting) = &self.disconnecting {
|
if let Some(disconnecting) = &self.disconnecting {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
let mut disconnect_root_node = false;
|
let mut disconnect_root_node = false;
|
||||||
if let Previewing::Yes { root_node_to_restore } = network_interface.previewing(selection_network_path) {
|
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) {
|
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 {
|
} else if let Some(drag_start) = &mut self.drag_start {
|
||||||
if self.begin_dragging {
|
if self.begin_dragging {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
self.begin_dragging = false;
|
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);
|
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;
|
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 {
|
if let Some(node_to_deselect) = self.deselect_on_pointer_up {
|
||||||
let mut new_selected_nodes = selected_nodes.selected_nodes_ref().clone();
|
let mut new_selected_nodes = selected_nodes.selected_nodes_ref().clone();
|
||||||
new_selected_nodes.remove(node_to_deselect);
|
new_selected_nodes.remove(node_to_deselect);
|
||||||
|
@ -896,8 +954,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||||
{
|
{
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::InsertNodeBetween {
|
responses.add(NodeGraphMessage::InsertNodeBetween {
|
||||||
node_id: selected_node_id,
|
node_id: selected_node_id,
|
||||||
input_connector: overlapping_wire.wire_end.clone(),
|
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");
|
log::error!("Could not get selected nodes in NodeGraphMessage::ToggleSelectedAsLayersOrNodes");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
for node_id in selected_nodes.selected_nodes() {
|
for node_id in selected_nodes.selected_nodes() {
|
||||||
responses.add(NodeGraphMessage::SetToNodeOrLayer {
|
responses.add(NodeGraphMessage::SetToNodeOrLayer {
|
||||||
node_id: *node_id,
|
node_id: *node_id,
|
||||||
|
@ -1096,6 +1152,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
NodeGraphMessage::SetDisplayName { node_id, alias } => {
|
NodeGraphMessage::SetDisplayName { node_id, alias } => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
responses.add(NodeGraphMessage::SetDisplayNameImpl { node_id, alias });
|
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::RenderRulers);
|
||||||
responses.add(DocumentMessage::RenderScrollbars);
|
responses.add(DocumentMessage::RenderScrollbars);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
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);
|
network_interface.set_display_name(&node_id, alias, selection_network_path);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::TogglePreview { node_id } => {
|
NodeGraphMessage::TogglePreview { node_id } => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::TogglePreviewImpl { node_id });
|
responses.add(NodeGraphMessage::TogglePreviewImpl { node_id });
|
||||||
responses.add(NodeGraphMessage::UpdateActionButtons);
|
responses.add(NodeGraphMessage::UpdateActionButtons);
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
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.
|
// 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));
|
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 {
|
for node_id in &node_ids {
|
||||||
responses.add(NodeGraphMessage::SetLocked { node_id: *node_id, locked });
|
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;
|
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::SetLocked { node_id, locked });
|
||||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] })
|
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.
|
// 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));
|
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 {
|
for node_id in &node_ids {
|
||||||
responses.add(NodeGraphMessage::SetVisibility { node_id: *node_id, visible });
|
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;
|
let visible = !node.visible;
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
|
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
|
||||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] });
|
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] });
|
||||||
}
|
}
|
||||||
|
@ -1935,6 +1993,7 @@ impl Default for NodeGraphMessageHandler {
|
||||||
begin_dragging: false,
|
begin_dragging: false,
|
||||||
shift_without_push: false,
|
shift_without_push: false,
|
||||||
box_selection_start: None,
|
box_selection_start: None,
|
||||||
|
selection_before_pointer_down: Vec::new(),
|
||||||
disconnecting: None,
|
disconnecting: None,
|
||||||
initial_disconnecting: false,
|
initial_disconnecting: false,
|
||||||
select_if_not_dragged: None,
|
select_if_not_dragged: None,
|
||||||
|
@ -1943,6 +2002,7 @@ impl Default for NodeGraphMessageHandler {
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
deselect_on_pointer_up: None,
|
deselect_on_pointer_up: None,
|
||||||
auto_panning: Default::default(),
|
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 {
|
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 {
|
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 interpreted_executor::{dynamic_executor::ResolvedDocumentNodeTypes, node_registry::NODE_REGISTRY};
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, DVec2, IVec2};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
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.
|
/// 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.
|
/// All input/output types based on the compiled network.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub resolved_types: ResolvedDocumentNodeTypes,
|
pub resolved_types: ResolvedDocumentNodeTypes,
|
||||||
|
/// Disallow aborting transactions whilst undoing to avoid #559.
|
||||||
|
#[serde(skip)]
|
||||||
|
transaction_status: TransactionStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for NodeNetworkInterface {
|
impl Clone for NodeNetworkInterface {
|
||||||
|
@ -40,6 +43,7 @@ impl Clone for NodeNetworkInterface {
|
||||||
network_metadata: self.network_metadata.clone(),
|
network_metadata: self.network_metadata.clone(),
|
||||||
document_metadata: Default::default(),
|
document_metadata: Default::default(),
|
||||||
resolved_types: Default::default(),
|
resolved_types: Default::default(),
|
||||||
|
transaction_status: TransactionStatus::Finished,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,13 +82,26 @@ impl NodeNetworkInterface {
|
||||||
&self.document_metadata
|
&self.document_metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transaction_status(&self) -> TransactionStatus {
|
||||||
|
self.transaction_status
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the selected nodes for the network at the network_path
|
/// 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 {
|
let Some(network_metadata) = self.network_metadata(network_path) else {
|
||||||
log::error!("Could not get nested network_metadata in selected_nodes");
|
log::error!("Could not get nested network_metadata in selected_nodes");
|
||||||
return None;
|
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.
|
/// 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
|
// 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);
|
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())) {
|
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 {
|
let Some(position) = self.position(node_id, network_path) else {
|
||||||
|
@ -373,7 +389,7 @@ impl NodeNetworkInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a node template from an existing node.
|
/// 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 {
|
let Some(network) = self.network(network_path) else {
|
||||||
log::error!("Could not get network in create_node_template");
|
log::error!("Could not get network in create_node_template");
|
||||||
return None;
|
return None;
|
||||||
|
@ -421,13 +437,13 @@ impl NodeNetworkInterface {
|
||||||
|
|
||||||
pub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> {
|
pub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> {
|
||||||
let Some(network) = self.network(network_path) else {
|
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;
|
return None;
|
||||||
};
|
};
|
||||||
match input_connector {
|
match input_connector {
|
||||||
InputConnector::Node { node_id, input_index } => {
|
InputConnector::Node { node_id, input_index } => {
|
||||||
let Some(node) = network.nodes.get(node_id) else {
|
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;
|
return None;
|
||||||
};
|
};
|
||||||
node.inputs.get(*input_index)
|
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 input_type = self.input_type(&InputConnector::node(encapsulating_node_id, *import_index), &encapsulating_path);
|
||||||
let data_type = FrontendGraphDataType::with_type(&input_type);
|
let data_type = FrontendGraphDataType::with_type(&input_type);
|
||||||
|
|
||||||
let import_name = if import_name.is_empty() {
|
let import_name = if import_name.is_empty() { input_type.clone().nested_type().to_string() } else { import_name };
|
||||||
TaggedValue::from_type(&input_type).ty().to_string()
|
|
||||||
} else {
|
|
||||||
import_name
|
|
||||||
};
|
|
||||||
|
|
||||||
let connected_to = self
|
let connected_to = self
|
||||||
.outward_wires(network_path)
|
.outward_wires(network_path)
|
||||||
|
@ -773,7 +785,7 @@ impl NodeNetworkInterface {
|
||||||
} else {
|
} else {
|
||||||
input_type
|
input_type
|
||||||
.clone()
|
.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))
|
.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);
|
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 all_outputs_are_sole_dependents = true;
|
||||||
|
let mut dead_ends = 0;
|
||||||
|
|
||||||
for output_index in 0..number_of_outputs {
|
for output_index in 0..number_of_outputs {
|
||||||
let downstream_connections = {
|
let downstream_connections = {
|
||||||
|
@ -899,25 +912,31 @@ impl NodeNetworkInterface {
|
||||||
log::error!("Could not get outward wires in upstream_nodes_below_layer");
|
log::error!("Could not get outward wires in upstream_nodes_below_layer");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
let mut has_downstream_connections = false;
|
||||||
for output_index in 0..number_of_outputs {
|
for output_index in 0..number_of_outputs {
|
||||||
let Some(downstream_connections) = outward_wires.get(&OutputConnector::node(*downstream_node_id, output_index)) else {
|
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");
|
log::error!("Could not get outward wires in upstream_nodes_below_layer");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
if !downstream_connections.is_empty() {
|
||||||
|
has_downstream_connections = true;
|
||||||
|
}
|
||||||
stack.extend(downstream_connections.clone());
|
stack.extend(downstream_connections.clone());
|
||||||
}
|
}
|
||||||
|
if !has_downstream_connections {
|
||||||
|
dead_ends += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputConnector::Export(_) => current_output_is_sole_dependent = false,
|
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;
|
all_outputs_are_sole_dependents = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if all_outputs_are_sole_dependents {
|
if all_outputs_are_sole_dependents && dead_ends == 0 {
|
||||||
sole_dependents.insert(upstream_node_from_input);
|
sole_dependents.insert(upstream_node_from_input);
|
||||||
} else {
|
} else {
|
||||||
upstream_chain_can_be_added = false;
|
upstream_chain_can_be_added = false;
|
||||||
|
@ -1391,6 +1410,7 @@ impl NodeNetworkInterface {
|
||||||
network_metadata,
|
network_metadata,
|
||||||
document_metadata: DocumentMetadata::default(),
|
document_metadata: DocumentMetadata::default(),
|
||||||
resolved_types: ResolvedDocumentNodeTypes::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
|
// 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.
|
// Mutable methods never recalculate the transient metadata, they only unload it. Loading metadata should only be done by the getter.
|
||||||
impl NodeNetworkInterface {
|
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.
|
/// 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> {
|
pub fn selected_nodes_mut(&mut self, network_path: &[NodeId]) -> Option<&mut SelectedNodes> {
|
||||||
self.unload_stack_dependents(network_path);
|
self.unload_stack_dependents(network_path);
|
||||||
|
@ -1487,7 +1521,38 @@ impl NodeNetworkInterface {
|
||||||
log::error!("Could not get nested network_metadata in selected_nodes");
|
log::error!("Could not get nested network_metadata in selected_nodes");
|
||||||
return None;
|
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>> {
|
fn stack_dependents(&mut self, network_path: &[NodeId]) -> Option<&HashMap<NodeId, LayerOwner>> {
|
||||||
|
@ -1616,6 +1681,7 @@ impl NodeNetworkInterface {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if !sole_dependents.contains(¤t_node) {
|
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) {
|
for output_index in 0..self.number_of_outputs(¤t_node, network_path) {
|
||||||
let Some(outward_wires) = self.outward_wires(network_path) else {
|
let Some(outward_wires) = self.outward_wires(network_path) else {
|
||||||
log::error!("Cannot load outward wires in load_stack_dependents");
|
log::error!("Cannot load outward wires in load_stack_dependents");
|
||||||
|
@ -1626,12 +1692,16 @@ impl NodeNetworkInterface {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
for downstream_input in outward_wires {
|
for downstream_input in outward_wires {
|
||||||
|
has_outward_wire = true;
|
||||||
match downstream_input {
|
match downstream_input {
|
||||||
InputConnector::Node { node_id, .. } => stack.push(*node_id),
|
InputConnector::Node { node_id, .. } => stack.push(*node_id),
|
||||||
InputConnector::Export(_) => is_sole_dependent = false,
|
InputConnector::Export(_) => is_sole_dependent = false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !has_outward_wire {
|
||||||
|
is_sole_dependent = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !is_sole_dependent {
|
if !is_sole_dependent {
|
||||||
break;
|
break;
|
||||||
|
@ -2384,20 +2454,6 @@ impl NodeNetworkInterface {
|
||||||
document_metadata.document_to_viewport = transform;
|
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 {
|
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 input_count = self.number_of_inputs(node_id, network_path);
|
||||||
let output_count = self.number_of_outputs(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);
|
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.
|
/// 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]) {
|
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
|
// 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);
|
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
|
// 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 let Some(encapsulating_node_metadata) = self.encapsulating_node_metadata_mut(network_path) {
|
||||||
if insert_index == -1 {
|
if insert_index == -1 {
|
||||||
|
@ -2904,6 +2978,8 @@ impl NodeNetworkInterface {
|
||||||
node.inputs.insert(insert_index as usize, input);
|
node.inputs.insert(insert_index as usize, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.transaction_modified();
|
||||||
|
|
||||||
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
|
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
|
||||||
log::error!("Could not get node_metadata in insert_input");
|
log::error!("Could not get node_metadata in insert_input");
|
||||||
return;
|
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
|
// 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 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) {
|
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");
|
log::error!("Network not found in insert_node");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
network.nodes.insert(node_id, node_template.document_node);
|
network.nodes.insert(node_id, node_template.document_node);
|
||||||
|
self.transaction_modified();
|
||||||
|
|
||||||
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
||||||
log::error!("Network not found in insert_node");
|
log::error!("Network not found in insert_node");
|
||||||
|
@ -3262,7 +3352,9 @@ impl NodeNetworkInterface {
|
||||||
log::error!("Network not found in insert_node");
|
log::error!("Network not found in insert_node");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
network.nodes.insert(node_id, node_template.document_node);
|
network.nodes.insert(node_id, node_template.document_node);
|
||||||
|
self.transaction_modified();
|
||||||
|
|
||||||
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
||||||
log::error!("Network not found in insert_node");
|
log::error!("Network not found in insert_node");
|
||||||
|
@ -3348,23 +3440,18 @@ impl NodeNetworkInterface {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect all inputs of the node to be deleted
|
for input_index in 0..self.number_of_inputs(delete_node_id, network_path) {
|
||||||
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 {
|
|
||||||
self.disconnect_input(&InputConnector::node(*delete_node_id, input_index), network_path);
|
self.disconnect_input(&InputConnector::node(*delete_node_id, input_index), network_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(network) = self.network_mut(network_path) else {
|
let Some(network) = self.network_mut(network_path) else {
|
||||||
log::error!("Could not get nested network in delete_nodes");
|
log::error!("Could not get nested network in delete_nodes");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
// TODO: Ensure node to delete is fully disconnected from the network
|
||||||
network.nodes.remove(delete_node_id);
|
network.nodes.remove(delete_node_id);
|
||||||
|
self.transaction_modified();
|
||||||
|
|
||||||
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
|
||||||
log::error!("Could not get nested network_metadata in delete_nodes");
|
log::error!("Could not get nested network_metadata in delete_nodes");
|
||||||
continue;
|
continue;
|
||||||
|
@ -3384,8 +3471,8 @@ impl NodeNetworkInterface {
|
||||||
selected_nodes.retain_selected_nodes(|node_id| !nodes_to_delete.contains(node_id));
|
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.
|
/// 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, deleting_node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
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.
|
// 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);
|
self.stop_previewing(network_path);
|
||||||
|
|
||||||
|
@ -3394,62 +3481,74 @@ impl NodeNetworkInterface {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut reconnect_to_input: Option<NodeInput> = None;
|
|
||||||
|
|
||||||
// Check whether the being-deleted node's first (primary) input is a node
|
// Check whether the being-deleted node's first (primary) input is a node
|
||||||
if let Some(node) = network.nodes.get(deleting_node_id) {
|
let mut reconnect_to_input = network.nodes.get(node_id).and_then(|node| {
|
||||||
// Reconnect to the upstream node. If the layer or first upstream layer node if the deleting node is a layer
|
node.inputs
|
||||||
if self.is_layer(deleting_node_id, network_path) {
|
.iter()
|
||||||
if let Some(upstream_layer_id) = self
|
.find(|input| input.is_exposed_to_frontend(network_path.is_empty()))
|
||||||
.upstream_flow_back_from_nodes(vec![*deleting_node_id], network_path, FlowType::PrimaryFlow)
|
.filter(|input| matches!(input, NodeInput::Node { .. } | NodeInput::Network { .. }))
|
||||||
.skip(1) // Skip the node to delete
|
.cloned()
|
||||||
.find(|node_id| self.is_layer(node_id, network_path))
|
});
|
||||||
{
|
// Get all upstream references
|
||||||
reconnect_to_input = Some(NodeInput::node(upstream_layer_id, 0));
|
let number_of_outputs = self.number_of_outputs(node_id, network_path);
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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 Some(all_outward_wires) = self.outward_wires(network_path) else {
|
let Some(all_outward_wires) = self.outward_wires(network_path) else {
|
||||||
log::error!("Could not get outward wires in remove_references_from_network");
|
log::error!("Could not get outward wires in remove_references_from_network");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let mut downstream_inputs_to_disconnect = Vec::new();
|
let mut downstream_inputs_to_disconnect = Vec::new();
|
||||||
for output_index in 0..number_of_outputs {
|
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());
|
downstream_inputs_to_disconnect.extend(outward_wires.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for input_to_disconnect in &downstream_inputs_to_disconnect {
|
let mut reconnect_node = None;
|
||||||
// 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));
|
|
||||||
|
|
||||||
self.set_input(input_to_disconnect, reconnect_input.clone(), network_path);
|
for downstream_input in &downstream_inputs_to_disconnect {
|
||||||
if let (Some(original_position), Some(original_downstream_position)) = (original_position, original_downstream_position) {
|
self.disconnect_input(downstream_input, network_path);
|
||||||
// Recalculate stack position (to keep layer in same place) if the upstream node is a layer in a chain
|
// Prevent reconnecting export to import until https://github.com/GraphiteEditor/Graphite/issues/1762 is solved
|
||||||
if reconnect_input
|
if !(matches!(reconnect_to_input, Some(NodeInput::Network { .. })) && matches!(downstream_input, InputConnector::Export(_))) {
|
||||||
.as_node()
|
if let Some(reconnect_input) = reconnect_to_input.take() {
|
||||||
.is_some_and(|upstream_node| self.is_stack(&upstream_node, network_path) && self.is_layer(&upstream_node, network_path))
|
// 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 });
|
||||||
let offset = (original_position.y - original_downstream_position.y - 3).max(0) as u32;
|
self.disconnect_input(&InputConnector::node(*node_id, 0), network_path);
|
||||||
self.set_stack_position(&reconnect_input.as_node().unwrap(), offset, 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
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3502,6 +3601,10 @@ impl NodeNetworkInterface {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if node_metadata.persistent_metadata.display_name == display_name {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
node_metadata.persistent_metadata.display_name.clone_from(&display_name);
|
node_metadata.persistent_metadata.display_name.clone_from(&display_name);
|
||||||
|
|
||||||
// Keep the alias in sync with the `ToArtboard` name input
|
// Keep the alias in sync with the `ToArtboard` name input
|
||||||
|
@ -3526,6 +3629,7 @@ impl NodeNetworkInterface {
|
||||||
to_artboard.inputs[label_index] = label_input;
|
to_artboard.inputs[label_index] = label_input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.transaction_modified();
|
||||||
self.try_unload_layer_width(node_id, network_path);
|
self.try_unload_layer_width(node_id, network_path);
|
||||||
self.unload_node_click_targets(node_id, network_path);
|
self.unload_node_click_targets(node_id, network_path);
|
||||||
}
|
}
|
||||||
|
@ -3541,6 +3645,7 @@ impl NodeNetworkInterface {
|
||||||
};
|
};
|
||||||
|
|
||||||
node.visible = is_visible;
|
node.visible = is_visible;
|
||||||
|
self.transaction_modified();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_locked(&mut self, node_id: &NodeId, network_path: &[NodeId], locked: bool) {
|
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;
|
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) {
|
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.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_upstream_node_click_targets(vec![*node_id], network_path);
|
||||||
self.unload_all_nodes_bounding_box(network_path);
|
self.unload_all_nodes_bounding_box(network_path);
|
||||||
self.unload_import_export_ports(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}");
|
log::error!("Could not get node_metadata for node {node_id}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let NodeTypePersistentMetadata::Node(node_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
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);
|
node_metadata.position = NodePosition::Absolute(position);
|
||||||
|
self.transaction_modified();
|
||||||
} else if let NodeTypePersistentMetadata::Layer(layer_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
} 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);
|
layer_metadata.position = LayerPosition::Absolute(position);
|
||||||
|
self.transaction_modified();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3765,7 +3881,11 @@ impl NodeNetworkInterface {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let NodeTypePersistentMetadata::Layer(layer_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
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);
|
layer_metadata.position = LayerPosition::Stack(y_offset);
|
||||||
|
self.transaction_modified();
|
||||||
} else {
|
} else {
|
||||||
log::error!("Could not set stack position for non layer node {node_id}");
|
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
|
// Set any absolute nodes to chain positioning
|
||||||
if let NodeTypePersistentMetadata::Node(NodePersistentMetadata { position }) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
if let NodeTypePersistentMetadata::Node(NodePersistentMetadata { position }) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
||||||
|
if *position == NodePosition::Chain {
|
||||||
|
return;
|
||||||
|
}
|
||||||
*position = NodePosition::Chain;
|
*position = NodePosition::Chain;
|
||||||
|
self.transaction_modified();
|
||||||
}
|
}
|
||||||
// If there is an upstream layer then stop breaking the chain
|
// If there is an upstream layer then stop breaking the chain
|
||||||
else {
|
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.
|
/// 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.
|
/// Ensure that the layer is absolute position.
|
||||||
pub fn shift_absolute_node_position(&mut self, layer: &NodeId, shift: IVec2, network_path: &[NodeId]) {
|
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);
|
let mut nodes_to_shift = self.upstream_nodes_below_layer(layer, network_path);
|
||||||
nodes_to_shift.insert(*layer);
|
nodes_to_shift.insert(*layer);
|
||||||
|
|
||||||
|
@ -3925,6 +4052,7 @@ impl NodeNetworkInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.transaction_modified();
|
||||||
self.unload_upstream_node_click_targets(vec![*layer], network_path);
|
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");
|
log::error!("Could not get selected nodes in shift_selected_nodes");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
if !shift_without_push {
|
||||||
for node_id in node_ids.clone() {
|
for node_id in node_ids.clone() {
|
||||||
if self.is_layer(&node_id, network_path) {
|
if self.is_layer(&node_id, network_path) {
|
||||||
if let Some(owned_nodes) = self.owned_nodes(&node_id, network_path) {
|
if let Some(owned_nodes) = self.owned_nodes(&node_id, network_path) {
|
||||||
for owned_node in owned_nodes {
|
for owned_node in owned_nodes {
|
||||||
node_ids.remove(owned_node);
|
node_ids.remove(owned_node);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut filtered_node_ids = HashSet::new();
|
for selected_node in &node_ids.clone() {
|
||||||
for selected_node in &node_ids {
|
|
||||||
// Deselect chain nodes upstream from a selected layer
|
// Deselect chain nodes upstream from a selected layer
|
||||||
if self.is_chain(selected_node, network_path)
|
if self.is_chain(selected_node, network_path)
|
||||||
&& self
|
&& self
|
||||||
.downstream_layer(selected_node, network_path)
|
.downstream_layer(selected_node, network_path)
|
||||||
.is_some_and(|downstream_layer| node_ids.contains(&downstream_layer.to_node()))
|
.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 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 {
|
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 {
|
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");
|
log::error!("Could not get node metadata for node {node_id} in shift_selected_nodes");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let NodeTypePersistentMetadata::Layer(layer_metadata) = &node_metadata.persistent_metadata.node_type_metadata {
|
if let NodeTypePersistentMetadata::Layer(layer_metadata) = &node_metadata.persistent_metadata.node_type_metadata {
|
||||||
if let LayerPosition::Stack(offset) = layer_metadata.position {
|
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
|
// Offset cannot be negative, so cancel the shift
|
||||||
if offset == 0 {
|
if offset == 0 {
|
||||||
return;
|
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()
|
.iter()
|
||||||
.filter_map(|&node_id| {
|
.filter_map(|&node_id| {
|
||||||
let Some(position) = self.position(&node_id, network_path) else {
|
let Some(position) = self.position(&node_id, network_path) else {
|
||||||
|
@ -3986,7 +4123,7 @@ impl NodeNetworkInterface {
|
||||||
})
|
})
|
||||||
.collect::<Vec<(NodeId, i32)>>();
|
.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");
|
log::error!("Could not get position for all nodes in shift_selected_nodes");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -4066,6 +4203,7 @@ impl NodeNetworkInterface {
|
||||||
if let TransientMetadata::Loaded(stack_dependents) = &mut network_metadata.transient_metadata.stack_dependents {
|
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) {
|
if let Some(LayerOwner::None(offset)) = stack_dependents.get_mut(node_id) {
|
||||||
*offset += shift_sign;
|
*offset += shift_sign;
|
||||||
|
self.transaction_modified();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4181,6 +4319,7 @@ impl NodeNetworkInterface {
|
||||||
match layer_owner {
|
match layer_owner {
|
||||||
LayerOwner::None(offset) => {
|
LayerOwner::None(offset) => {
|
||||||
*offset += shift_sign;
|
*offset += shift_sign;
|
||||||
|
self.transaction_modified();
|
||||||
}
|
}
|
||||||
LayerOwner::Layer(_) => {
|
LayerOwner::Layer(_) => {
|
||||||
log::error!("Node being shifted with a push should not be owned");
|
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)> {
|
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_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 {
|
let Some(stack_dependents) = self.try_get_stack_dependents(network_path) else {
|
||||||
log::error!("Could not load stack dependents in shift_selected_nodes");
|
log::error!("Could not load stack dependents in shift_selected_nodes");
|
||||||
return Vec::new();
|
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 NodeTypePersistentMetadata::Layer(layer_metadata) = &mut node_metadata.persistent_metadata.node_type_metadata {
|
||||||
if let LayerPosition::Absolute(layer_position) = &mut layer_metadata.position {
|
if let LayerPosition::Absolute(layer_position) = &mut layer_metadata.position {
|
||||||
*layer_position += shift;
|
*layer_position += shift;
|
||||||
|
self.transaction_modified();
|
||||||
} else if let LayerPosition::Stack(y_offset) = &mut layer_metadata.position {
|
} else if let LayerPosition::Stack(y_offset) = &mut layer_metadata.position {
|
||||||
let shifted_y_offset = *y_offset as i32 + shift.y;
|
let shifted_y_offset = *y_offset as i32 + shift.y;
|
||||||
// A layer can only be shifted to a positive y_offset
|
// A layer can only be shifted to a positive y_offset
|
||||||
|
@ -4302,14 +4442,19 @@ impl NodeNetworkInterface {
|
||||||
if shift.x != 0 {
|
if shift.x != 0 {
|
||||||
log::error!("Stack layer {node_id} cannot be shifted horizontally.");
|
log::error!("Stack layer {node_id} cannot be shifted horizontally.");
|
||||||
}
|
}
|
||||||
|
let new_y_offset = shifted_y_offset.max(0) as u32;
|
||||||
*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
|
// 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);
|
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 {
|
} 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 {
|
if let NodePosition::Absolute(node_metadata) = &mut node_metadata.position {
|
||||||
*node_metadata += shift;
|
*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
|
// 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);
|
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
|
// 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
|
/// 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]) {
|
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
|
// 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 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() {
|
if let Some(first_layer) = LayerNodeIdentifier::ROOT_PARENT.children(&self.document_metadata).next() {
|
||||||
|
@ -4352,16 +4503,6 @@ impl NodeNetworkInterface {
|
||||||
return;
|
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;
|
let mut lowest_upstream_node_height = 0;
|
||||||
for upstream_node in self
|
for upstream_node in self
|
||||||
.upstream_flow_back_from_nodes(vec![layer.to_node()], network_path, FlowType::LayerChildrenUpstreamFlow)
|
.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);
|
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 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) {
|
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
|
// 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
|
// Disconnect layer to move
|
||||||
self.remove_references_from_network(&layer.to_node(), network_path);
|
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);
|
let post_node = ModifyInputsContext::get_post_node_with_index(self, parent, insert_index);
|
||||||
|
|
||||||
|
@ -4415,8 +4550,124 @@ impl NodeNetworkInterface {
|
||||||
return;
|
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
|
// 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 {
|
match post_node_input {
|
||||||
// Create a new stack
|
// Create a new stack
|
||||||
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
|
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
|
||||||
|
@ -4426,17 +4677,17 @@ impl NodeNetworkInterface {
|
||||||
let shift = final_layer_position - previous_layer_position;
|
let shift = final_layer_position - previous_layer_position;
|
||||||
self.shift_absolute_node_position(&layer.to_node(), shift, network_path);
|
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, .. } => {
|
NodeInput::Node { node_id, .. } => {
|
||||||
let Some(top_of_stack_position) = self.position(&node_id, network_path) else {
|
let Some(stack_top_position) = self.position(&node_id, network_path) else {
|
||||||
log::error!("Could not get top of stack position in move_layer_to_stack");
|
log::error!("Could not get stack x position in move_layer_to_stack");
|
||||||
return;
|
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(&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.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 { .. } => {
|
NodeInput::Network { .. } => {
|
||||||
log::error!("Cannot move post node to parent which connects to the imports")
|
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
|
// Move to the bottom of the stack
|
||||||
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
|
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
|
// 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.shift_absolute_node_position(&layer.to_node(), offset, network_path);
|
||||||
self.create_wire(&OutputConnector::node(layer.to_node(), 0), &post_node, 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
|
// Insert into the stack
|
||||||
NodeInput::Node { node_id: upstream_node_id, .. } => {
|
NodeInput::Node { .. } => {
|
||||||
let Some(upstream_node_metadata) = self.node_metadata(&upstream_node_id, network_path) else {
|
let final_layer_position = after_move_post_layer_position + IVec2::new(0, 3 + height_above_layer);
|
||||||
log::error!("Could not get upstream node metadata in move_layer_to_stack");
|
let shift = final_layer_position - previous_layer_position;
|
||||||
return;
|
self.shift_absolute_node_position(&layer.to_node(), shift, network_path);
|
||||||
};
|
|
||||||
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);
|
|
||||||
|
|
||||||
self.insert_node_between(&layer.to_node(), &post_node, 0, 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 { .. } => {
|
NodeInput::Network { .. } => {
|
||||||
log::error!("Cannot move post node to parent which connects to the imports")
|
log::error!("Cannot move post node to parent which connects to the imports")
|
||||||
|
@ -4809,14 +5034,14 @@ impl NodeNetworkMetadata {
|
||||||
|
|
||||||
for segment in nested_path {
|
for segment in nested_path {
|
||||||
network_metadata = network_metadata
|
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());
|
.and_then(|node| node.persistent_metadata.network_metadata.as_mut());
|
||||||
}
|
}
|
||||||
network_metadata
|
network_metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct NodeNetworkPersistentMetadata {
|
pub struct NodeNetworkPersistentMetadata {
|
||||||
/// Node metadata must exist for every document node in the network
|
/// 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")]
|
#[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,
|
pub previewing: Previewing,
|
||||||
// Stores the transform and navigation state for the network
|
// Stores the transform and navigation state for the network
|
||||||
pub navigation_metadata: NavigationMetadata,
|
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
|
/// 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 document_node: DocumentNode,
|
||||||
pub persistent_node_metadata: DocumentNodePersistentMetadata,
|
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) {
|
pub fn clear_selected_nodes(&mut self) {
|
||||||
self.0 = Vec::new();
|
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)]
|
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
||||||
|
|
|
@ -190,6 +190,20 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||||
..MenuBarEntry::default()
|
..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 {
|
vec![MenuBarEntry {
|
||||||
label: "Delete Selected".into(),
|
label: "Delete Selected".into(),
|
||||||
icon: Some("Trash".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) {
|
if let Ok(data) = serde_json::from_str::<Vec<CopyBufferEntry>>(&data) {
|
||||||
let parent = document.new_layer_parent(false);
|
let parent = document.new_layer_parent(false);
|
||||||
|
|
||||||
responses.add(DocumentMessage::DeselectAllLayers);
|
let mut added_nodes = false;
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
|
|
||||||
for entry in data.into_iter().rev() {
|
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);
|
document.load_layer_resources(responses);
|
||||||
let new_ids: HashMap<_, _> = entry.nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
|
let new_ids: HashMap<_, _> = entry.nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
|
||||||
let layer = LayerNodeIdentifier::new_unchecked(new_ids[&NodeId(0)]);
|
let layer = LayerNodeIdentifier::new_unchecked(new_ids[&NodeId(0)]);
|
||||||
|
|
|
@ -45,11 +45,8 @@ impl Pivot {
|
||||||
|
|
||||||
/// Recomputes the pivot position and transform.
|
/// Recomputes the pivot position and transform.
|
||||||
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
|
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
|
||||||
let mut layers = document
|
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
|
||||||
.network_interface
|
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
|
||||||
.selected_nodes(&[])
|
|
||||||
.unwrap()
|
|
||||||
.selected_visible_and_unlocked_layers(&document.network_interface);
|
|
||||||
let Some(first) = layers.next() else {
|
let Some(first) = layers.next() else {
|
||||||
// If no layers are selected then we revert things back to default
|
// If no layers are selected then we revert things back to default
|
||||||
self.normalized_pivot = DVec2::splat(0.5);
|
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 {
|
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) {
|
if let Some(intersection) = Self::hovered_artboard(document, input) {
|
||||||
self.selected_artboard = Some(intersection);
|
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_start = to_document.transform_point2(input.mouse.position);
|
||||||
tool_data.drag_current = 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) {
|
let state = if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
|
|
||||||
tool_data.start_resizing(selected_edges, document, input);
|
tool_data.start_resizing(selected_edges, document, input);
|
||||||
tool_data.get_snap_candidates(document, input);
|
tool_data.get_snap_candidates(document, input);
|
||||||
ArtboardToolFsmState::ResizingBounds
|
ArtboardToolFsmState::ResizingBounds
|
||||||
|
@ -258,7 +254,9 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
tool_data.drag_current = snapped.snapped_point_document;
|
tool_data.drag_current = snapped.snapped_point_document;
|
||||||
|
|
||||||
ArtboardToolFsmState::Drawing
|
ArtboardToolFsmState::Drawing
|
||||||
}
|
};
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
state
|
||||||
}
|
}
|
||||||
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
|
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
|
||||||
let from_center = input.keyboard.get(center as usize);
|
let from_center = input.keyboard.get(center as usize);
|
||||||
|
@ -427,32 +425,15 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
|
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
|
(ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds | ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
|
||||||
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
|
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
bounds.original_transforms.clear();
|
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);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
ArtboardToolFsmState::Ready { hovered }
|
ArtboardToolFsmState::Ready { hovered }
|
||||||
|
|
|
@ -381,7 +381,9 @@ impl Fsm for BrushToolFsmState {
|
||||||
|
|
||||||
(BrushToolFsmState::Drawing, BrushToolMessage::DragStop) => {
|
(BrushToolFsmState::Drawing, BrushToolMessage::DragStop) => {
|
||||||
if !tool_data.strokes.is_empty() {
|
if !tool_data.strokes.is_empty() {
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
|
} else {
|
||||||
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
}
|
}
|
||||||
tool_data.strokes.clear();
|
tool_data.strokes.clear();
|
||||||
|
|
||||||
|
|
|
@ -96,9 +96,8 @@ impl Fsm for FillToolFsmState {
|
||||||
_ => return self,
|
_ => return self,
|
||||||
};
|
};
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(GraphOperationMessage::FillSet { layer: layer_identifier, fill });
|
responses.add(GraphOperationMessage::FillSet { layer: layer_identifier, fill });
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
|
||||||
|
|
||||||
FillToolFsmState::Filling
|
FillToolFsmState::Filling
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,7 +254,7 @@ impl Fsm for FreehandToolFsmState {
|
||||||
if tool_data.dragged {
|
if tool_data.dragged {
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
} else {
|
} else {
|
||||||
responses.add(DocumentMessage::DocumentHistoryBackward);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
tool_data.end_point = None;
|
tool_data.end_point = None;
|
||||||
|
|
|
@ -284,6 +284,8 @@ impl Fsm for GradientToolFsmState {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
// Remove the selected point
|
// Remove the selected point
|
||||||
match selected_gradient.dragging {
|
match selected_gradient.dragging {
|
||||||
GradientDragTarget::Start => selected_gradient.gradient.stops.0.remove(0),
|
GradientDragTarget::Start => selected_gradient.gradient.stops.0.remove(0),
|
||||||
|
@ -326,8 +328,8 @@ impl Fsm for GradientToolFsmState {
|
||||||
(_, GradientToolMessage::InsertStop) => {
|
(_, GradientToolMessage::InsertStop) => {
|
||||||
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_visible_layers(&document.network_interface) {
|
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 };
|
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 transform = gradient_space_transform(layer, document);
|
||||||
|
|
||||||
let mouse = input.mouse.position;
|
let mouse = input.mouse.position;
|
||||||
let (start, end) = (transform.transform_point2(gradient.start), transform.transform_point2(gradient.end));
|
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.) {
|
if distance < (SELECTION_THRESHOLD * 2.) {
|
||||||
// Try and insert the new stop
|
// Try and insert the new stop
|
||||||
if let Some(index) = gradient.insert_stop(mouse, transform) {
|
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);
|
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) {
|
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 Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
||||||
let transform = gradient_space_transform(layer, document);
|
let transform = gradient_space_transform(layer, document);
|
||||||
|
|
||||||
// Check for dragging step
|
// Check for dragging step
|
||||||
for (index, (pos, _)) in gradient.stops.0.iter().enumerate() {
|
for (index, (pos, _)) in gradient.stops.0.iter().enumerate() {
|
||||||
let pos = transform.transform_point2(gradient.start.lerp(gradient.end, *pos));
|
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
|
GradientToolFsmState::Drawing
|
||||||
} else {
|
} else {
|
||||||
let selected_layer = document.click(input);
|
let selected_layer = document.click(input);
|
||||||
|
@ -409,8 +410,6 @@ impl Fsm for GradientToolFsmState {
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
|
|
||||||
// Use the already existing gradient if it exists
|
// Use the already existing gradient if it exists
|
||||||
let gradient = if let Some(gradient) = get_gradient(layer, &document.network_interface) {
|
let gradient = if let Some(gradient) = get_gradient(layer, &document.network_interface) {
|
||||||
gradient.clone()
|
gradient.clone()
|
||||||
|
@ -433,7 +432,9 @@ impl Fsm for GradientToolFsmState {
|
||||||
} else {
|
} else {
|
||||||
GradientToolFsmState::Ready
|
GradientToolFsmState::Ready
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
gradient_state
|
||||||
}
|
}
|
||||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
||||||
if let Some(selected_gradient) = &mut tool_data.selected_gradient {
|
if let Some(selected_gradient) = &mut tool_data.selected_gradient {
|
||||||
|
@ -473,6 +474,11 @@ impl Fsm for GradientToolFsmState {
|
||||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
|
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
|
||||||
input.mouse.finish_transaction(tool_data.drag_start, responses);
|
input.mouse.finish_transaction(tool_data.drag_start, responses);
|
||||||
tool_data.snap_manager.cleanup(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
|
GradientToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ impl Fsm for ImaginateToolFsmState {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
|
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
|
||||||
shape_data.start(document, input);
|
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));
|
//shape_data.layer = Some(LayerNodeIdentifier::new(NodeId(generate_uuid()), &document.network_interface));
|
||||||
responses.add(DocumentMessage::DeselectAllLayers);
|
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 {
|
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() {
|
match self.segment.as_mut() {
|
||||||
None => {
|
None => {
|
||||||
warn!("Segment was `None` before `end_insertion`")
|
warn!("Segment was `None` before `end_insertion`")
|
||||||
}
|
}
|
||||||
Some(closed_segment) => {
|
Some(closed_segment) => {
|
||||||
if let InsertEndKind::Add { shift } = kind {
|
if let InsertEndKind::Add { shift } = kind {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
closed_segment.adjusted_insert_and_select(shape_editor, responses, shift);
|
closed_segment.adjusted_insert_and_select(shape_editor, responses, shift);
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
commit_transaction = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.segment = None;
|
self.segment = None;
|
||||||
|
if commit_transaction {
|
||||||
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
|
} else {
|
||||||
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
|
}
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
@ -323,15 +328,18 @@ impl PathToolData {
|
||||||
|
|
||||||
// Select the first point within the threshold (in pixels)
|
// 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) {
|
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 {
|
if let Some(selected_points) = selected_points {
|
||||||
self.drag_start_pos = input.mouse.position;
|
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);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
PathToolFsmState::Dragging
|
PathToolFsmState::Dragging
|
||||||
}
|
}
|
||||||
// We didn't find a point nearby, so now we'll try to add a point into the closest path segment
|
// 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) {
|
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 {
|
if direct_insert_without_sliding {
|
||||||
self.start_insertion(responses, closed_segment);
|
self.start_insertion(responses, closed_segment);
|
||||||
self.end_insertion(shape_editor, responses, InsertEndKind::Add { shift: add_to_selection })
|
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
|
// We didn't find a segment path, so consider selecting the nearest shape instead
|
||||||
else if let Some(layer) = document.click(input) {
|
else if let Some(layer) = document.click(input) {
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
if add_to_selection {
|
if add_to_selection {
|
||||||
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
||||||
} else {
|
} else {
|
||||||
|
@ -361,16 +370,7 @@ impl PathToolData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_dragging_point(
|
fn start_dragging_point(&mut self, selected_points: SelectedPointsInfo, input: &InputPreprocessorMessageHandler, document: &DocumentMessageHandler, shape_editor: &mut ShapeState) {
|
||||||
&mut self,
|
|
||||||
selected_points: SelectedPointsInfo,
|
|
||||||
input: &InputPreprocessorMessageHandler,
|
|
||||||
document: &DocumentMessageHandler,
|
|
||||||
shape_editor: &mut ShapeState,
|
|
||||||
responses: &mut VecDeque<Message>,
|
|
||||||
) {
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
|
|
||||||
let mut manipulators = HashMap::with_hasher(NoHashBuilder);
|
let mut manipulators = HashMap::with_hasher(NoHashBuilder);
|
||||||
let mut unselected = Vec::new();
|
let mut unselected = Vec::new();
|
||||||
for (&layer, state) in &shape_editor.selected_shape_state {
|
for (&layer, state) in &shape_editor.selected_shape_state {
|
||||||
|
@ -587,7 +587,6 @@ impl Fsm for PathToolFsmState {
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(PathToolFsmState::DrawingBox, PathToolMessage::Escape | PathToolMessage::RightClick) => {
|
(PathToolFsmState::DrawingBox, PathToolMessage::Escape | PathToolMessage::RightClick) => {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
@ -621,6 +620,7 @@ impl Fsm for PathToolFsmState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
|
@ -629,7 +629,7 @@ impl Fsm for PathToolFsmState {
|
||||||
// Delete key
|
// Delete key
|
||||||
(_, PathToolMessage::Delete) => {
|
(_, PathToolMessage::Delete) => {
|
||||||
// Delete the selected points and clean up overlays
|
// Delete the selected points and clean up overlays
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
shape_editor.delete_selected_points(document, responses);
|
shape_editor.delete_selected_points(document, responses);
|
||||||
responses.add(PathToolMessage::SelectionChanged);
|
responses.add(PathToolMessage::SelectionChanged);
|
||||||
|
|
||||||
|
@ -689,14 +689,14 @@ impl Fsm for PathToolFsmState {
|
||||||
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
|
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
|
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
responses.add(PathToolMessage::SelectionChanged);
|
responses.add(PathToolMessage::SelectionChanged);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::ManipulatorMakeHandlesFree) => {
|
(_, PathToolMessage::ManipulatorMakeHandlesFree) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);
|
shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, _) => PathToolFsmState::Ready,
|
(_, _) => PathToolFsmState::Ready,
|
||||||
|
|
|
@ -630,6 +630,7 @@ impl Fsm for PenToolFsmState {
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
|
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
|
||||||
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
tool_data.layer = None;
|
tool_data.layer = None;
|
||||||
tool_data.handle_end = None;
|
tool_data.handle_end = None;
|
||||||
tool_data.latest_points.clear();
|
tool_data.latest_points.clear();
|
||||||
|
@ -639,6 +640,8 @@ impl Fsm for PenToolFsmState {
|
||||||
PenToolFsmState::Ready
|
PenToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PenToolMessage::Abort) => {
|
(_, PenToolMessage::Abort) => {
|
||||||
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
|
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
self
|
self
|
||||||
|
|
|
@ -479,7 +479,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
}
|
}
|
||||||
// Only highlight layers if the viewport is not being panned (middle mouse button is pressed)
|
// 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
|
// 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
|
// Get the layer the user is hovering over
|
||||||
let click = document.click(input);
|
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()));
|
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 {
|
if let Some(intersection) = intersection {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
|
|
||||||
tool_data.layer_selected_on_start = Some(intersection);
|
tool_data.layer_selected_on_start = Some(intersection);
|
||||||
selected = intersection_list;
|
selected = intersection_list;
|
||||||
|
@ -659,6 +658,8 @@ impl Fsm for SelectToolFsmState {
|
||||||
_ => drag_deepest_manipulation(responses, selected, tool_data, document),
|
_ => drag_deepest_manipulation(responses, selected, tool_data, document),
|
||||||
}
|
}
|
||||||
tool_data.get_snap_candidates(document, input);
|
tool_data.get_snap_candidates(document, input);
|
||||||
|
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
SelectToolFsmState::Dragging
|
SelectToolFsmState::Dragging
|
||||||
} else {
|
} else {
|
||||||
// Deselect all layers if using shallowest selection behavior
|
// Deselect all layers if using shallowest selection behavior
|
||||||
|
@ -894,8 +895,8 @@ impl Fsm for SelectToolFsmState {
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Dragging, SelectToolMessage::Enter) => {
|
(SelectToolFsmState::Dragging, SelectToolMessage::Enter) => {
|
||||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||||
true => DocumentMessage::Undo,
|
true => DocumentMessage::AbortTransaction,
|
||||||
false => DocumentMessage::CommitTransaction,
|
false => DocumentMessage::EndTransaction,
|
||||||
};
|
};
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
responses.add_front(response);
|
responses.add_front(response);
|
||||||
|
@ -905,6 +906,8 @@ impl Fsm for SelectToolFsmState {
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Dragging, SelectToolMessage::DragStop { remove_from_selection }) => {
|
(SelectToolFsmState::Dragging, SelectToolMessage::DragStop { remove_from_selection }) => {
|
||||||
// Deselect layer if not snap dragging
|
// 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() {
|
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 quad = tool_data.selection_quad();
|
||||||
let intersection = document.intersect_quad(quad, input);
|
let intersection = document.intersect_quad(quad, input);
|
||||||
|
@ -950,7 +953,6 @@ impl Fsm for SelectToolFsmState {
|
||||||
tool_data.has_dragged = false;
|
tool_data.has_dragged = false;
|
||||||
tool_data.layer_selected_on_start = None;
|
tool_data.layer_selected_on_start = None;
|
||||||
|
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
tool_data.select_single_layer = None;
|
tool_data.select_single_layer = None;
|
||||||
|
|
||||||
|
@ -959,8 +961,8 @@ impl Fsm for SelectToolFsmState {
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::ResizingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
(SelectToolFsmState::ResizingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
||||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||||
true => DocumentMessage::Undo,
|
true => DocumentMessage::AbortTransaction,
|
||||||
false => DocumentMessage::CommitTransaction,
|
false => DocumentMessage::EndTransaction,
|
||||||
};
|
};
|
||||||
responses.add(response);
|
responses.add(response);
|
||||||
|
|
||||||
|
@ -975,8 +977,8 @@ impl Fsm for SelectToolFsmState {
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::RotatingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
(SelectToolFsmState::RotatingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
||||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||||
true => DocumentMessage::Undo,
|
true => DocumentMessage::AbortTransaction,
|
||||||
false => DocumentMessage::CommitTransaction,
|
false => DocumentMessage::EndTransaction,
|
||||||
};
|
};
|
||||||
responses.add(response);
|
responses.add(response);
|
||||||
|
|
||||||
|
@ -989,8 +991,8 @@ impl Fsm for SelectToolFsmState {
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
(SelectToolFsmState::DraggingPivot, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
||||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||||
true => DocumentMessage::Undo,
|
true => DocumentMessage::AbortTransaction,
|
||||||
false => DocumentMessage::CommitTransaction,
|
false => DocumentMessage::EndTransaction,
|
||||||
};
|
};
|
||||||
responses.add(response);
|
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();
|
let current_selected: HashSet<_> = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect();
|
||||||
if new_selected != current_selected {
|
if new_selected != current_selected {
|
||||||
tool_data.layers_dragging = new_selected.into_iter().collect();
|
tool_data.layers_dragging = new_selected.into_iter().collect();
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||||
nodes: tool_data
|
nodes: tool_data
|
||||||
.layers_dragging
|
.layers_dragging
|
||||||
|
@ -1027,7 +1028,8 @@ impl Fsm for SelectToolFsmState {
|
||||||
SelectToolFsmState::Ready { selection }
|
SelectToolFsmState::Ready { selection }
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Ready { .. }, SelectToolMessage::Enter) => {
|
(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() {
|
if let Some(layer) = selected_layers.next() {
|
||||||
// Check that only one layer is selected
|
// Check that only one layer is selected
|
||||||
|
@ -1041,8 +1043,8 @@ impl Fsm for SelectToolFsmState {
|
||||||
SelectToolFsmState::Ready { selection }
|
SelectToolFsmState::Ready { selection }
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Dragging, SelectToolMessage::Abort) => {
|
(SelectToolFsmState::Dragging, SelectToolMessage::Abort) => {
|
||||||
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
responses.add(DocumentMessage::Undo);
|
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
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) {
|
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(
|
tool_data.layers_dragging.append(&mut vec![document
|
||||||
document.network_interface.root_node(&[]).expect("Root node should exist when dragging layers").node_id,
|
.find_deepest(&selected)
|
||||||
&document.network_interface,
|
.unwrap_or(LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()).next().expect("Child should exist when dragging deepest"))]);
|
||||||
&[],
|
|
||||||
))]);
|
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||||
nodes: tool_data
|
nodes: tool_data
|
||||||
.layers_dragging
|
.layers_dragging
|
||||||
|
|
|
@ -228,6 +228,8 @@ impl Fsm for SplineToolFsmState {
|
||||||
SplineToolFsmState::Drawing
|
SplineToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
(SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => {
|
(SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => {
|
||||||
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
|
|
||||||
let Some(layer) = tool_data.layer else {
|
let Some(layer) = tool_data.layer else {
|
||||||
return SplineToolFsmState::Ready;
|
return SplineToolFsmState::Ready;
|
||||||
};
|
};
|
||||||
|
@ -279,7 +281,7 @@ impl Fsm for SplineToolFsmState {
|
||||||
(SplineToolFsmState::Drawing, SplineToolMessage::Confirm | SplineToolMessage::Abort) => {
|
(SplineToolFsmState::Drawing, SplineToolMessage::Confirm | SplineToolMessage::Abort) => {
|
||||||
if tool_data.points.len() >= 2 {
|
if tool_data.points.len() >= 2 {
|
||||||
update_spline(document, tool_data, false, responses);
|
update_spline(document, tool_data, false, responses);
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
} else {
|
} else {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
}
|
}
|
||||||
|
|
|
@ -259,7 +259,7 @@ impl TextToolData {
|
||||||
self.layer = layer;
|
self.layer = layer;
|
||||||
self.load_layer_text_node(document);
|
self.load_layer_text_node(document);
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
self.set_editing(true, font_cache, document, responses);
|
self.set_editing(true, font_cache, document, responses);
|
||||||
|
|
||||||
|
@ -282,7 +282,7 @@ impl TextToolData {
|
||||||
}
|
}
|
||||||
// Create new text
|
// Create new text
|
||||||
else if let Some(editing_text) = self.editing_text.as_ref().filter(|_| state == TextToolFsmState::Ready) {
|
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()));
|
self.layer = LayerNodeIdentifier::new_unchecked(NodeId(generate_uuid()));
|
||||||
|
|
||||||
|
@ -320,7 +320,8 @@ impl TextToolData {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
|
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()?;
|
let layer = selected_layers.next()?;
|
||||||
|
|
||||||
// Check that only one layer is selected
|
// Check that only one layer is selected
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub trait EditorTestUtils {
|
||||||
fn move_mouse(&mut self, x: f64, y: f64);
|
fn move_mouse(&mut self, x: f64, y: f64);
|
||||||
fn mousedown(&mut self, state: EditorMouseState);
|
fn mousedown(&mut self, state: EditorMouseState);
|
||||||
fn mouseup(&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 input(&mut self, message: InputPreprocessorMessage);
|
||||||
fn select_tool(&mut self, typ: ToolType);
|
fn select_tool(&mut self, typ: ToolType);
|
||||||
fn select_primary_color(&mut self, color: Color);
|
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) {
|
fn drag_tool(&mut self, typ: ToolType, x1: f64, y1: f64, x2: f64, y2: f64) {
|
||||||
self.select_tool(typ);
|
self.select_tool(typ);
|
||||||
self.move_mouse(x1, y1);
|
self.move_mouse(x1, y1);
|
||||||
self.lmb_mousedown(x1, y1);
|
self.left_mousedown(x1, y1);
|
||||||
self.move_mouse(x2, y2);
|
self.move_mouse(x2, y2);
|
||||||
self.mouseup(EditorMouseState {
|
self.mouseup(EditorMouseState {
|
||||||
editor_position: (x2, y2).into(),
|
editor_position: (x2, y2).into(),
|
||||||
|
@ -91,7 +91,7 @@ impl EditorTestUtils for Editor {
|
||||||
self.handle_message(InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys });
|
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 {
|
self.mousedown(EditorMouseState {
|
||||||
editor_position: (x, y).into(),
|
editor_position: (x, y).into(),
|
||||||
mouse_keys: MouseKeys::LEFT,
|
mouse_keys: MouseKeys::LEFT,
|
||||||
|
|
|
@ -182,6 +182,12 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPointerUp(e: PointerEvent) {
|
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 (!e.buttons) viewportPointerInteractionOngoing = false;
|
||||||
|
|
||||||
if (textToolInteractiveInputElement) return;
|
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
|
// `e.buttons` is always 0 in the `mouseup` event, so we have to convert from `e.button` instead
|
||||||
let buttons = 1;
|
let buttons = 1;
|
||||||
if (e.button === 0) buttons = 1; // LMB
|
if (e.button === 0) buttons = 1; // Left
|
||||||
if (e.button === 1) buttons = 4; // MMB
|
if (e.button === 2) buttons = 2; // Right
|
||||||
if (e.button === 2) buttons = 2; // RMB
|
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);
|
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||||
editor.handle.onDoubleClick(e.clientX, e.clientY, buttons, modifiers);
|
editor.handle.onDoubleClick(e.clientX, e.clientY, buttons, modifiers);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue