Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
> - Added BeginPanning, UpdatePanning, EndPanning, CancelPanning and AllowPanningCancellation to NodifyEditor
> - Added UpdateCuttingLine to NodifyEditor
> - Added BeginSelecting, UpdateSelection, EndSelecting, CancelSelecting and AllowSelectionCancellation to NodifyEditor
> - Added IsDragging, BeginDragging, UpdateDragging, EndDragging and CancelDragging to NodifyEditor
> - Bugfixes:

#### **Version 6.6.0**
Expand Down
22 changes: 8 additions & 14 deletions Nodify/Helpers/PushItemsStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;

Expand All @@ -12,12 +11,11 @@ internal interface IPushStrategy
Rect Push(Vector amount);
Rect End();
Rect Cancel();
Rect OnViewportChanged();
Rect GetPushedArea();
}

internal abstract class BasePushStrategy : IPushStrategy
{
private IDraggingStrategy? _draggingStrategy;
private const double _minOffset = 2;
private double _actualOffset;
private double _initialPosition;
Expand All @@ -33,7 +31,7 @@ public BasePushStrategy(NodifyEditor editor)
public Rect Start(Point position)
{
var containers = GetFilteredContainers(position);
_draggingStrategy = Editor.CreateDraggingStrategy(containers);
Editor.BeginDragging(containers);

_initialPosition = GetInitialPosition(position);
_actualOffset = 0;
Expand All @@ -43,10 +41,8 @@ public Rect Start(Point position)

public Rect Push(Vector amount)
{
Debug.Assert(_draggingStrategy != null);

var offset = GetPushOffset(amount);
_draggingStrategy!.Update(offset);
Editor.UpdateDragging(offset);

_actualOffset += offset.X;
_actualOffset += offset.Y;
Expand All @@ -59,23 +55,21 @@ public Rect Push(Vector amount)

public Rect End()
{
Debug.Assert(_draggingStrategy != null);
_draggingStrategy!.End();
Editor.EndDragging();
return new Rect();
}

public Rect Cancel()
{
Debug.Assert(_draggingStrategy != null);
_draggingStrategy!.Abort();
Editor.CancelDragging();
return new Rect();
}

protected abstract IEnumerable<ItemContainer> GetFilteredContainers(Point position);
protected abstract double GetInitialPosition(Point position);
protected abstract Vector GetPushOffset(Vector offset);
protected abstract Rect CalculatePushedArea(double position, double offset);
public abstract Rect OnViewportChanged();
public abstract Rect GetPushedArea();
}

internal sealed class HorizontalPushStrategy : BasePushStrategy
Expand All @@ -96,7 +90,7 @@ protected override Vector GetPushOffset(Vector offset)
protected override Rect CalculatePushedArea(double position, double offset)
=> new Rect(position, Editor.ViewportLocation.Y - OffscreenOffset, offset, Editor.ViewportSize.Height + OffscreenOffset * 2);

public override Rect OnViewportChanged()
public override Rect GetPushedArea()
=> CalculatePushedArea(Editor.PushedArea.X, Editor.PushedArea.Width);
}

Expand All @@ -118,7 +112,7 @@ protected override Vector GetPushOffset(Vector offset)
protected override Rect CalculatePushedArea(double position, double offset)
=> new Rect(Editor.ViewportLocation.X - OffscreenOffset, position, Editor.ViewportSize.Width + OffscreenOffset * 2, offset);

public override Rect OnViewportChanged()
public override Rect GetPushedArea()
=> CalculatePushedArea(Editor.PushedArea.Y, Editor.PushedArea.Height);
}
}
4 changes: 2 additions & 2 deletions Nodify/ItemContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public class ItemContainer : ContentControl, INodifyCanvasItem
public static readonly DependencyProperty SelectedBorderThicknessProperty = DependencyProperty.Register(nameof(SelectedBorderThickness), typeof(Thickness), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.Thickness2));
public static readonly DependencyProperty IsSelectableProperty = DependencyProperty.Register(nameof(IsSelectable), typeof(bool), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.True));
public static readonly DependencyProperty IsSelectedProperty = Selector.IsSelectedProperty.AddOwner(typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.False, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsSelectedChanged));
public static readonly DependencyPropertyKey IsPreviewingSelectionPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsPreviewingSelection), typeof(bool?), typeof(ItemContainer), new FrameworkPropertyMetadata(null));
protected static readonly DependencyPropertyKey IsPreviewingSelectionPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsPreviewingSelection), typeof(bool?), typeof(ItemContainer), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty IsPreviewingSelectionProperty = IsPreviewingSelectionPropertyKey.DependencyProperty;
public static readonly DependencyProperty LocationProperty = DependencyProperty.Register(nameof(Location), typeof(Point), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.Point, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnLocationChanged));
public static readonly DependencyProperty ActualSizeProperty = DependencyProperty.Register(nameof(ActualSize), typeof(Size), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.Size));
public static readonly DependencyProperty DesiredSizeForSelectionProperty = DependencyProperty.Register(nameof(DesiredSizeForSelection), typeof(Size?), typeof(ItemContainer), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.NotDataBindable));
public static readonly DependencyPropertyKey IsPreviewingLocationPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsPreviewingLocation), typeof(bool), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.False));
private static readonly DependencyPropertyKey IsPreviewingLocationPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsPreviewingLocation), typeof(bool), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.False));
public static readonly DependencyProperty IsPreviewingLocationProperty = IsPreviewingLocationPropertyKey.DependencyProperty;
public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.Register(nameof(IsDraggable), typeof(bool), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.True));

Expand Down
2 changes: 1 addition & 1 deletion Nodify/Nodes/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class Node : HeaderedContentControl
public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(nameof(Footer), typeof(object), typeof(Node), new FrameworkPropertyMetadata(OnFooterChanged));
public static readonly DependencyProperty FooterTemplateProperty = DependencyProperty.Register(nameof(FooterTemplate), typeof(DataTemplate), typeof(Node));
public static readonly DependencyProperty InputConnectorTemplateProperty = DependencyProperty.Register(nameof(InputConnectorTemplate), typeof(DataTemplate), typeof(Node));
protected internal static readonly DependencyPropertyKey HasFooterPropertyKey = DependencyProperty.RegisterReadOnly(nameof(HasFooter), typeof(bool), typeof(Node), new FrameworkPropertyMetadata(BoxValue.False));
protected static readonly DependencyPropertyKey HasFooterPropertyKey = DependencyProperty.RegisterReadOnly(nameof(HasFooter), typeof(bool), typeof(Node), new FrameworkPropertyMetadata(BoxValue.False));
public static readonly DependencyProperty HasFooterProperty = HasFooterPropertyKey.DependencyProperty;
public static readonly DependencyProperty OutputConnectorTemplateProperty = DependencyProperty.Register(nameof(OutputConnectorTemplate), typeof(DataTemplate), typeof(Node));
public static readonly DependencyProperty InputProperty = DependencyProperty.Register(nameof(Input), typeof(IEnumerable), typeof(Node));
Expand Down
190 changes: 190 additions & 0 deletions Nodify/NodifyEditor.Dragging.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using System.Windows.Input;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Controls.Primitives;
using System.Diagnostics;

namespace Nodify
{
public partial class NodifyEditor
{
public static readonly DependencyProperty ItemsDragStartedCommandProperty = DependencyProperty.Register(nameof(ItemsDragStartedCommand), typeof(ICommand), typeof(NodifyEditor));
public static readonly DependencyProperty ItemsDragCompletedCommandProperty = DependencyProperty.Register(nameof(ItemsDragCompletedCommand), typeof(ICommand), typeof(NodifyEditor));

protected static readonly DependencyPropertyKey IsDraggingPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsDragging), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False, OnIsDraggingChanged));
public static readonly DependencyProperty IsDraggingProperty = IsDraggingPropertyKey.DependencyProperty;

private static void OnIsDraggingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var editor = (NodifyEditor)d;

if ((bool)e.NewValue == true)
{
editor.OnItemsDragStarted();
}
else
{
editor.OnItemsDragCompleted();
}
}

private void OnItemsDragCompleted()
{
if (ItemsDragCompletedCommand?.CanExecute(DataContext) ?? false)
ItemsDragCompletedCommand.Execute(DataContext);
}

private void OnItemsDragStarted()
{
if (ItemsDragStartedCommand?.CanExecute(DataContext) ?? false)
ItemsDragStartedCommand.Execute(DataContext);
}

/// <summary>
/// Invoked when a drag operation starts for the <see cref="SelectedItems"/>, or when <see cref="IsPushingItems"/> is set to true.
/// </summary>
public ICommand? ItemsDragStartedCommand
{
get => (ICommand?)GetValue(ItemsDragStartedCommandProperty);
set => SetValue(ItemsDragStartedCommandProperty, value);
}

/// <summary>
/// Invoked when a drag operation is completed for the <see cref="SelectedItems"/>, or when <see cref="IsPushingItems"/> is set to false.
/// </summary>
public ICommand? ItemsDragCompletedCommand
{
get => (ICommand?)GetValue(ItemsDragCompletedCommandProperty);
set => SetValue(ItemsDragCompletedCommandProperty, value);
}

/// <summary>
/// Gets a value that indicates whether a dragging operation is in progress.
/// </summary>
public bool IsDragging
{
get => (bool)GetValue(IsDraggingProperty);
private set => SetValue(IsDraggingPropertyKey, value);
}

/// <summary>
/// Gets or sets if the current position of containers that are being dragged should not be committed until the end of the dragging operation.
/// </summary>
public static bool EnableDraggingContainersOptimizations { get; set; } = true;

private IDraggingStrategy? _draggingStrategy;

private void RegisterDragEvents()
{
AddHandler(ItemContainer.DragStartedEvent, new DragStartedEventHandler(OnItemsDragStarted));
AddHandler(ItemContainer.DragCompletedEvent, new DragCompletedEventHandler(OnItemsDragCompleted));
AddHandler(ItemContainer.DragDeltaEvent, new DragDeltaEventHandler(OnItemsDragDelta));
}

/// <summary>
/// Initiates the dragging operation using the currently selected <see cref="ItemContainer" />s.
/// </summary>
/// <remarks>This method has no effect if a dragging operation is already in progress.</remarks>
public void BeginDragging()
=> BeginDragging(SelectedContainers);

/// <summary>
/// Initiates the dragging operation for the specified <see cref="ItemContainer" />s.
/// </summary>
/// <param name="containers">The collection of item containers to be dragged.</param>
/// <remarks>This method has no effect if a dragging operation is already in progress.</remarks>
public void BeginDragging(IEnumerable<ItemContainer> containers)
{
if(IsDragging)
{
return;
}

IsDragging = true;
_draggingStrategy = CreateDraggingStrategy(containers);
}

/// <summary>
/// Updates the position of the items being dragged by a specified offset.
/// </summary>
/// <param name="amount">The vector by which to adjust the position of the dragged items.</param>
/// <remarks>
/// This method adjusts the items positions incrementally. It should only be called while a dragging operation is in progress (see <see cref="BeginDragging" />).
/// </remarks>
public void UpdateDragging(Vector amount)
{
Debug.Assert(IsDragging);
_draggingStrategy!.Update(amount);
}

/// <summary>
/// Completes the dragging operation, finalizing the position of the dragged items.
/// </summary>
/// <remarks>This method has no effect if there's no dragging operation in progress.</remarks>
public void EndDragging()
{
if (!IsDragging)
{
return;
}

IsBulkUpdatingItems = true;
_draggingStrategy!.End();
IsBulkUpdatingItems = false;

// Draw the containers at the new position.
ItemsHost.InvalidateArrange();

_draggingStrategy = null;
IsDragging = false;
}

/// <summary>
/// Cancels the ongoing dragging operation, reverting any changes made to the positions of the dragged items.
/// </summary>
/// <remarks>This method has no effect if there's no dragging operation in progress.</remarks>
public void CancelDragging()
{
if (!ItemContainer.AllowDraggingCancellation || !IsDragging)
{
return;
}

_draggingStrategy!.Abort();
IsDragging = false;
}

private IDraggingStrategy CreateDraggingStrategy(IEnumerable<ItemContainer> containers)
{
if (EnableDraggingContainersOptimizations)
{
return new DraggingOptimized(containers, GridCellSize);
}

return new DraggingSimple(containers, GridCellSize);
}

private void OnItemsDragStarted(object sender, DragStartedEventArgs e)
{
BeginDragging();
e.Handled = true;
}

private void OnItemsDragDelta(object sender, DragDeltaEventArgs e)
{
UpdateDragging(new Vector(e.HorizontalChange, e.VerticalChange));
}

private void OnItemsDragCompleted(object sender, DragCompletedEventArgs e)
{
if (e.Canceled)
{
CancelDragging();
}
else
{
EndDragging();
}
}
}
}
Loading
Loading