Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve cutting methods in NodifyEditor #154

Merged
merged 2 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading