Skip to content

Commit

Permalink
Improve cutting methods in NodifyEditor (#154)
Browse files Browse the repository at this point in the history
* Improve cutting methods in NodifyEditor
  • Loading branch information
miroiu authored Nov 26, 2024
1 parent 4521531 commit da921f9
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 195 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

> - Breaking Changes:
> - Made the setter of NodifyEditor.IsPanning private
> - Renamed StartCutting to BeginCutting in NodifyEditor
> - Features:
> - Added BeginPanning, UpdatePanning, EndPanning, CancelPanning and AllowPanningCancellation to NodifyEditor
> - Added UpdateCuttingLine to NodifyEditor
> - Bugfixes:
#### **Version 6.6.0**
Expand Down
2 changes: 1 addition & 1 deletion Nodify/Connections/CuttingLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static void SetIsOverElement(UIElement elem, bool value)
=> elem.SetValue(IsOverElementProperty, value);

/// <summary>
/// Gets or sets whether cancelling a cutting operation is allowed.
/// Gets or sets whether cancelling a cutting operation is allowed (see <see cref="EditorGestures.NodifyEditorGestures.CancelAction"/>).
/// </summary>
public static bool AllowCuttingCancellation { get; set; } = true;

Expand Down
45 changes: 4 additions & 41 deletions Nodify/EditorStates/EditorCuttingState.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Input;

namespace Nodify
{
public class EditorCuttingState : EditorState
{
private readonly LineGeometry _lineGeometry = new LineGeometry();
private List<FrameworkElement>? _previousConnections;

public bool Canceled { get; set; } = CuttingLine.AllowCuttingCancellation;

public EditorCuttingState(NodifyEditor editor) : base(editor)
Expand All @@ -20,25 +14,19 @@ public override void Enter(EditorState? from)
{
Canceled = false;

var startLocation = Editor.MouseLocation;
Editor.StartCutting(startLocation);

_lineGeometry.StartPoint = startLocation;
_lineGeometry.EndPoint = startLocation;
Editor.BeginCutting(Editor.MouseLocation);
}

public override void Exit()
{
ResetConnectionStyle();

// TODO: This is not canceled on LostMouseCapture (add OnLostMouseCapture/OnCancel callback?)
if (Canceled)
{
Editor.CancelCutting();
}
else
{
Editor.EndCutting(Editor.MouseLocation);
Editor.EndCutting();
}
}

Expand All @@ -60,32 +48,7 @@ public override void HandleMouseUp(MouseButtonEventArgs e)

public override void HandleMouseMove(MouseEventArgs e)
{
Editor.CuttingLineEnd = Editor.MouseLocation;

if (NodifyEditor.EnableCuttingLinePreview)
{
ResetConnectionStyle();

_lineGeometry.EndPoint = Editor.MouseLocation;
var connections = Editor.ConnectionsHost.GetIntersectingElements(_lineGeometry, NodifyEditor.CuttingConnectionTypes);
foreach (var connection in connections)
{
CuttingLine.SetIsOverElement(connection, true);
}

_previousConnections = connections;
}
}

private void ResetConnectionStyle()
{
if (_previousConnections != null)
{
foreach (var connection in _previousConnections)
{
CuttingLine.SetIsOverElement(connection, false);
}
}
Editor.UpdateCuttingLine(Editor.MouseLocation);
}

public override void HandleKeyUp(KeyEventArgs e)
Expand Down
1 change: 1 addition & 0 deletions Nodify/EditorStates/EditorPanningState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public EditorPanningState(NodifyEditor editor) : base(editor)
/// <inheritdoc />
public override void Exit()
{
// TODO: This is not canceled on LostMouseCapture (add OnLostMouseCapture/OnCancel callback?)
if (Canceled)
{
Editor.CancelPanning();
Expand Down
244 changes: 244 additions & 0 deletions Nodify/NodifyEditor.Cutting.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
using System.Collections.Generic;
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Diagnostics;

namespace Nodify
{
[StyleTypedProperty(Property = nameof(CuttingLineStyle), StyleTargetType = typeof(CuttingLine))]
public partial class NodifyEditor
{
protected static readonly DependencyPropertyKey CuttingLineStartPropertyKey = DependencyProperty.RegisterReadOnly(nameof(CuttingLineStart), typeof(Point), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.Point));
public static readonly DependencyProperty CuttingLineStartProperty = CuttingLineStartPropertyKey.DependencyProperty;

protected static readonly DependencyPropertyKey CuttingLineEndPropertyKey = DependencyProperty.RegisterReadOnly(nameof(CuttingLineEnd), typeof(Point), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.Point));
public static readonly DependencyProperty CuttingLineEndProperty = CuttingLineEndPropertyKey.DependencyProperty;

protected static readonly DependencyPropertyKey IsCuttingPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsCutting), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False, OnIsCuttingChanged));
public static readonly DependencyProperty IsCuttingProperty = IsCuttingPropertyKey.DependencyProperty;

public static readonly DependencyProperty CuttingLineStyleProperty = DependencyProperty.Register(nameof(CuttingLineStyle), typeof(Style), typeof(NodifyEditor));

public static readonly DependencyProperty CuttingStartedCommandProperty = DependencyProperty.Register(nameof(CuttingStartedCommand), typeof(ICommand), typeof(NodifyEditor));
public static readonly DependencyProperty CuttingCompletedCommandProperty = DependencyProperty.Register(nameof(CuttingCompletedCommand), typeof(ICommand), typeof(NodifyEditor));

private static void OnIsCuttingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var editor = (NodifyEditor)d;
if ((bool)e.NewValue == true)
editor.OnCuttingStarted();
else
editor.OnCuttingCompleted();
}

private void OnCuttingCompleted()
{
if (CuttingCompletedCommand?.CanExecute(DataContext) ?? false)
CuttingCompletedCommand.Execute(DataContext);
}

private void OnCuttingStarted()
{
if (CuttingStartedCommand?.CanExecute(DataContext) ?? false)
CuttingStartedCommand.Execute(DataContext);
}

/// <summary>
/// Gets or sets the style to use for the cutting line.
/// </summary>
public Style CuttingLineStyle
{
get => (Style)GetValue(CuttingLineStyleProperty);
set => SetValue(CuttingLineStyleProperty, value);
}

/// <summary>
/// Gets the start point of the <see cref="CuttingLine"/> while <see cref="IsCutting"/> is true.
/// </summary>
public Point CuttingLineStart
{
get => (Point)GetValue(CuttingLineStartProperty);
private set => SetValue(CuttingLineStartPropertyKey, value);
}

/// <summary>
/// Gets the end point of the <see cref="CuttingLine"/> while <see cref="IsCutting"/> is true.
/// </summary>
public Point CuttingLineEnd
{
get => (Point)GetValue(CuttingLineEndProperty);
private set => SetValue(CuttingLineEndPropertyKey, value);
}

/// <summary>
/// Gets a value that indicates whether a cutting operation is in progress.
/// </summary>
public bool IsCutting
{
get => (bool)GetValue(IsCuttingProperty);
private set => SetValue(IsCuttingPropertyKey, value);
}

/// <summary>Invoked when a cutting operation is started.</summary>
public ICommand? CuttingStartedCommand
{
get => (ICommand?)GetValue(CuttingStartedCommandProperty);
set => SetValue(CuttingStartedCommandProperty, value);
}

/// <summary>Invoked when a cutting operation is completed.</summary>
public ICommand? CuttingCompletedCommand
{
get => (ICommand?)GetValue(CuttingCompletedCommandProperty);
set => SetValue(CuttingCompletedCommandProperty, value);
}

/// <summary>
/// Gets or sets whether the cutting line should apply the preview style to the interesected elements.
/// </summary>
/// <remarks>
/// This may hurt performance because intersection must be calculated on mouse move.
/// </remarks>
public static bool EnableCuttingLinePreview { get; set; } = false;

/// <summary>
/// The list of supported connection types for cutting. Type must be derived from <see cref="FrameworkElement" />.
/// </summary>
public static readonly HashSet<Type> CuttingConnectionTypes = new HashSet<Type>();

private List<FrameworkElement>? _cuttingLinePreviousConnections;
private readonly LineGeometry _cuttingLineGeometry = new LineGeometry();

/// <summary>
/// Starts the cutting operation at the specified location. Call <see cref="EndCutting"/> to complete the operation or <see cref="CancelCutting"/> to abort it.
/// </summary>
/// <remarks>This method has no effect if a cutting operation is already in progress.</remarks>
/// <param name="location">The starting location for cutting items, in graph space coordinates.</param>
public void BeginCutting(Point location)
{
if (IsCutting)
{
return;
}

CuttingLineStart = location;
CuttingLineEnd = location;
IsCutting = true;

_cuttingLineGeometry.StartPoint = location;
_cuttingLineGeometry.EndPoint = location;
}

/// <summary>
/// Updates the current cutting line position and the style for the intersecting elements if <see cref="EnableCuttingLinePreview"/> is true.
/// </summary>
/// <param name="amount">The amount to adjust the cutting line's endpoint.</param>
public void UpdateCuttingLine(Vector amount)
{
CuttingLineEnd += amount;

UpdateCuttingLine(CuttingLineEnd);
}

/// <summary>
/// Updates the current cutting line position and the style for the intersecting elements if <see cref="EnableCuttingLinePreview"/> is true.
/// </summary>
/// <param name="location">The location of the cutting line's endpoint.</param>
public void UpdateCuttingLine(Point location)
{
Debug.Assert(IsCutting);
CuttingLineEnd = location;

if (EnableCuttingLinePreview)
{
_cuttingLineGeometry.EndPoint = CuttingLineEnd;

ResetConnectionStyle();
ApplyConnectionStyle();
}
}

/// <summary>
/// Cancels the current cutting operation without applying any changes.
/// </summary>
/// <remarks>This method has no effect if there's no cutting operation in progress.</remarks>
public void CancelCutting()
{
if (!CuttingLine.AllowCuttingCancellation || !IsCutting)
{
return;
}

ResetConnectionStyle();
IsCutting = false;
}

/// <summary>
/// Completes the cutting operation and applies the changes.
/// </summary>
/// <remarks>This method has no effect if there's no cutting operation in progress.</remarks>
public void EndCutting()
{
if (!IsCutting)
{
return;
}

ResetConnectionStyle();

var lineGeometry = new LineGeometry(CuttingLineStart, CuttingLineEnd);
var connections = ConnectionsHost.GetIntersectingElements(lineGeometry, CuttingConnectionTypes);

if (RemoveConnectionCommand != null)
{
foreach (var connection in connections)
{
OnRemoveConnection(connection.DataContext);
}
}
else
{
RemoveSupportedConnections(connections);
}

IsCutting = false;
}

private static void RemoveSupportedConnections(List<FrameworkElement> connections)
{
foreach (var connection in connections)
{
if (connection is BaseConnection bc)
{
bc.OnDisconnect();
}
}
}

private void ApplyConnectionStyle()
{
var connections = ConnectionsHost.GetIntersectingElements(_cuttingLineGeometry, CuttingConnectionTypes);
foreach (var connection in connections)
{
CuttingLine.SetIsOverElement(connection, true);
}

_cuttingLinePreviousConnections = connections;
}

private void ResetConnectionStyle()
{
if (_cuttingLinePreviousConnections != null)
{
foreach (var connection in _cuttingLinePreviousConnections)
{
CuttingLine.SetIsOverElement(connection, false);
}

_cuttingLinePreviousConnections = null;
}
}
}
}
Loading

0 comments on commit da921f9

Please sign in to comment.