Add project files.
This commit is contained in:
40
Nodify/Minimap/Events/ZoomEventArgs.cs
Normal file
40
Nodify/Minimap/Events/ZoomEventArgs.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the method that will handle <see cref="Minimap.Zoom"/> routed event.
|
||||
/// </summary>
|
||||
/// <param name="sender">The object where the event handler is attached.</param>
|
||||
/// <param name="e">The event data.</param>
|
||||
public delegate void ZoomEventHandler(object sender, ZoomEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for <see cref="Minimap.Zoom"/> routed event.
|
||||
/// </summary>
|
||||
public class ZoomEventArgs : RoutedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ZoomEventArgs"/> class using the specified <see cref="Zoom"/> and <see cref="Location"/>.
|
||||
/// </summary>
|
||||
public ZoomEventArgs(double zoom, Point location)
|
||||
{
|
||||
Zoom = zoom;
|
||||
Location = location;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the zoom amount.
|
||||
/// </summary>
|
||||
public double Zoom { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the location where the editor should zoom in.
|
||||
/// </summary>
|
||||
public Point Location { get; }
|
||||
|
||||
protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
|
||||
=> ((ZoomEventHandler)genericHandler)(genericTarget, this);
|
||||
}
|
||||
}
|
||||
390
Nodify/Minimap/Minimap.cs
Normal file
390
Nodify/Minimap/Minimap.cs
Normal file
@@ -0,0 +1,390 @@
|
||||
using Nodify.Events;
|
||||
using Nodify.Interactivity;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Nodify
|
||||
{
|
||||
/// <summary>
|
||||
/// A minimap control that can position the viewport, and zoom in and out.
|
||||
/// </summary>
|
||||
[StyleTypedProperty(Property = nameof(ViewportStyle), StyleTargetType = typeof(Rectangle))]
|
||||
[StyleTypedProperty(Property = nameof(ItemContainerStyle), StyleTargetType = typeof(MinimapItem))]
|
||||
[TemplatePart(Name = ElementItemsHost, Type = typeof(Panel))]
|
||||
public class Minimap : ItemsControl
|
||||
{
|
||||
private const string ElementItemsHost = "PART_ItemsHost";
|
||||
|
||||
public static readonly DependencyProperty ViewportLocationProperty = NodifyEditor.ViewportLocationProperty.AddOwner(typeof(Minimap), new FrameworkPropertyMetadata(BoxValue.Point, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||
public static readonly DependencyProperty ViewportSizeProperty = NodifyEditor.ViewportSizeProperty.AddOwner(typeof(Minimap));
|
||||
public static readonly DependencyProperty ViewportStyleProperty = DependencyProperty.Register(nameof(ViewportStyle), typeof(Style), typeof(Minimap));
|
||||
public static readonly DependencyProperty ExtentProperty = NodifyCanvas.ExtentProperty.AddOwner(typeof(Minimap));
|
||||
public static readonly DependencyProperty ItemsExtentProperty = DependencyProperty.Register(nameof(ItemsExtent), typeof(Rect), typeof(Minimap));
|
||||
public static readonly DependencyProperty MaxViewportOffsetProperty = DependencyProperty.Register(nameof(MaxViewportOffset), typeof(Size), typeof(Minimap), new FrameworkPropertyMetadata(new Size(2000, 2000)));
|
||||
public static readonly DependencyProperty ResizeToViewportProperty = DependencyProperty.Register(nameof(ResizeToViewport), typeof(bool), typeof(Minimap));
|
||||
public static readonly DependencyProperty IsReadOnlyProperty = TextBoxBase.IsReadOnlyProperty.AddOwner(typeof(Minimap));
|
||||
|
||||
public static readonly RoutedEvent ZoomEvent = EventManager.RegisterRoutedEvent(nameof(Zoom), RoutingStrategy.Bubble, typeof(ZoomEventHandler), typeof(Minimap));
|
||||
|
||||
/// <inheritdoc cref="NodifyEditor.ViewportLocation" />
|
||||
public Point ViewportLocation
|
||||
{
|
||||
get => (Point)GetValue(ViewportLocationProperty);
|
||||
set => SetValue(ViewportLocationProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="NodifyEditor.ViewportSize" />
|
||||
public Size ViewportSize
|
||||
{
|
||||
get => (Size)GetValue(ViewportSizeProperty);
|
||||
set => SetValue(ViewportSizeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style to use for the viewport rectangle.
|
||||
/// </summary>
|
||||
public Style ViewportStyle
|
||||
{
|
||||
get => (Style)GetValue(ViewportStyleProperty);
|
||||
set => SetValue(ViewportStyleProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>The area covered by the items and the viewport rectangle in graph space.</summary>
|
||||
public Rect Extent
|
||||
{
|
||||
get => (Rect)GetValue(ExtentProperty);
|
||||
set => SetValue(ExtentProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>The area covered by the <see cref="MinimapItem"/>s in graph space.</summary>
|
||||
public Rect ItemsExtent
|
||||
{
|
||||
get => (Rect)GetValue(ItemsExtentProperty);
|
||||
set => SetValue(ItemsExtentProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>The max position from the <see cref="NodifyEditor.ItemsExtent"/> that the viewport can move to.</summary>
|
||||
public Size MaxViewportOffset
|
||||
{
|
||||
get => (Size)GetValue(MaxViewportOffsetProperty);
|
||||
set => SetValue(MaxViewportOffsetProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>Whether the minimap should resize to also display the whole viewport.</summary>
|
||||
public bool ResizeToViewport
|
||||
{
|
||||
get => (bool)GetValue(ResizeToViewportProperty);
|
||||
set => SetValue(ResizeToViewportProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>Whether the minimap can move and zoom the viewport.</summary>
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get => (bool)GetValue(IsReadOnlyProperty);
|
||||
set => SetValue(IsReadOnlyProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>Triggered when zooming in or out using the mouse wheel.</summary>
|
||||
public event ZoomEventHandler Zoom
|
||||
{
|
||||
add => AddHandler(ZoomEvent, value);
|
||||
remove => RemoveHandler(ZoomEvent, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the panel that holds all the <see cref="MinimapItem"/>s.
|
||||
/// </summary>
|
||||
protected Panel ItemsHost { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user is currently panning the minimap.
|
||||
/// </summary>
|
||||
protected bool IsPanning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current mouse location in graph space coordinates (relative to the <see cref="ItemsHost" />).
|
||||
/// </summary>
|
||||
public Point MouseLocation { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether panning cancellation is allowed (see <see cref="EditorGestures.MinimapGestures.CancelAction"/>).
|
||||
/// </summary>
|
||||
public static bool AllowPanningCancellation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the distance to pan when using directional input (such as arrow keys).
|
||||
/// </summary>
|
||||
public static double NavigationStepSize { get; set; } = 50d;
|
||||
|
||||
private Point _initialViewportLocation;
|
||||
|
||||
static Minimap()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(Minimap), new FrameworkPropertyMetadata(typeof(Minimap)));
|
||||
FocusableProperty.OverrideMetadata(typeof(Minimap), new FrameworkPropertyMetadata(BoxValue.True));
|
||||
|
||||
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(Minimap), new FrameworkPropertyMetadata(KeyboardNavigationMode.None));
|
||||
KeyboardNavigation.ControlTabNavigationProperty.OverrideMetadata(typeof(Minimap), new FrameworkPropertyMetadata(KeyboardNavigationMode.None));
|
||||
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(Minimap), new FrameworkPropertyMetadata(KeyboardNavigationMode.None));
|
||||
}
|
||||
|
||||
public Minimap()
|
||||
{
|
||||
InputProcessor.AddSharedHandlers(this);
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
ItemsHost = GetTemplateChild(ElementItemsHost) as Panel ?? throw new InvalidOperationException($"{ElementItemsHost} is missing or is not of type {nameof(Panel)}.");
|
||||
}
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride()
|
||||
=> new MinimapItem();
|
||||
|
||||
protected override bool IsItemItsOwnContainerOverride(object item)
|
||||
=> item is MinimapItem;
|
||||
|
||||
#region Gesture Handling
|
||||
|
||||
protected InputProcessor InputProcessor { get; } = new InputProcessor();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||
{
|
||||
MouseLocation = e.GetPosition(ItemsHost);
|
||||
InputProcessor.ProcessEvent(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnMouseUp(MouseButtonEventArgs e)
|
||||
{
|
||||
MouseLocation = e.GetPosition(ItemsHost);
|
||||
InputProcessor.ProcessEvent(e);
|
||||
|
||||
// Release the mouse capture if all the mouse buttons are released and there's no interaction in progress
|
||||
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released)
|
||||
{
|
||||
ReleaseMouseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
MouseLocation = e.GetPosition(ItemsHost);
|
||||
InputProcessor.ProcessEvent(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
MouseLocation = e.GetPosition(ItemsHost);
|
||||
InputProcessor.ProcessEvent(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnLostMouseCapture(MouseEventArgs e)
|
||||
=> InputProcessor.ProcessEvent(e);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnKeyUp(KeyEventArgs e)
|
||||
{
|
||||
InputProcessor.ProcessEvent(e);
|
||||
|
||||
// Release the mouse capture if all the mouse buttons are released and there's no interaction in progress
|
||||
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && Mouse.RightButton == MouseButtonState.Released && Mouse.LeftButton == MouseButtonState.Released && Mouse.MiddleButton == MouseButtonState.Released)
|
||||
{
|
||||
ReleaseMouseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
=> InputProcessor.ProcessEvent(e);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Panning
|
||||
|
||||
/// <summary>
|
||||
/// Starts the panning operation from the current <see cref="MouseLocation" />.
|
||||
/// </summary>
|
||||
/// <remarks>This method has no effect if a panning operation is already in progress.</remarks>
|
||||
public void BeginPanning()
|
||||
=> BeginPanning(MouseLocation);
|
||||
|
||||
/// <summary>
|
||||
/// Starts the panning operation from the specified location. Call <see cref="EndPanning"/> to end the panning operation.
|
||||
/// </summary>
|
||||
/// <remarks>This method has no effect if a panning operation is already in progress.</remarks>
|
||||
/// <param name="location">The initial location where panning starts, in graph space coordinates.</param>
|
||||
public void BeginPanning(Point location)
|
||||
{
|
||||
if (IsPanning || IsReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsPanning = true;
|
||||
_initialViewportLocation = location;
|
||||
SetViewportLocation(location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the viewport location to the specified location.
|
||||
/// </summary>
|
||||
/// <param name="location">The location to pan the viewport to.</param>
|
||||
public void UpdatePanning(Point location)
|
||||
{
|
||||
Debug.Assert(IsPanning);
|
||||
SetViewportLocation(location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pans the viewport by the specified amount.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount to pan the viewport.</param>
|
||||
/// <remarks>
|
||||
/// This method adjusts the current <see cref="ViewportLocation"/> incrementally based on the provided amount.
|
||||
/// </remarks>
|
||||
public void UpdatePanning(Vector amount)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ViewportLocation -= amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current panning operation, retaining the current <see cref="ViewportLocation"/>.
|
||||
/// </summary>
|
||||
/// <remarks>This method has no effect if there's no panning operation in progress.</remarks>
|
||||
public void EndPanning()
|
||||
{
|
||||
if (!IsPanning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_initialViewportLocation = MouseLocation;
|
||||
IsPanning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the current panning operation and reverts the viewport to its initial location if <see cref="AllowPanningCancellation"/> is true.
|
||||
/// Otherwise, it ends the panning operation by calling <see cref="EndPanning"/>.
|
||||
/// </summary>
|
||||
/// <remarks>This method has no effect if there's no panning operation in progress.</remarks>
|
||||
public void CancelPanning()
|
||||
{
|
||||
if (!AllowPanningCancellation)
|
||||
{
|
||||
EndPanning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsPanning)
|
||||
{
|
||||
SetViewportLocation(_initialViewportLocation);
|
||||
IsPanning = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetViewportLocation(Point location)
|
||||
{
|
||||
var position = location - new Vector(ViewportSize.Width / 2, ViewportSize.Height / 2) + (Vector)Extent.Location;
|
||||
|
||||
if (MaxViewportOffset.Width != 0 || MaxViewportOffset.Height != 0)
|
||||
{
|
||||
double maxRight = ResizeToViewport ? ItemsExtent.Right : Math.Max(ItemsExtent.Right, ItemsExtent.Left + ViewportSize.Width);
|
||||
double maxBottom = ResizeToViewport ? ItemsExtent.Bottom : Math.Max(ItemsExtent.Bottom, ItemsExtent.Top + ViewportSize.Height);
|
||||
|
||||
position.X = position.X.Clamp(ItemsExtent.Left - ViewportSize.Width / 2 - MaxViewportOffset.Width, maxRight - ViewportSize.Width / 2 + MaxViewportOffset.Width);
|
||||
position.Y = position.Y.Clamp(ItemsExtent.Top - ViewportSize.Height / 2 - MaxViewportOffset.Height, maxBottom - ViewportSize.Height / 2 + MaxViewportOffset.Height);
|
||||
}
|
||||
|
||||
ViewportLocation = position;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Zooming
|
||||
|
||||
/// <summary>
|
||||
/// Zoom at the specified location in graph space coordinates.
|
||||
/// </summary>
|
||||
/// <param name="zoom">The zoom factor.</param>
|
||||
/// <param name="location">The location to focus when zooming.</param>
|
||||
public void ZoomAtPosition(double zoom, Point location)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ResizeToViewport)
|
||||
{
|
||||
SetViewportLocation(location);
|
||||
}
|
||||
|
||||
var viewportLocation = ViewportLocation + (Vector)ViewportSize / 2;
|
||||
var args = new ZoomEventArgs(zoom, viewportLocation)
|
||||
{
|
||||
RoutedEvent = ZoomEvent,
|
||||
Source = this
|
||||
};
|
||||
RaiseEvent(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zoom in at the viewport's center.
|
||||
/// </summary>
|
||||
public void ZoomIn() => SetZoom(Math.Pow(2.0, 120.0 / 3.0 / Mouse.MouseWheelDeltaForOneLine));
|
||||
|
||||
/// <summary>
|
||||
/// Zoom out at the viewport's center.
|
||||
/// </summary>
|
||||
public void ZoomOut() => SetZoom(Math.Pow(2.0, -120.0 / 3.0 / Mouse.MouseWheelDeltaForOneLine));
|
||||
|
||||
public void ResetViewport()
|
||||
{
|
||||
SetCurrentValue(ViewportLocationProperty, new Point(0, 0));
|
||||
var args = new ZoomEventArgs(1d, new Point(ViewportSize.Width / 2, ViewportSize.Height / 2))
|
||||
{
|
||||
RoutedEvent = ZoomEvent,
|
||||
Source = this
|
||||
};
|
||||
RaiseEvent(args);
|
||||
}
|
||||
|
||||
private void SetZoom(double zoom)
|
||||
{
|
||||
var viewportLocation = ViewportLocation + (Vector)ViewportSize / 2;
|
||||
var args = new ZoomEventArgs(zoom, viewportLocation)
|
||||
{
|
||||
RoutedEvent = ZoomEvent,
|
||||
Source = this
|
||||
};
|
||||
RaiseEvent(args);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Translates the event location to graph space coordinates (relative to the <see cref="ItemsHost" />).
|
||||
/// </summary>
|
||||
/// <param name="args">The mouse event.</param>
|
||||
/// <returns>A location inside the minimap</returns>
|
||||
public Point GetLocationInsideMinimap(MouseEventArgs args)
|
||||
=> args.GetPosition(ItemsHost);
|
||||
}
|
||||
}
|
||||
24
Nodify/Minimap/MinimapItem.cs
Normal file
24
Nodify/Minimap/MinimapItem.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify
|
||||
{
|
||||
public class MinimapItem : ContentControl
|
||||
{
|
||||
static MinimapItem()
|
||||
{
|
||||
FocusableProperty.OverrideMetadata(typeof(MinimapItem), new FrameworkPropertyMetadata(BoxValue.False));
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LocationProperty = ItemContainer.LocationProperty.AddOwner(typeof(MinimapItem), new FrameworkPropertyMetadata(BoxValue.Point, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.AffectsParentMeasure));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the location of this <see cref="MinimapItem"/> inside the <see cref="Minimap"/>.
|
||||
/// </summary>
|
||||
public Point Location
|
||||
{
|
||||
get => (Point)GetValue(LocationProperty);
|
||||
set => SetValue(LocationProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
119
Nodify/Minimap/MinimapPanel.cs
Normal file
119
Nodify/Minimap/MinimapPanel.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify
|
||||
{
|
||||
internal sealed class MinimapPanel : Panel
|
||||
{
|
||||
public static readonly DependencyProperty ViewportLocationProperty = NodifyEditor.ViewportLocationProperty.AddOwner(typeof(MinimapPanel), new FrameworkPropertyMetadata(BoxValue.Point, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||||
public static readonly DependencyProperty ViewportSizeProperty = NodifyEditor.ViewportSizeProperty.AddOwner(typeof(MinimapPanel), new FrameworkPropertyMetadata(BoxValue.Size, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||||
public static readonly DependencyProperty ExtentProperty = NodifyCanvas.ExtentProperty.AddOwner(typeof(MinimapPanel));
|
||||
public static readonly DependencyProperty ItemsExtentProperty = Minimap.ItemsExtentProperty.AddOwner(typeof(MinimapPanel));
|
||||
public static readonly DependencyProperty ResizeToViewportProperty = Minimap.ResizeToViewportProperty.AddOwner(typeof(MinimapPanel));
|
||||
|
||||
/// <inheritdoc cref="Minimap.ViewportLocation" />
|
||||
public Point ViewportLocation
|
||||
{
|
||||
get => (Point)GetValue(ViewportLocationProperty);
|
||||
set => SetValue(ViewportLocationProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Minimap.ViewportSize" />
|
||||
public Size ViewportSize
|
||||
{
|
||||
get => (Size)GetValue(ViewportSizeProperty);
|
||||
set => SetValue(ViewportSizeProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Minimap.Extent" />
|
||||
public Rect Extent
|
||||
{
|
||||
get => (Rect)GetValue(ExtentProperty);
|
||||
set => SetValue(ExtentProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Minimap.Extent" />
|
||||
public Rect ItemsExtent
|
||||
{
|
||||
get => (Rect)GetValue(ItemsExtentProperty);
|
||||
set => SetValue(ItemsExtentProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Minimap.ResizeToViewport" />
|
||||
public bool ResizeToViewport
|
||||
{
|
||||
get => (bool)GetValue(ResizeToViewportProperty);
|
||||
set => SetValue(ResizeToViewportProperty, value);
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
double minX = double.MaxValue;
|
||||
double minY = double.MaxValue;
|
||||
|
||||
double maxX = double.MinValue;
|
||||
double maxY = double.MinValue;
|
||||
|
||||
UIElementCollection children = InternalChildren;
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
var item = (MinimapItem)children[i];
|
||||
item.Measure(availableSize);
|
||||
|
||||
Size size = item.DesiredSize;
|
||||
|
||||
if (item.Location.X < minX)
|
||||
{
|
||||
minX = item.Location.X;
|
||||
}
|
||||
|
||||
if (item.Location.Y < minY)
|
||||
{
|
||||
minY = item.Location.Y;
|
||||
}
|
||||
|
||||
double sizeX = item.Location.X + size.Width;
|
||||
if (sizeX > maxX)
|
||||
{
|
||||
maxX = sizeX;
|
||||
}
|
||||
|
||||
double sizeY = item.Location.Y + size.Height;
|
||||
if (sizeY > maxY)
|
||||
{
|
||||
maxY = sizeY;
|
||||
}
|
||||
}
|
||||
|
||||
var itemsExtent = minX == double.MaxValue
|
||||
? new Rect(0, 0, 0, 0)
|
||||
: new Rect(minX, minY, maxX - minX, maxY - minY);
|
||||
|
||||
ItemsExtent = itemsExtent;
|
||||
|
||||
if (ResizeToViewport)
|
||||
{
|
||||
itemsExtent.Union(new Rect(ViewportLocation, ViewportSize));
|
||||
}
|
||||
|
||||
Extent = itemsExtent;
|
||||
|
||||
double width = Math.Max(itemsExtent.Size.Width, ViewportSize.Width);
|
||||
double height = Math.Max(itemsExtent.Height, ViewportSize.Height);
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
UIElementCollection children = InternalChildren;
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
var item = (MinimapItem)children[i];
|
||||
item.Arrange(new Rect(item.Location - (Vector)Extent.Location, item.DesiredSize));
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Nodify/Minimap/States/KeyboardNavigation.cs
Normal file
35
Nodify/Minimap/States/KeyboardNavigation.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Interactivity
|
||||
{
|
||||
public static partial class MinimapState
|
||||
{
|
||||
public class KeyboardNavigation : InputElementState<Minimap>
|
||||
{
|
||||
public KeyboardNavigation(Minimap element) : base(element)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (Element.IsKeyboardFocused)
|
||||
{
|
||||
var gestures = EditorGestures.Mappings.Minimap;
|
||||
|
||||
if (gestures.Pan.TryGetNavigationDirection(e, out var panDirection))
|
||||
{
|
||||
var panning = new Vector(-panDirection.X * Minimap.NavigationStepSize, panDirection.Y * Minimap.NavigationStepSize);
|
||||
Element.UpdatePanning(panning);
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (gestures.ResetViewport.Matches(e.Source, e))
|
||||
{
|
||||
Element.ResetViewport();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Nodify/Minimap/States/MinimapState.cs
Normal file
17
Nodify/Minimap/States/MinimapState.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Nodify.Interactivity
|
||||
{
|
||||
public static partial class MinimapState
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether toggled panning mode is enabled, allowing the user to start and end the interaction in two steps with the same input gesture.
|
||||
/// </summary>
|
||||
public static bool EnableToggledPanningMode { get; set; }
|
||||
|
||||
internal static void RegisterDefaultHandlers()
|
||||
{
|
||||
InputProcessor.Shared<Minimap>.RegisterHandlerFactory(elem => new Panning(elem));
|
||||
InputProcessor.Shared<Minimap>.RegisterHandlerFactory(elem => new Zooming(elem));
|
||||
InputProcessor.Shared<Minimap>.RegisterHandlerFactory(elem => new KeyboardNavigation(elem));
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Nodify/Minimap/States/Panning.cs
Normal file
38
Nodify/Minimap/States/Panning.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Interactivity
|
||||
{
|
||||
public static partial class MinimapState
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the panning state of the <see cref="Minimap"/>, allowing the user to pan the viewport by clicking and dragging.
|
||||
/// </summary>
|
||||
public class Panning : DragState<Minimap>
|
||||
{
|
||||
protected override bool CanBegin => !Element.IsReadOnly;
|
||||
protected override bool CanCancel => Minimap.AllowPanningCancellation;
|
||||
protected override bool IsToggle => EnableToggledPanningMode;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Panning"/> class.
|
||||
/// </summary>
|
||||
/// <param name="minimap">The <see cref="Minimap"/> associated with this state.</param>
|
||||
public Panning(Minimap minimap)
|
||||
: base(minimap, EditorGestures.Mappings.Minimap.DragViewport, EditorGestures.Mappings.Minimap.CancelAction)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnBegin(InputEventArgs e)
|
||||
=> Element.BeginPanning();
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
=> Element.UpdatePanning(Element.MouseLocation);
|
||||
|
||||
protected override void OnEnd(InputEventArgs e)
|
||||
=> Element.EndPanning();
|
||||
|
||||
protected override void OnCancel(InputEventArgs e)
|
||||
=> Element.CancelPanning();
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Nodify/Minimap/States/Zooming.cs
Normal file
52
Nodify/Minimap/States/Zooming.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Interactivity
|
||||
{
|
||||
public static partial class MinimapState
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the zooming state of the <see cref="Minimap"/>.
|
||||
/// This state handles zooming operations using the mouse wheel with an optional modifier key.
|
||||
/// </summary>
|
||||
public class Zooming : InputElementState<Minimap>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Zooming"/> class.
|
||||
/// </summary>
|
||||
/// <param name="minimap">The <see cref="Minimap"/> associated with this state.</param>
|
||||
public Zooming(Minimap minimap) : base(minimap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
if (!Element.IsReadOnly && EditorGestures.Mappings.Minimap.ZoomModifierKey == Keyboard.Modifiers)
|
||||
{
|
||||
double zoom = Math.Pow(2.0, e.Delta / 3.0 / Mouse.MouseWheelDeltaForOneLine);
|
||||
Element.ZoomAtPosition(zoom, Element.MouseLocation);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
var gestures = EditorGestures.Mappings.Minimap;
|
||||
|
||||
if (!Element.IsReadOnly)
|
||||
{
|
||||
if (gestures.ZoomIn.Matches(e.Source, e))
|
||||
{
|
||||
Element.ZoomIn();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (gestures.ZoomOut.Matches(e.Source, e))
|
||||
{
|
||||
Element.ZoomOut();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Nodify/Minimap/SubtractConverter.cs
Normal file
20
Nodify/Minimap/SubtractConverter.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Nodify
|
||||
{
|
||||
internal sealed class SubtractConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
double result = (double)values[0] - (double)values[1];
|
||||
return result;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user