Add project files.

This commit is contained in:
Ankitkumar Satapara
2026-04-17 22:31:58 +05:30
commit 21aaef6776
473 changed files with 50152 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
using System.Windows.Input;
namespace Nodify.Interactivity
{
/// <inheritdoc cref="MultiGesture.Match.All" />
public sealed class AllGestures : MultiGesture
{
public AllGestures(params InputGesture[] gestures) : base(Match.All, gestures)
{
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Windows.Input;
namespace Nodify.Interactivity
{
/// <inheritdoc cref="MultiGesture.Match.Any" />
public sealed class AnyGesture : MultiGesture
{
public AnyGesture(params InputGesture[] gestures) : base(Match.Any, gestures)
{
}
}
}

View File

@@ -0,0 +1,653 @@
using System.Windows.Input;
namespace Nodify.Interactivity
{
/// <summary>Gestures used by built-in controls inside the <see cref="NodifyEditor"/>.</summary>
public class EditorGestures
{
public static readonly EditorGestures Mappings = new EditorGestures();
/// <summary>Gestures for the selection.</summary>
public class SelectionGestures
{
/// <summary>Disable selection gestures.</summary>
public static readonly SelectionGestures None = new SelectionGestures(MouseAction.None);
/// <summary>
/// Initializes a new instance of the <see cref="SelectionGestures"/> class with specified mouse action
/// and a flag indicating whether modifier keys should be ignored when releasing the mouse button.
/// </summary>
/// <param name="mouseAction">The mouse action to trigger the gestures.</param>
/// <param name="ignoreModifierKeysOnRelease">
/// A value indicating whether modifier keys (Alt, Shift, Control) should be ignored when the mouse button is released.
/// </param>
public SelectionGestures(MouseAction mouseAction, bool ignoreModifierKeysOnRelease)
{
Replace = new MouseGesture(mouseAction);
Remove = new MouseGesture(mouseAction, ModifierKeys.Alt, ignoreModifierKeysOnRelease);
Append = new MouseGesture(mouseAction, ModifierKeys.Shift, ignoreModifierKeysOnRelease);
Invert = new MouseGesture(mouseAction, ModifierKeys.Control, ignoreModifierKeysOnRelease);
Select = new AnyGesture(Replace, Remove, Append, Invert);
Cancel = new KeyGesture(Key.Escape);
}
/// <summary>
/// Initializes a new instance of the <see cref="SelectionGestures"/> class with a specified mouse action.
/// Modifier keys will be ignored when releasing the mouse button.
/// </summary>
/// <param name="mouseAction">The mouse action to trigger the gestures.</param>
public SelectionGestures(MouseAction mouseAction)
: this(mouseAction, true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SelectionGestures"/> class with a flag indicating
/// whether modifier keys should be ignored when releasing the mouse button.
/// The default mouse action is <see cref="MouseAction.LeftClick"/>.
/// </summary>
/// <param name="ignoreModifierKeysOnRelease">
/// A value indicating whether modifier keys (Alt, Shift, Control) should be ignored when the mouse button is released.
/// </param>
public SelectionGestures(bool ignoreModifierKeysOnRelease)
: this(MouseAction.LeftClick, ignoreModifierKeysOnRelease)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SelectionGestures"/> class with default values:
/// the mouse action is <see cref="MouseAction.LeftClick"/>, and modifier keys are ignored when releasing the mouse button.
/// </summary>
public SelectionGestures() : this(true)
{
}
/// <summary>Gesture to replace previous selection with the selected items.</summary>
/// <remarks>Defaults to <see cref="MouseAction.LeftClick"/>.</remarks>
public InputGestureRef Replace { get; }
/// <summary>Gesture to remove the selected items from the previous selection.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Alt"/>+<see cref="MouseAction.LeftClick"/>.</remarks>
public InputGestureRef Remove { get; }
/// <summary>Gesture to add the new selected items to the previous selection.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Shift"/>+<see cref="MouseAction.LeftClick"/>.</remarks>
public InputGestureRef Append { get; }
/// <summary>Gesture to invert the selected items.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Control"/>+<see cref="MouseAction.LeftClick"/>.</remarks>
public InputGestureRef Invert { get; }
/// <summary>Cancel the current selection operation reverting to the previous selection.</summary>
/// <remarks>Defaults to <see cref="Key.Escape"/>.</remarks>
public InputGestureRef Cancel { get; }
/// <summary>Gesture used to start selecting using a <see cref="SelectionGestures"/> strategy.</summary>
public InputGestureRef Select { get; }
/// <summary>Copies from the specified gestures.</summary>
/// <param name="gestures">The gestures to copy.</param>
public void Apply(SelectionGestures gestures)
{
Replace.Value = gestures.Replace.Value;
Remove.Value = gestures.Remove.Value;
Append.Value = gestures.Append.Value;
Invert.Value = gestures.Invert.Value;
Select.Value = gestures.Select.Value;
Cancel.Value = gestures.Cancel.Value;
}
/// <summary>
/// Unbinds the all the gestures used for selection.
/// </summary>
public void Unbind()
=> Apply(None);
}
/// <summary>Gestures for the item containers.</summary>
public class ItemContainerGestures
{
public ItemContainerGestures()
{
Selection = new SelectionGestures();
Selection.Select.Value = new AnyGesture(
Selection.Replace,
Selection.Remove,
Selection.Append,
Selection.Invert);
Drag = new AnyGesture(Selection.Replace, Selection.Remove, Selection.Append, Selection.Invert);
CancelAction = new AnyGesture(new MouseGesture(MouseAction.RightClick), new KeyGesture(Key.Escape));
}
/// <summary>Gesture to select the container using a <see cref="SelectionGestures"/> strategy.</summary>
/// <remarks>Defaults to <see cref="MouseAction.LeftClick"/> or any of the <see cref="SelectionGestures"/> gestures.</remarks>
public SelectionGestures Selection { get; }
/// <summary>Gesture to start and complete a dragging operation.</summary>
/// <remarks>Using a <see cref="Selection"/> strategy to drag from a new selection.
/// <br /> Defaults to any of the <see cref="Selection"/> gestures.
/// </remarks>
public InputGestureRef Drag { get; }
/// <summary>Gesture to cancel the dragging operation.</summary>
/// <remarks>Defaults to <see cref="MouseAction.RightClick"/> or <see cref="Key.Escape"/>.</remarks>
public InputGestureRef CancelAction { get; }
/// <summary>Copies from the specified gestures.</summary>
/// <param name="gestures">The gestures to copy.</param>
public void Apply(ItemContainerGestures gestures)
{
Selection.Apply(gestures.Selection);
Drag.Value = gestures.Drag.Value;
CancelAction.Value = gestures.CancelAction.Value;
}
/// <summary>
/// Unbinds all the gestures.
/// </summary>
public void Unbind()
{
Selection.Unbind();
Drag.Unbind();
CancelAction.Unbind();
}
}
/// <summary>
/// Keyboard gestures used for navigating the editor and moving selected items.
/// </summary>
public class DirectionalNavigationGestures
{
public DirectionalNavigationGestures(ModifierKeys modifierKeys = ModifierKeys.None)
{
Up = new KeyGesture(Key.Up, modifierKeys);
Left = new KeyGesture(Key.Left, modifierKeys);
Down = new KeyGesture(Key.Down, modifierKeys);
Right = new KeyGesture(Key.Right, modifierKeys);
}
public DirectionalNavigationGestures(Key triggerKey, ModifierKeys modifierKeys = ModifierKeys.None, bool repeated = false)
{
Up = new KeyComboGesture(triggerKey, Key.Up, modifierKeys) { AllowRepeatingComboKey = repeated };
Left = new KeyComboGesture(triggerKey, Key.Left, modifierKeys) { AllowRepeatingComboKey = repeated };
Down = new KeyComboGesture(triggerKey, Key.Down, modifierKeys) { AllowRepeatingComboKey = repeated };
Right = new KeyComboGesture(triggerKey, Key.Right, modifierKeys) { AllowRepeatingComboKey = repeated };
}
/// <summary>
/// Gesture used for navigating or moving upward.
/// </summary>
public InputGestureRef Up { get; }
/// <summary>
/// Gesture used for navigating or moving left.
/// </summary>
public InputGestureRef Left { get; }
/// <summary>
/// Gesture used for navigating or moving downward.
/// </summary>
public InputGestureRef Down { get; }
/// <summary>
/// Gesture used for navigating or moving right.
/// </summary>
public InputGestureRef Right { get; }
/// <summary>Copies from the specified gestures.</summary>
/// <param name="gestures">The gestures to copy.</param>
public void Apply(DirectionalNavigationGestures gestures)
{
Up.Value = gestures.Up.Value;
Left.Value = gestures.Left.Value;
Down.Value = gestures.Down.Value;
Right.Value = gestures.Right.Value;
}
/// <summary>
/// Unbinds all the gestures.
/// </summary>
public void Unbind()
{
Up.Unbind();
Left.Unbind();
Down.Unbind();
Right.Unbind();
}
}
/// <summary>Gestures for the editor.</summary>
public class NodifyEditorGestures
{
/// <summary>
/// Keyboard gestures used for navigation, selection, and manipulation in the editor.
/// </summary>
public class KeyboardGestures
{
public KeyboardGestures()
{
Pan = new DirectionalNavigationGestures(Key.Space, repeated: true);
DragSelection = new DirectionalNavigationGestures(ModifierKeys.Control);
NavigateSelection = new DirectionalNavigationGestures(ModifierKeys.None);
ToggleSelected = new AnyGesture(new KeyGesture(Key.Space), new KeyGesture(Key.Enter));
DeselectAll = new KeyGesture(Key.Escape);
NextNavigationLayer = new KeyGesture(Key.OemCloseBrackets, ModifierKeys.Control);
PrevNavigationLayer = new KeyGesture(Key.OemOpenBrackets, ModifierKeys.Control);
}
/// <summary>
/// Directional gestures used for panning the viewport.
/// </summary>
/// <remarks>Defaults to <see cref="Key.Space"/>+arrow keys.</remarks>
public DirectionalNavigationGestures Pan { get; }
/// <summary>
/// Directional gestures used for dragging the selected items.
/// </summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Control"/>+arrow keys.</remarks>
public DirectionalNavigationGestures DragSelection { get; }
/// <summary>
/// Directional gestures used to navigate the selection focus (e.g., between nodes).
/// </summary>
/// <remarks>Defaults to arrow keys.</remarks>
public DirectionalNavigationGestures NavigateSelection { get; }
/// <summary>
/// Gesture used to toggle the selected state of the currently focused item.
/// </summary>
/// <remarks>Defaults to <see cref="Key.Space"/></remarks>
public InputGestureRef ToggleSelected { get; }
/// <summary>
/// Gesture used to clear the current selection.
/// </summary>
/// <remarks>Defaults to <see cref="Key.Escape"/>.</remarks>
public InputGestureRef DeselectAll { get; }
/// <summary>
/// Gesture used to activate the previous keyboard navigation layer.
/// </summary>
/// <remarks><see cref="ModifierKeys.Control"/>+<see cref="Key.OemCloseBrackets"/>.</remarks>
public InputGestureRef NextNavigationLayer { get; }
/// <summary>
/// Gesture used to activate the next keyboard navigation layer.
/// </summary>
/// <remarks><see cref="ModifierKeys.Control"/>+<see cref="Key.OemOpenBrackets"/>.</remarks>
public InputGestureRef PrevNavigationLayer { get; }
/// <summary>Copies from the specified gestures.</summary>
/// <param name="gestures">The gestures to copy.</param>
public void Apply(KeyboardGestures gestures)
{
Pan.Apply(gestures.Pan);
DragSelection.Apply(gestures.DragSelection);
NavigateSelection.Apply(gestures.NavigateSelection);
ToggleSelected.Value = gestures.ToggleSelected.Value;
DeselectAll.Value = gestures.DeselectAll.Value;
NextNavigationLayer.Value = gestures.NextNavigationLayer.Value;
PrevNavigationLayer.Value = gestures.PrevNavigationLayer.Value;
}
/// <summary>
/// Unbinds all the gestures.
/// </summary>
public void Unbind()
{
Pan.Unbind();
DragSelection.Unbind();
NavigateSelection.Unbind();
ToggleSelected.Unbind();
DeselectAll.Unbind();
NextNavigationLayer.Unbind();
PrevNavigationLayer.Unbind();
}
}
public NodifyEditorGestures()
{
Keyboard = new KeyboardGestures();
Selection = new SelectionGestures();
SelectAll = ApplicationCommands.SelectAll.InputGestures[0].AsRef();
Cutting = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Alt | ModifierKeys.Shift, true);
PushItems = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control | ModifierKeys.Shift, true);
Pan = new AnyGesture(new MouseGesture(MouseAction.RightClick), new MouseGesture(MouseAction.MiddleClick));
ZoomModifierKey = ModifierKeys.None;
ZoomIn = new AnyGesture(new KeyGesture(Key.OemPlus, ModifierKeys.Control), new KeyGesture(Key.Add, ModifierKeys.Control));
ZoomOut = new AnyGesture(new KeyGesture(Key.OemMinus, ModifierKeys.Control), new KeyGesture(Key.Subtract, ModifierKeys.Control));
ResetViewport = new KeyGesture(Key.Home);
FitToScreen = new KeyGesture(Key.Home, ModifierKeys.Shift);
CancelAction = new AnyGesture(new MouseGesture(MouseAction.RightClick), new KeyGesture(Key.Escape));
PanWithMouseWheel = false;
PanHorizontalModifierKey = ModifierKeys.Shift;
PanVerticalModifierKey = ModifierKeys.None;
}
public KeyboardGestures Keyboard { get; }
/// <summary>Gesture used to start selecting using a <see cref="SelectionGestures"/> strategy.</summary>
public SelectionGestures Selection { get; }
/// <summary>Gesture used to select all <see cref="Nodify.ItemContainer"/>s in the editor.</summary>
public InputGestureRef SelectAll { get; }
/// <summary>Gesture used to start cutting connections.</summary>
public InputGestureRef Cutting { get; }
/// <summary>Gesture used to start panning.</summary>
/// <remarks>Defaults to <see cref="MouseAction.RightClick"/> or <see cref="MouseAction.MiddleClick"/>.</remarks>
public InputGestureRef Pan { get; }
/// <summary>Whether panning using mouse wheel is allowed.</summary>
/// <remarks>Set the <see cref="ZoomModifierKey"/> to allow zooming using the mouse wheel.</remarks>
public bool PanWithMouseWheel { get; set; }
/// <summary>The modifier key required to start panning vertically with the mouse wheel (see <see cref="PanWithMouseWheel"/>)</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.None"/>.</remarks>
public ModifierKeys PanVerticalModifierKey { get; set; }
/// <summary>The modifier key required to start panning horizontally with the mouse wheel (see <see cref="PanWithMouseWheel"/>)</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Shift"/>.</remarks>
public ModifierKeys PanHorizontalModifierKey { get; set; }
/// <summary>Gesture used to start pushing.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Control"/>+<see cref="ModifierKeys.Shift"/>+<see cref="MouseAction.LeftClick"/>.</remarks>
public InputGestureRef PushItems { get; }
/// <summary>The key modifier required to start zooming by mouse wheel.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.None"/>.</remarks>
public ModifierKeys ZoomModifierKey { get; set; }
/// <summary>Gesture used to zoom in.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Control"/>+<see cref="Key.OemPlus"/>.</remarks>
public InputGestureRef ZoomIn { get; }
/// <summary>Gesture used to zoom out.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Control"/>+<see cref="Key.OemMinus"/>.</remarks>
public InputGestureRef ZoomOut { get; }
/// <summary>Gesture used to move the editor's viewport location to (0, 0) and set the zoom to 1.</summary>
/// <remarks>Defaults to <see cref="Key.Home"/>.</remarks>
public InputGestureRef ResetViewport { get; }
/// <summary>Gesture used to fit as many containers as possible into the viewport.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Shift"/>+<see cref="Key.Home"/>.</remarks>
public InputGestureRef FitToScreen { get; }
/// <summary>Gesture to cancel the current operation.</summary>
/// <remarks>Defaults to <see cref="MouseAction.RightClick"/> or <see cref="Key.Escape"/>.</remarks>
public InputGestureRef CancelAction { get; }
/// <summary>Copies from the specified gestures.</summary>
/// <param name="gestures">The gestures to copy.</param>
public void Apply(NodifyEditorGestures gestures)
{
Keyboard.Apply(gestures.Keyboard);
Selection.Apply(gestures.Selection);
SelectAll.Value = gestures.SelectAll.Value;
Cutting.Value = gestures.Cutting.Value;
PushItems.Value = gestures.PushItems.Value;
Pan.Value = gestures.Pan.Value;
ZoomModifierKey = gestures.ZoomModifierKey;
ZoomIn.Value = gestures.ZoomIn.Value;
ZoomOut.Value = gestures.ZoomOut.Value;
ResetViewport.Value = gestures.ResetViewport.Value;
FitToScreen.Value = gestures.FitToScreen.Value;
CancelAction.Value = gestures.CancelAction.Value;
PanWithMouseWheel = gestures.PanWithMouseWheel;
PanHorizontalModifierKey = gestures.PanHorizontalModifierKey;
PanVerticalModifierKey = gestures.PanVerticalModifierKey;
}
/// <summary>
/// Unbinds all the gestures.
/// </summary>
public void Unbind()
{
Keyboard.Unbind();
Selection.Unbind();
SelectAll.Unbind();
Cutting.Unbind();
Pan.Unbind();
PushItems.Unbind();
ZoomIn.Unbind();
ZoomOut.Unbind();
ResetViewport.Unbind();
FitToScreen.Unbind();
CancelAction.Unbind();
}
}
/// <summary>Gestures used by the <see cref="Connector"/>.</summary>
public class ConnectorGestures
{
public ConnectorGestures()
{
Disconnect = new AnyGesture(new MouseGesture(MouseAction.LeftClick, ModifierKeys.Alt), new KeyGesture(Key.Delete));
Connect = new AnyGesture(new MouseGesture(MouseAction.LeftClick), new KeyGesture(Key.Space));
CancelAction = new AnyGesture(new MouseGesture(MouseAction.RightClick), new KeyGesture(Key.Escape));
}
/// <summary>Gesture to call the <see cref="Connector.DisconnectCommand"/>.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Alt"/>+<see cref="MouseAction.LeftClick"/> or <see cref="Key.Delete"/>.</remarks>
public InputGestureRef Disconnect { get; }
/// <summary>Gesture to start and complete a pending connection.</summary>
/// <remarks>Defaults to <see cref="MouseAction.LeftClick"/>.</remarks>
public InputGestureRef Connect { get; }
/// <summary>Gesture to cancel the pending connection.</summary>
/// <remarks>Defaults to <see cref="MouseAction.RightClick"/> or <see cref="Key.Escape"/>.</remarks>
public InputGestureRef CancelAction { get; }
/// <summary>Copies from the specified gestures.</summary>
/// <param name="gestures">The gestures to copy.</param>
public void Apply(ConnectorGestures gestures)
{
Disconnect.Value = gestures.Disconnect.Value;
Connect.Value = gestures.Connect.Value;
CancelAction.Value = gestures.CancelAction.Value;
}
/// <summary>
/// Unbinds all the gestures.
/// </summary>
public void Unbind()
{
Disconnect.Unbind();
Connect.Unbind();
CancelAction.Unbind();
}
}
/// <summary>Gestures used by the <see cref="BaseConnection"/>.</summary>
public class ConnectionGestures
{
public ConnectionGestures()
{
Split = new MouseGesture(MouseAction.LeftDoubleClick);
Selection = new SelectionGestures(MouseAction.LeftClick);
Disconnect = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Alt);
}
/// <summary>Gesture to call the <see cref="BaseConnection.SplitCommand"/> command.</summary>
/// <remarks>Defaults to <see cref="MouseAction.LeftDoubleClick"/>.</remarks>
public InputGestureRef Split { get; }
/// <summary>Gesture used to start selecting using a <see cref="SelectionGestures"/> strategy.</summary>
public SelectionGestures Selection { get; }
/// <summary>Gesture to call the <see cref="BaseConnection.DisconnectCommand"/> command.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Alt"/>+<see cref="MouseAction.LeftClick"/>.</remarks>
public InputGestureRef Disconnect { get; }
/// <summary>Copies from the specified gestures.</summary>
/// <param name="gestures">The gestures to copy.</param>
public void Apply(ConnectionGestures gestures)
{
Split.Value = gestures.Split.Value;
Disconnect.Value = gestures.Disconnect.Value;
Selection.Apply(gestures.Selection);
}
/// <summary>
/// Unbinds all the gestures.
/// </summary>
public void Unbind()
{
Split.Unbind();
Selection.Unbind();
Disconnect.Unbind();
}
}
/// <summary>Gestures for the <see cref="GroupingNode"/>.</summary>
public class GroupingNodeGestures
{
public GroupingNodeGestures()
{
SwitchMovementMode = ModifierKeys.Shift;
ToggleContentSelection = new AnyGesture(new KeyGesture(Key.Space, ModifierKeys.Control), new KeyGesture(Key.Enter, ModifierKeys.Control));
}
/// <summary>The key modifier that will toggle between <see cref="GroupingMovementMode"/>s.</summary>
/// <remarks>The modifier must be allowed by the <see cref="ItemContainer.Drag"/> gesture.
/// <br /> Defaults to <see cref="ModifierKeys.Shift"/>.
/// </remarks>
public ModifierKeys SwitchMovementMode { get; set; }
/// <summary>Gesture to toggle the content selection of the <see cref="GroupingNode"/> when it is selected.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Control"/>+<see cref="Key.Space"/>.</remarks>
public InputGestureRef ToggleContentSelection { get; }
/// <summary>Copies from the specified gestures.</summary>
/// <param name="gestures">The gestures to copy.</param>
public void Apply(GroupingNodeGestures gestures)
{
SwitchMovementMode = gestures.SwitchMovementMode;
ToggleContentSelection.Value = gestures.ToggleContentSelection.Value;
}
/// <summary>
/// Unbinds all the gestures.
/// </summary>
public void Unbind()
{
ToggleContentSelection.Unbind();
}
}
/// <summary>Gestures used by the <see cref="Nodify.Minimap"/> control.</summary>
public class MinimapGestures
{
public MinimapGestures()
{
Pan = new DirectionalNavigationGestures();
DragViewport = new MouseGesture(MouseAction.LeftClick);
ResetViewport = new KeyGesture(Key.Home);
CancelAction = new AnyGesture(new MouseGesture(MouseAction.RightClick), new KeyGesture(Key.Escape));
ZoomIn = new AnyGesture(new KeyGesture(Key.OemPlus, ModifierKeys.Control), new KeyGesture(Key.Add, ModifierKeys.Control));
ZoomOut = new AnyGesture(new KeyGesture(Key.OemMinus, ModifierKeys.Control), new KeyGesture(Key.Subtract, ModifierKeys.Control));
ZoomModifierKey = ModifierKeys.None;
}
/// <summary>
/// Directional gestures used for panning the viewport.
/// </summary>
/// <remarks>Defaults to <see cref="Key.Space"/>+arrow keys.</remarks>
public DirectionalNavigationGestures Pan { get; }
/// <summary>Gesture to move the viewport inside the <see cref="Minimap" />.</summary>
public InputGestureRef DragViewport { get; }
/// <summary>Gesture to move the viewport inside the <see cref="Minimap" />.</summary>
public InputGestureRef ResetViewport { get; }
/// <summary>Gesture to cancel the panning operation.</summary>
/// <remarks>Defaults to <see cref="MouseAction.RightClick"/> or <see cref="Key.Escape"/>.</remarks>
public InputGestureRef CancelAction { get; }
/// <summary>Gesture used to zoom in.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Control"/>+<see cref="Key.OemPlus"/>.</remarks>
public InputGestureRef ZoomIn { get; }
/// <summary>Gesture used to zoom out.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.Control"/>+<see cref="Key.OemMinus"/>.</remarks>
public InputGestureRef ZoomOut { get; }
/// <summary>The key modifier required to start zooming by mouse wheel.</summary>
/// <remarks>Defaults to <see cref="ModifierKeys.None"/>.</remarks>
public ModifierKeys ZoomModifierKey { get; set; }
/// <summary>Copies from the specified gestures.</summary>
/// <param name="gestures">The gestures to copy.</param>
public void Apply(MinimapGestures gestures)
{
Pan.Apply(gestures.Pan);
ZoomIn.Value = gestures.ZoomIn.Value;
ZoomOut.Value = gestures.ZoomOut.Value;
ResetViewport.Value = gestures.ResetViewport.Value;
DragViewport.Value = gestures.DragViewport.Value;
CancelAction.Value = gestures.CancelAction.Value;
ZoomModifierKey = gestures.ZoomModifierKey;
}
/// <summary>
/// Unbinds all the gestures.
/// </summary>
public void Unbind()
{
Pan.Unbind();
ZoomIn.Unbind();
ZoomOut.Unbind();
ResetViewport.Unbind();
DragViewport.Unbind();
CancelAction.Unbind();
}
}
/// <summary>Gestures for the editor.</summary>
public NodifyEditorGestures Editor { get; } = new NodifyEditorGestures();
/// <summary>Gestures for the item container.</summary>
public ItemContainerGestures ItemContainer { get; } = new ItemContainerGestures();
/// <summary>Gestures for the connector.</summary>
public ConnectorGestures Connector { get; } = new ConnectorGestures();
/// <summary>Gestures for the connection.</summary>
public ConnectionGestures Connection { get; } = new ConnectionGestures();
/// <summary>Gestures for the grouping node.</summary>
public GroupingNodeGestures GroupingNode { get; } = new GroupingNodeGestures();
/// <summary>Gestures for the minimap.</summary>
public MinimapGestures Minimap { get; } = new MinimapGestures();
/// <summary>Copies from the specified gestures.</summary>
/// <param name="gestures">The gestures to copy.</param>
public void Apply(EditorGestures gestures)
{
Editor.Apply(gestures.Editor);
ItemContainer.Apply(gestures.ItemContainer);
Connector.Apply(gestures.Connector);
Connection.Apply(gestures.Connection);
GroupingNode.Apply(gestures.GroupingNode);
Minimap.Apply(gestures.Minimap);
}
/// <summary>
/// Unbinds all the gestures used by the editor and its controls.
/// </summary>
public void Unbind()
{
Editor.Unbind();
ItemContainer.Unbind();
Connector.Unbind();
Connection.Unbind();
Minimap.Unbind();
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Windows.Input;
namespace Nodify.Interactivity
{
/// <summary>
/// An input gesture that allows changing its logic at runtime without changing its reference.
/// Useful for classes that capture the object reference without the posibility of updating it. (e.g. <see cref="EditorCommands"/>)
/// </summary>
public sealed class InputGestureRef : InputGesture
{
/// <summary>The referenced gesture.</summary>
public InputGesture Value { get; set; } = MultiGesture.None;
private InputGestureRef() { }
internal InputGestureRef(InputGesture gesture)
{
Value = gesture;
}
public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
{
return Value.Matches(targetElement, inputEventArgs);
}
public static implicit operator InputGestureRef(MouseGesture gesture)
=> new InputGestureRef { Value = gesture };
public static implicit operator InputGestureRef(System.Windows.Input.MouseGesture gesture)
=> new InputGestureRef { Value = gesture };
public static implicit operator InputGestureRef(KeyGesture gesture)
=> new InputGestureRef { Value = gesture };
public static implicit operator InputGestureRef(MultiGesture gesture)
=> new InputGestureRef { Value = gesture };
/// <summary>
/// Unbinds the current gesture.
/// </summary>
public void Unbind()
=> Value = MultiGesture.None;
}
/// <summary>
/// Extension methods for the <see cref="InputGestureRef"/> class.
/// </summary>
public static class InputGestureRefExtensions
{
/// <summary>
/// Creates a new <see cref="InputGestureRef"/> from the specified gesture.
/// </summary>
public static InputGestureRef AsRef(this InputGesture gesture)
{
return new InputGestureRef(gesture);
}
}
}

View File

@@ -0,0 +1,131 @@
using System.Windows;
using System.Windows.Input;
namespace Nodify.Interactivity
{
/// <summary>
/// Represents a keyboard gesture that requires a trigger key to be held down
/// before pressing a combo key. For example, press and hold Space, then press Left arrow.
/// </summary>
public class KeyComboGesture : KeyGesture
{
private static readonly WeakReferenceCollection<KeyComboGesture> _allCombos = new WeakReferenceCollection<KeyComboGesture>(16);
private bool _isTriggerDown;
private int _comboCounter;
/// <summary>
/// Gets a value indicating whether the combo gesture has been performed at least once.
/// </summary>
private bool HasBeenPerformedAtLeastOnce => _comboCounter > 0;
/// <summary>
/// Gets or sets the key that must be pressed first to activate this combo gesture.
/// </summary>
public Key TriggerKey { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the combo key can be repeatedly triggered
/// without releasing the trigger key.
/// </summary>
public bool AllowRepeatingComboKey { get; set; }
static KeyComboGesture()
{
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.PreviewKeyUpEvent, new KeyEventHandler(HandleKeyUp), true);
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.LostKeyboardFocusEvent, new KeyboardFocusChangedEventHandler(HandleFocusLost), true);
}
/// <summary>
/// Initializes a new instance of the <see cref="KeyComboGesture"/> class with the specified trigger and combo keys.
/// </summary>
/// <param name="triggerKey">The key that must be pressed first.</param>
/// <param name="comboKey">The combo key pressed while the trigger key is held.</param>
public KeyComboGesture(Key triggerKey, Key comboKey) : this(triggerKey, comboKey, ModifierKeys.None, string.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="KeyComboGesture"/> class with the specified trigger and combo keys and modifiers.
/// </summary>
/// <param name="triggerKey">The key that must be pressed first.</param>
/// <param name="comboKey">The combo key pressed while the trigger key is held.</param>
/// <param name="modifiers">Any modifier keys required for the combo key.</param>
public KeyComboGesture(Key triggerKey, Key comboKey, ModifierKeys modifiers) : this(triggerKey, comboKey, modifiers, string.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="KeyComboGesture"/> class with the specified trigger key,
/// combo key, modifiers, and display string.
/// </summary>
/// <param name="triggerKey">The key that must be pressed first.</param>
/// <param name="comboKey">The combo key pressed while the trigger key is held.</param>
/// <param name="modifiers">Any modifier keys required for the combo key.</param>
/// <param name="displayString">The display string representing the gesture.</param>
public KeyComboGesture(Key triggerKey, Key comboKey, ModifierKeys modifiers, string displayString) : base(comboKey, modifiers, displayString)
{
TriggerKey = triggerKey;
_allCombos.Add(this);
}
private static void HandleFocusLost(object sender, KeyboardFocusChangedEventArgs e)
{
foreach (var combo in _allCombos)
{
combo.Reset();
}
}
private static void HandleKeyUp(object sender, KeyEventArgs e)
{
foreach (var combo in _allCombos)
{
if (e.Key == combo.TriggerKey)
{
// We don't want to handle the event if only the trigger key was pressed.
if (combo.HasBeenPerformedAtLeastOnce)
{
e.Handled = true;
}
combo.Reset();
}
}
}
private void Reset()
{
_isTriggerDown = false;
_comboCounter = 0;
}
public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
{
if (inputEventArgs is KeyEventArgs { IsDown: true } keyArgs)
{
if (keyArgs.Key == TriggerKey)
{
_isTriggerDown = true;
}
// The combo key only triggers the combo on key down
bool matches = _isTriggerDown && base.Matches(targetElement, inputEventArgs);
if (!matches)
{
return false;
}
_comboCounter++;
if (!AllowRepeatingComboKey)
{
_isTriggerDown = false;
}
return matches;
}
return false;
}
}
}

View File

@@ -0,0 +1,153 @@
using System.Linq;
using System.Windows.Input;
namespace Nodify.Interactivity
{
/// <summary>
/// Represents a mouse gesture that optionally includes a specific key press as part of the gesture.
/// </summary>
public sealed class MouseGesture : System.Windows.Input.MouseGesture
{
/// <summary>
/// Gets or sets the key that must be pressed to match this gesture.
/// </summary>
public Key Key { get; set; }
/// <summary>
/// Whether to ignore modifier keys when releasing the mouse button.
/// </summary>
public bool IgnoreModifierKeysOnRelease { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="MouseGesture"/> class with the specified mouse action, modifier keys, and a specific key.
/// </summary>
/// <param name="action">The action associated with this gesture.</param>
/// <param name="modifiers">The modifiers associated with this gesture.</param>
/// <param name="key">The key required to match the gesture.</param>
public MouseGesture(MouseAction action, ModifierKeys modifiers, Key key) : base(action, modifiers)
{
Key = key;
}
/// <summary>
/// Initializes a new instance of the <see cref="MouseGesture"/> class with the specified mouse action and key.
/// </summary>
/// <param name="action">The action associated with this gesture.</param>
/// <param name="key">The key required to match the gesture.</param>
public MouseGesture(MouseAction action, Key key) : base(action)
{
Key = key;
}
/// <inheritdoc />
public MouseGesture(MouseAction action, ModifierKeys modifiers)
: base(action, modifiers)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MouseGesture"/> class with the specified mouse action and modifier keys.
/// </summary>
/// <param name="action">The action associated with this gesture.</param>
/// <param name="modifiers">The modifiers required to match the gesture.</param>
/// <param name="ignoreModifierKeysOnRelease">Whether to ignore modifiers when releasing the mouse button.</param>
public MouseGesture(MouseAction action, ModifierKeys modifiers, bool ignoreModifierKeysOnRelease)
: base(action, modifiers)
{
IgnoreModifierKeysOnRelease = ignoreModifierKeysOnRelease;
}
/// <inheritdoc />
public MouseGesture(MouseAction action) : base(action)
{
}
/// <inheritdoc />
public MouseGesture()
{
}
/// <inheritdoc />
public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
{
if (inputEventArgs is MouseButtonEventArgs || inputEventArgs is MouseWheelEventArgs)
{
bool matches = base.Matches(targetElement, inputEventArgs);
if (IgnoreModifierKeysOnRelease && IsButtonReleased(inputEventArgs))
{
ModifierKeys prevModifiers = Modifiers;
Modifiers = ModifierKeys.None;
matches |= base.Matches(targetElement, inputEventArgs);
Modifiers = prevModifiers;
}
return matches && MatchesKeyboard();
}
return false;
}
/// <summary>
/// Checks whether the required key is pressed or no keys are pressed when <see cref="Key"/> is <see cref="Key.None"/>.
/// </summary>
private bool MatchesKeyboard()
{
if (Key is Key.None)
{
return !IsAnyKeyPressed();
}
return Keyboard.IsKeyDown(Key);
}
private static readonly Key[] _allKeys = new[]
{
// Alphanumeric
Key.A, Key.B, Key.C, Key.D, Key.E, Key.F, Key.G, Key.H, Key.I, Key.J,
Key.K, Key.L, Key.M, Key.N, Key.O, Key.P, Key.Q, Key.R, Key.S, Key.T,
Key.U, Key.V, Key.W, Key.X, Key.Y, Key.Z,
Key.D0, Key.D1, Key.D2, Key.D3, Key.D4, Key.D5, Key.D6, Key.D7, Key.D8, Key.D9,
// Punctuation and symbols
Key.Oem3, Key.OemMinus, Key.OemPlus, Key.OemOpenBrackets, Key.OemCloseBrackets,
Key.Oem5, Key.Oem1, Key.OemQuotes, Key.OemComma, Key.OemPeriod, Key.Oem2,
// Function keys
Key.F1, Key.F2, Key.F3, Key.F4, Key.F5, Key.F6, Key.F7, Key.F8,
Key.F9, Key.F10, Key.F11, Key.F12,
// Navigation
Key.Left, Key.Right, Key.Up, Key.Down,
Key.PageUp, Key.PageDown, Key.Home, Key.End,
// Editing
Key.Back, Key.Delete, Key.Insert,
// Special
Key.Space, Key.Return, Key.Escape, Key.Tab,
// Numeric keypad
Key.NumPad0, Key.NumPad1, Key.NumPad2, Key.NumPad3, Key.NumPad4,
Key.NumPad5, Key.NumPad6, Key.NumPad7, Key.NumPad8, Key.NumPad9,
Key.Multiply, Key.Add, Key.Subtract, Key.Divide, Key.Decimal
};
/// <summary>
/// Determines whether any key (excluding modifiers) is currently pressed.
/// </summary>
private static bool IsAnyKeyPressed()
=> _allKeys.Any(Keyboard.IsKeyDown);
private static bool IsButtonReleased(InputEventArgs e)
{
if (e is MouseButtonEventArgs mbe && mbe.ButtonState == MouseButtonState.Released)
return true;
if (e is MouseWheelEventArgs mwe && mwe.MiddleButton == MouseButtonState.Released)
return true;
return false;
}
}
}

View File

@@ -0,0 +1,68 @@
using System.Windows.Input;
namespace Nodify.Interactivity
{
/// <summary>Combines multiple input gestures.</summary>
public class MultiGesture : InputGesture
{
public static readonly MultiGesture None = new MultiGesture(Match.Any);
/// <summary>The strategy used by <see cref="Matches(object, InputEventArgs)"/>.</summary>
public enum Match
{
/// <summary>At least one gesture must match.</summary>
Any,
/// <summary>All gestures must match.</summary>
All
}
private readonly InputGesture[] _gestures;
private readonly Match _match;
/// <summary>Constructs an instance of a <see cref="MultiGesture"/>.</summary>
/// <param name="match">The matching strategy.</param>
/// <param name="gestures">The input gestures.</param>
public MultiGesture(Match match, params InputGesture[] gestures)
{
_gestures = gestures;
_match = match;
}
/// <inheritdoc />
public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
{
if (_match == Match.Any)
{
return MatchesAny(targetElement, inputEventArgs);
}
return MatchesAll(targetElement, inputEventArgs);
}
private bool MatchesAll(object targetElement, InputEventArgs inputEventArgs)
{
for (int i = 0; i < _gestures.Length; i++)
{
if (!_gestures[i].Matches(targetElement, inputEventArgs))
{
return false;
}
}
return true;
}
private bool MatchesAny(object targetElement, InputEventArgs inputEventArgs)
{
for (int i = 0; i < _gestures.Length; i++)
{
if (_gestures[i].Matches(targetElement, inputEventArgs))
{
return true;
}
}
return false;
}
}
}