Add project files.
This commit is contained in:
269
Examples/Nodify.Shapes/App.xaml
Normal file
269
Examples/Nodify.Shapes/App.xaml
Normal file
@@ -0,0 +1,269 @@
|
||||
<Application x:Class="Nodify.Shapes.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary>
|
||||
|
||||
<Style x:Key="{x:Static SystemParameters.FocusVisualStyleKey}">
|
||||
<Setter Property="Control.Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Rectangle StrokeThickness="1"
|
||||
StrokeDashArray="2"
|
||||
Margin="-2"
|
||||
RadiusX="3"
|
||||
RadiusY="3"
|
||||
Stroke="DodgerBlue" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Pen x:Key="{x:Static nodify:BaseConnection.FocusVisualPenKey}"
|
||||
Thickness="1"
|
||||
Brush="DodgerBlue"
|
||||
DashStyle="{x:Static DashStyles.Dash}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary>
|
||||
<Style x:Key="IconButton"
|
||||
TargetType="Button"
|
||||
BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="Transparent" />
|
||||
<Setter Property="Padding"
|
||||
Value="2" />
|
||||
<Setter Property="Cursor"
|
||||
Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border x:Name="PART_Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
CornerRadius="3"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter x:Name="contentPresenter"
|
||||
Focusable="False"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsEnabled"
|
||||
Value="False">
|
||||
<Setter Property="Opacity"
|
||||
Value="0.4" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!--ICONS-->
|
||||
|
||||
<DataTemplate x:Key="MinusIcon">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M0 0h24v24H0z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M5 12h14" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="PlusIcon">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M0 0h24v24H0z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M12 5v14M5 12h14" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="MaximizeIcon">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M0 0h24v24H0z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M4 8V6a2 2 0 0 1 2-2h2M4 16v2a2 2 0 0 0 2 2h2M16 4h2a2 2 0 0 1 2 2v2M16 20h2a2 2 0 0 0 2-2v-2" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="CursorIcon">
|
||||
<Viewbox Stretch="Fill"
|
||||
Margin="2">
|
||||
<Path Stroke="White"
|
||||
StrokeThickness="1"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M.256.255a.874.874 0 0 0-.18.974l4.753 17.114a.875.875 0 0 0 1.603-.012L10 10l8.334-3.57a.875.875 0 0 0 .01-1.601L1.23.075a.874.874 0 0 0-.974.18Z" />
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="CircleIcon">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M0 0h24v24H0z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M3 12a9 9 0 1 0 18 0 9 9 0 1 0-18 0" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="SquareIcon">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M0 0h24v24H0z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="TriangleIcon">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M0 0h24v24H0z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M10.363 3.591 2.257 17.125a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636-2.87L13.637 3.59a1.914 1.914 0 0 0-3.274 0z" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ArrowBackIcon">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M0 0h24v24H0z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="m9 14-4-4 4-4" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M5 10h11a4 4 0 1 1 0 8h-1" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ArrowForwardIcon">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M0 0h24v24H0z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="m15 14 4-4-4-4" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M19 10H8a4 4 0 1 0 0 8h1" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="LockIcon">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M0 0h24v24H0z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M5 13a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-6z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M11 16a1 1 0 1 0 2 0 1 1 0 0 0-2 0M8 11V7a4 4 0 1 1 8 0v4" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="LockOpenIcon">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M0 0h24v24H0z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M5 13a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2z" />
|
||||
<Path Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M11 16a1 1 0 1 0 2 0 1 1 0 1 0-2 0M8 11V6a4 4 0 0 1 8 0" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
21
Examples/Nodify.Shapes/App.xaml.cs
Normal file
21
Examples/Nodify.Shapes/App.xaml.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
NodifyEditor.EnableDraggingContainersOptimizations = false;
|
||||
NodifyEditor.EnableCuttingLinePreview = true;
|
||||
|
||||
EditorGestures.Mappings.Connection.Disconnect.Value = new AnyGesture(new Interactivity.MouseGesture(MouseAction.LeftClick, ModifierKeys.Alt), new Interactivity.MouseGesture(MouseAction.RightClick));
|
||||
EditorGestures.Mappings.Editor.Pan.Value = new AnyGesture(EditorGestures.Mappings.Editor.Pan.Value, new Interactivity.MouseGesture(MouseAction.LeftClick, Key.Space));
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Examples/Nodify.Shapes/AppShellViewModel.cs
Normal file
9
Examples/Nodify.Shapes/AppShellViewModel.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Nodify.Shapes.Canvas;
|
||||
|
||||
namespace Nodify.Shapes
|
||||
{
|
||||
public class AppShellViewModel : ObservableObject
|
||||
{
|
||||
public CanvasViewModel Canvas { get; } = new CanvasViewModel();
|
||||
}
|
||||
}
|
||||
10
Examples/Nodify.Shapes/AssemblyInfo.cs
Normal file
10
Examples/Nodify.Shapes/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
94
Examples/Nodify.Shapes/Canvas/CanvasToolbarViewModel.cs
Normal file
94
Examples/Nodify.Shapes/Canvas/CanvasToolbarViewModel.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Nodify.Interactivity;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public enum CanvasTool
|
||||
{
|
||||
None,
|
||||
Ellipse,
|
||||
Rectangle,
|
||||
Triangle
|
||||
}
|
||||
|
||||
public class CanvasToolbarViewModel : ObservableObject
|
||||
{
|
||||
public static readonly CanvasTool[] AvailableTools = Enum.GetValues(typeof(CanvasTool)).Cast<CanvasTool>().ToArray();
|
||||
|
||||
internal static readonly EditorGestures EditorGestures = new EditorGestures();
|
||||
|
||||
private bool _locked;
|
||||
public bool Locked
|
||||
{
|
||||
get => _locked;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _locked, value))
|
||||
{
|
||||
var newMappings = _locked ? LockedGestureMappings.Instance : EditorGestures;
|
||||
EditorGestures.Mappings.Apply(newMappings);
|
||||
|
||||
if (_locked)
|
||||
{
|
||||
_selectedTool = CanvasTool.None;
|
||||
OnPropertyChanged(nameof(SelectedTool));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand ToggleLockCommand { get; set; }
|
||||
|
||||
private CanvasTool _selectedTool = CanvasTool.None;
|
||||
public CanvasTool SelectedTool
|
||||
{
|
||||
get => _selectedTool;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedTool, Locked ? CanvasTool.None : value))
|
||||
{
|
||||
var newMappings = _selectedTool == CanvasTool.None ? EditorGestures : DrawingGesturesMappings.Instance;
|
||||
EditorGestures.Mappings.Apply(newMappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CanvasViewModel Canvas { get; }
|
||||
|
||||
public CanvasToolbarViewModel(CanvasViewModel canvas)
|
||||
{
|
||||
// copy any user modifications
|
||||
EditorGestures.Apply(EditorGestures.Mappings);
|
||||
|
||||
ToggleLockCommand = new DelegateCommand(() => Locked = !Locked);
|
||||
Canvas = canvas;
|
||||
}
|
||||
|
||||
public ShapeViewModel CreateShapeAtLocation(Point location)
|
||||
{
|
||||
using (Canvas.UndoRedo.Batch("Create shape"))
|
||||
{
|
||||
ShapeViewModel shape = SelectedTool switch
|
||||
{
|
||||
CanvasTool.Ellipse => new EllipseViewModel(),
|
||||
CanvasTool.Rectangle => new RectangleViewModel(),
|
||||
CanvasTool.Triangle => new TriangleViewModel(),
|
||||
CanvasTool.None => throw new InvalidOperationException("Cannot draw in this state"),
|
||||
_ => throw new NotImplementedException(nameof(CanvasTool)),
|
||||
};
|
||||
|
||||
shape.Location = location;
|
||||
shape.Text = "Double click to edit";
|
||||
|
||||
Canvas.AddShape(shape);
|
||||
Canvas.SelectedShapes.Clear();
|
||||
Canvas.SelectedShapes.Add(shape);
|
||||
|
||||
return shape;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
710
Examples/Nodify.Shapes/Canvas/CanvasView.xaml
Normal file
710
Examples/Nodify.Shapes/Canvas/CanvasView.xaml
Normal file
@@ -0,0 +1,710 @@
|
||||
<UserControl x:Class="Nodify.Shapes.Canvas.CanvasView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Nodify.Shapes.Canvas"
|
||||
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
xmlns:controls="clr-namespace:Nodify.Shapes.Controls"
|
||||
xmlns:o="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
|
||||
mc:Ignorable="d"
|
||||
Background="#0a172a"
|
||||
Foreground="white"
|
||||
d:DataContext="{d:DesignInstance Type=local:CanvasViewModel, IsDesignTimeCreatable=True}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Grid.Resources>
|
||||
<shared:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
|
||||
<shared:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||
<shared:ColorToSolidColorBrushConverter x:Key="ColorToSolidColorBrushConverter" />
|
||||
|
||||
<VisualBrush x:Key="GridDrawingBrush"
|
||||
TileMode="Tile"
|
||||
Viewport="0 0 30 30"
|
||||
ViewportUnits="Absolute"
|
||||
Viewbox="0 0 30 30"
|
||||
ViewboxUnits="Absolute"
|
||||
o:Freeze="True"
|
||||
Transform="{Binding ViewportTransform, ElementName=Editor}">
|
||||
<VisualBrush.Visual>
|
||||
<Rectangle Width="1"
|
||||
Height="1"
|
||||
Fill="White" />
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Grid.Resources>
|
||||
|
||||
<nodify:NodifyEditor ItemsSource="{Binding Shapes}"
|
||||
SelectedItems="{Binding SelectedShapes}"
|
||||
Connections="{Binding Connections}"
|
||||
Decorators="{Binding Decorators}"
|
||||
Background="{StaticResource GridDrawingBrush}"
|
||||
ConnectionCompletedCommand="{Binding CreateConnectionCommand}"
|
||||
ItemsDragStartedCommand="{Binding MoveShapesStartedCommand}"
|
||||
ItemsDragCompletedCommand="{Binding MoveShapesCompletedCommand}"
|
||||
RemoveConnectionCommand="{Binding RemoveConnectionCommand}"
|
||||
SelectionChanged="Editor_SelectionChanged"
|
||||
ItemsMoved="Editor_ItemsMoved"
|
||||
MouseDown="Editor_MouseDown"
|
||||
MouseMove="Editor_MouseMove"
|
||||
MouseUp="Editor_MouseUp"
|
||||
GridCellSize="5"
|
||||
x:Name="Editor">
|
||||
<nodify:NodifyEditor.Style>
|
||||
<Style TargetType="{x:Type nodify:NodifyEditor}"
|
||||
BasedOn="{StaticResource {x:Type nodify:NodifyEditor}}">
|
||||
<Setter Property="Cursor"
|
||||
Value="Cross" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding CanvasToolbar.SelectedTool}"
|
||||
Value="{x:Static local:CanvasTool.None}">
|
||||
<Setter Property="Cursor"
|
||||
Value="Arrow" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:NodifyEditor.Style>
|
||||
|
||||
<nodify:NodifyEditor.Resources>
|
||||
<DataTemplate DataType="{x:Type local:EllipseViewModel}">
|
||||
<Ellipse Stretch="Fill"
|
||||
Focusable="False"
|
||||
Fill="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
Stroke="{Binding BorderColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
StrokeThickness="2"
|
||||
Opacity="0.8" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:RectangleViewModel}">
|
||||
<Rectangle Stretch="Fill"
|
||||
Focusable="False"
|
||||
Fill="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
Stroke="{Binding BorderColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
StrokeThickness="2"
|
||||
Opacity="0.8" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:TriangleViewModel}">
|
||||
<Polygon Points="0,100 50,0 100,100"
|
||||
Focusable="False"
|
||||
Stretch="Fill"
|
||||
Fill="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
Stroke="{Binding BorderColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
StrokeThickness="2"
|
||||
Opacity="0.8" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:ShapeToolbarViewModel}">
|
||||
<Canvas Visibility="{Binding Shape, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<shared:Swatches SelectedColor="{Binding Shape.Color}"
|
||||
Colors="{x:Static local:ShapeViewModel.Colors}"
|
||||
IsEnabled="{Binding DataContext.CanvasToolbar.Locked, ElementName=Editor, Converter={StaticResource InverseBooleanConverter}}"
|
||||
Canvas.Top="-70"
|
||||
Panel.ZIndex="1">
|
||||
<shared:Swatches.Effect>
|
||||
<DropShadowEffect ShadowDepth="1" />
|
||||
</shared:Swatches.Effect>
|
||||
</shared:Swatches>
|
||||
</Canvas>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:UserCursorViewModel}">
|
||||
<StackPanel IsHitTestVisible="False">
|
||||
<Viewbox Width="24"
|
||||
Height="24"
|
||||
Margin="-10 0 0 5"
|
||||
Stretch="Fill"
|
||||
HorizontalAlignment="Left">
|
||||
<Path Fill="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
Stroke="White"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Data="M.256.255a.874.874 0 0 0-.18.974l4.753 17.114a.875.875 0 0 0 1.603-.012L10 10l8.334-3.57a.875.875 0 0 0 .01-1.601L1.23.075a.874.874 0 0 0-.974.18Z" />
|
||||
</Viewbox>
|
||||
<Border CornerRadius="3"
|
||||
Background="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}, ConverterParameter=0.7}"
|
||||
Padding="6 2">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<Style TargetType="{x:Type nodify:PendingConnection}"
|
||||
BasedOn="{StaticResource {x:Type nodify:PendingConnection}}">
|
||||
<Setter Property="Stroke">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="White"
|
||||
Opacity="0.7" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</nodify:NodifyEditor.Resources>
|
||||
|
||||
<nodify:NodifyEditor.ConnectionTemplate>
|
||||
<DataTemplate DataType="{x:Type local:ConnectionViewModel}">
|
||||
<nodify:StepConnection Source="{Binding Source.Anchor}"
|
||||
Target="{Binding Target.Anchor}"
|
||||
SourcePosition="{Binding Source.Position}"
|
||||
TargetPosition="{Binding Target.Position}"
|
||||
Cursor="Hand"
|
||||
IsSelectable="True"
|
||||
Fill="Transparent"
|
||||
FocusVisualPadding="2"
|
||||
StrokeThickness="3">
|
||||
<nodify:StepConnection.Stroke>
|
||||
<SolidColorBrush Color="White"
|
||||
Opacity="0.7" />
|
||||
</nodify:StepConnection.Stroke>
|
||||
<nodify:StepConnection.Style>
|
||||
<Style TargetType="{x:Type nodify:StepConnection}">
|
||||
<Setter Property="OutlineBrush"
|
||||
Value="Transparent" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver"
|
||||
Value="True">
|
||||
<Setter Property="OutlineBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="White"
|
||||
Opacity="0.15" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected"
|
||||
Value="True">
|
||||
<Setter Property="OutlineBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="DodgerBlue"
|
||||
Opacity="0.25" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:StepConnection.Style>
|
||||
</nodify:StepConnection>
|
||||
</DataTemplate>
|
||||
</nodify:NodifyEditor.ConnectionTemplate>
|
||||
|
||||
<nodify:NodifyEditor.DecoratorContainerStyle>
|
||||
<Style TargetType="{x:Type nodify:DecoratorContainer}"
|
||||
BasedOn="{StaticResource {x:Type nodify:DecoratorContainer}}">
|
||||
<Setter Property="Location"
|
||||
Value="{Binding Location}" />
|
||||
</Style>
|
||||
</nodify:NodifyEditor.DecoratorContainerStyle>
|
||||
|
||||
<nodify:NodifyEditor.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type nodify:ItemContainer}"
|
||||
BasedOn="{StaticResource {x:Type nodify:ItemContainer}}">
|
||||
<Style.Resources>
|
||||
<Style TargetType="{x:Type nodify:Connector}"
|
||||
BasedOn="{StaticResource {x:Type nodify:Connector}}">
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=nodify:ItemContainer}}" />
|
||||
<Setter Property="Background"
|
||||
Value="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=nodify:ItemContainer}}" />
|
||||
<Setter Property="HorizontalContentAlignment"
|
||||
Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment"
|
||||
Value="Center" />
|
||||
<Setter Property="IsConnected"
|
||||
Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Grid Background="Transparent">
|
||||
<Ellipse x:Name="PART_Connector"
|
||||
Width="14"
|
||||
Height="14"
|
||||
Stroke="{TemplateBinding BorderBrush}"
|
||||
Fill="{TemplateBinding Background}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type shared:Resizer}"
|
||||
BasedOn="{StaticResource {x:Type shared:Resizer}}">
|
||||
<Setter Property="Background"
|
||||
Value="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=nodify:ItemContainer}}" />
|
||||
<Setter Property="IsEnabled"
|
||||
Value="{Binding DataContext.CanvasToolbar.Locked, ElementName=Editor, Converter={StaticResource InverseBooleanConverter}}" />
|
||||
</Style>
|
||||
|
||||
<!--For some reason, the designer doesn't like having these bindings on the element itself but in a style-->
|
||||
<Style TargetType="{x:Type controls:ResizableContainer}"
|
||||
BasedOn="{StaticResource {x:Type shared:ResizablePanel}}">
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=nodify:ItemContainer}}" />
|
||||
<Setter Property="Padding"
|
||||
Value="3" />
|
||||
<Setter Property="ResizeStartedCommand"
|
||||
Value="{Binding DataContext.ResizeShapeStartedCommand, ElementName=Editor}" />
|
||||
<Setter Property="ResizeCompletedCommand"
|
||||
Value="{Binding DataContext.ResizeShapeCompletedCommand, ElementName=Editor}" />
|
||||
</Style>
|
||||
</Style.Resources>
|
||||
<Setter Property="Location"
|
||||
Value="{Binding Location}" />
|
||||
<Setter Property="SelectedBrush"
|
||||
Value="{Binding BorderColor, Converter={StaticResource ColorToSolidColorBrushConverter}}" />
|
||||
<Setter Property="SelectedBorderThickness"
|
||||
Value="1" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="Transparent" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type nodify:ItemContainer}">
|
||||
<controls:ResizableContainer Height="{Binding Height, Mode=TwoWay}"
|
||||
Width="{Binding Width, Mode=TwoWay}"
|
||||
Directions="Corners"
|
||||
GridCellSize="5"
|
||||
Focusable="False">
|
||||
<Grid>
|
||||
<ContentPresenter Cursor="Hand" />
|
||||
|
||||
<shared:EditableTextBlock Text="{Binding Text}"
|
||||
IsEnabled="{Binding DataContext.CanvasToolbar.Locked, ElementName=Editor, Converter={StaticResource InverseBooleanConverter}}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
FontSize="16"
|
||||
ToolTip="Double click to edit" />
|
||||
|
||||
<DockPanel LastChildFill="False">
|
||||
<nodify:Connector DataContext="{Binding LeftConnector}"
|
||||
Anchor="{Binding Anchor, Mode=OneWayToSource}"
|
||||
DockPanel.Dock="Left"
|
||||
HorizontalContentAlignment="Left"
|
||||
Margin="-11 0 0 0"
|
||||
Visibility="Hidden"
|
||||
Height="Auto"
|
||||
Width="25"
|
||||
x:Name="LeftConnector" />
|
||||
|
||||
<nodify:Connector DataContext="{Binding RightConnector}"
|
||||
Anchor="{Binding Anchor, Mode=OneWayToSource}"
|
||||
DockPanel.Dock="Right"
|
||||
HorizontalContentAlignment="Right"
|
||||
Margin="0 0 -11 0"
|
||||
Visibility="Hidden"
|
||||
Height="Auto"
|
||||
Width="25"
|
||||
x:Name="RightConnector" />
|
||||
|
||||
<nodify:Connector DataContext="{Binding TopConnector}"
|
||||
Anchor="{Binding Anchor, Mode=OneWayToSource}"
|
||||
DockPanel.Dock="Top"
|
||||
VerticalContentAlignment="Top"
|
||||
Margin="0 -11 0 0"
|
||||
Visibility="Hidden"
|
||||
Width="Auto"
|
||||
Height="25"
|
||||
x:Name="TopConnector" />
|
||||
|
||||
<nodify:Connector DataContext="{Binding BottomConnector}"
|
||||
Anchor="{Binding Anchor, Mode=OneWayToSource}"
|
||||
DockPanel.Dock="Bottom"
|
||||
VerticalContentAlignment="Bottom"
|
||||
Margin="0 0 0 -11"
|
||||
Visibility="Hidden"
|
||||
Width="Auto"
|
||||
Height="25"
|
||||
x:Name="BottomConnector" />
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</controls:ResizableContainer>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsSelected"
|
||||
Value="True" />
|
||||
<Condition Property="IsPreviewingSelection"
|
||||
Value="{x:Null}" />
|
||||
</MultiTrigger.Conditions>
|
||||
<MultiTrigger.Setters>
|
||||
<Setter Property="Visibility"
|
||||
TargetName="LeftConnector"
|
||||
Value="Visible" />
|
||||
<Setter Property="Visibility"
|
||||
TargetName="RightConnector"
|
||||
Value="Visible" />
|
||||
<Setter Property="Visibility"
|
||||
TargetName="TopConnector"
|
||||
Value="Visible" />
|
||||
<Setter Property="Visibility"
|
||||
TargetName="BottomConnector"
|
||||
Value="Visible" />
|
||||
</MultiTrigger.Setters>
|
||||
</MultiTrigger>
|
||||
<Trigger Property="IsPreviewingSelection"
|
||||
Value="True">
|
||||
<Setter Property="Visibility"
|
||||
TargetName="LeftConnector"
|
||||
Value="Visible" />
|
||||
<Setter Property="Visibility"
|
||||
TargetName="RightConnector"
|
||||
Value="Visible" />
|
||||
<Setter Property="Visibility"
|
||||
TargetName="TopConnector"
|
||||
Value="Visible" />
|
||||
<Setter Property="Visibility"
|
||||
TargetName="BottomConnector"
|
||||
Value="Visible" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected"
|
||||
Value="True">
|
||||
<Setter Property="Panel.ZIndex"
|
||||
Value="1" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:NodifyEditor.ItemContainerStyle>
|
||||
</nodify:NodifyEditor>
|
||||
|
||||
<!--MINIMAP-->
|
||||
<shared:ResizablePanel Directions="BottomLeft"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Width="300"
|
||||
Height="200"
|
||||
MinWidth="250"
|
||||
MinHeight="150"
|
||||
BorderBrush="{x:Null}"
|
||||
Focusable="False"
|
||||
Margin="20">
|
||||
<shared:ResizablePanel.Resources>
|
||||
<Style TargetType="{x:Type shared:Resizer}">
|
||||
<Setter Property="Cursor"
|
||||
Value="SizeNESW" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type shared:Resizer}">
|
||||
<Grid Margin="-3 -6 -6 -3"
|
||||
Background="Transparent">
|
||||
<Rectangle Height="12"
|
||||
Width="3"
|
||||
Fill="#d2d4d7"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom" />
|
||||
<Rectangle Height="3"
|
||||
Width="12"
|
||||
Fill="#d2d4d7"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</shared:ResizablePanel.Resources>
|
||||
<Border CornerRadius="3"
|
||||
BorderBrush="#1e293b"
|
||||
BorderThickness="3">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="1" />
|
||||
</Border.Effect>
|
||||
|
||||
<nodify:Minimap ItemsSource="{Binding ItemsSource, ElementName=Editor}"
|
||||
ViewportLocation="{Binding ViewportLocation, ElementName=Editor}"
|
||||
ViewportSize="{Binding ViewportSize, ElementName=Editor}"
|
||||
ResizeToViewport="True"
|
||||
Zoom="Minimap_Zoom">
|
||||
<nodify:Minimap.Resources>
|
||||
<DataTemplate DataType="{x:Type local:EllipseViewModel}">
|
||||
<Ellipse Stretch="Fill"
|
||||
Fill="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
Stroke="{Binding BorderColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
StrokeThickness="2"
|
||||
Opacity="0.8" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:RectangleViewModel}">
|
||||
<Rectangle Stretch="Fill"
|
||||
Fill="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
Stroke="{Binding BorderColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
StrokeThickness="2"
|
||||
Opacity="0.8" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:TriangleViewModel}">
|
||||
<Polygon Points="0,100 50,0 100,100"
|
||||
Stretch="Fill"
|
||||
Fill="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
Stroke="{Binding BorderColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
StrokeThickness="2"
|
||||
Opacity="0.8" />
|
||||
</DataTemplate>
|
||||
</nodify:Minimap.Resources>
|
||||
<nodify:Minimap.Background>
|
||||
<SolidColorBrush Color="#111a2d"
|
||||
Opacity="0.5" />
|
||||
</nodify:Minimap.Background>
|
||||
<nodify:Minimap.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type nodify:MinimapItem}">
|
||||
<Setter Property="Location"
|
||||
Value="{Binding Location}" />
|
||||
<Setter Property="Width"
|
||||
Value="{Binding Width}" />
|
||||
<Setter Property="Height"
|
||||
Value="{Binding Height}" />
|
||||
</Style>
|
||||
</nodify:Minimap.ItemContainerStyle>
|
||||
<nodify:Minimap.ViewportStyle>
|
||||
<Style TargetType="Rectangle">
|
||||
<Setter Property="StrokeThickness"
|
||||
Value="3" />
|
||||
<Setter Property="Fill">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="#445e87"
|
||||
Opacity="0.3" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</nodify:Minimap.ViewportStyle>
|
||||
</nodify:Minimap>
|
||||
</Border>
|
||||
</shared:ResizablePanel>
|
||||
|
||||
<!--TOOLBARS-->
|
||||
|
||||
<Border VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0 0 0 20"
|
||||
CornerRadius="3"
|
||||
Background="#1e293b">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="1" />
|
||||
</Border.Effect>
|
||||
<ItemsControl Focusable="True"
|
||||
KeyboardNavigation.TabNavigation="Cycle">
|
||||
<ItemsControl.Resources>
|
||||
<Style TargetType="Button"
|
||||
BasedOn="{StaticResource IconButton}">
|
||||
<Setter Property="Margin"
|
||||
Value="2" />
|
||||
<Setter Property="Width"
|
||||
Value="32" />
|
||||
<Setter Property="Height"
|
||||
Value="32" />
|
||||
<Setter Property="Focusable"
|
||||
Value="False" />
|
||||
</Style>
|
||||
</ItemsControl.Resources>
|
||||
|
||||
<ListBox BorderThickness="0"
|
||||
Background="Transparent"
|
||||
ItemsSource="{x:Static local:CanvasToolbarViewModel.AvailableTools}"
|
||||
SelectedValue="{Binding CanvasToolbar.SelectedTool}"
|
||||
KeyboardNavigation.ControlTabNavigation="None">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<Setter Property="Margin"
|
||||
Value="2" />
|
||||
<Setter Property="Padding"
|
||||
Value="0" />
|
||||
<Setter Property="Width"
|
||||
Value="32" />
|
||||
<Setter Property="Height"
|
||||
Value="32" />
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
|
||||
<ListBox.Resources>
|
||||
<DataTemplate x:Key="Cursor">
|
||||
<Border CornerRadius="3"
|
||||
Padding="5">
|
||||
<ContentPresenter ContentTemplate="{StaticResource CursorIcon}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="Circle">
|
||||
<Border CornerRadius="3"
|
||||
Padding="5">
|
||||
<ContentPresenter ContentTemplate="{StaticResource CircleIcon}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="Square">
|
||||
<Border CornerRadius="3"
|
||||
Padding="5">
|
||||
<ContentPresenter ContentTemplate="{StaticResource SquareIcon}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="Triangle">
|
||||
<Border CornerRadius="3"
|
||||
Padding="5">
|
||||
<ContentPresenter ContentTemplate="{StaticResource TriangleIcon}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.Resources>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{Binding}"
|
||||
Focusable="False">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource Cursor}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding}"
|
||||
Value="{x:Static local:CanvasTool.Ellipse}">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource Circle}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding}"
|
||||
Value="{x:Static local:CanvasTool.Rectangle}">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource Square}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding}"
|
||||
Value="{x:Static local:CanvasTool.Triangle}">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource Triangle}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
IsItemsHost="True" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
</ListBox>
|
||||
|
||||
<Button Command="{Binding UndoCommand}"
|
||||
CommandTarget="{Binding ElementName=Editor}"
|
||||
ContentTemplate="{StaticResource ArrowBackIcon}"
|
||||
ToolTip="CTRL+Z"
|
||||
Padding="1" />
|
||||
|
||||
<Button Command="{Binding RedoCommand}"
|
||||
CommandTarget="{Binding ElementName=Editor}"
|
||||
ContentTemplate="{StaticResource ArrowForwardIcon}"
|
||||
ToolTip="CTRL+SHIFT+z"
|
||||
Padding="1" />
|
||||
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
|
||||
<Border VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="20 0 0 20"
|
||||
CornerRadius="3"
|
||||
Background="#1e293b">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="1" />
|
||||
</Border.Effect>
|
||||
|
||||
<ItemsControl Focusable="True"
|
||||
KeyboardNavigation.TabNavigation="Cycle"
|
||||
KeyboardNavigation.DirectionalNavigation="Cycle"
|
||||
KeyboardNavigation.ControlTabNavigation="None">
|
||||
<ItemsControl.Resources>
|
||||
<Style TargetType="Button"
|
||||
BasedOn="{StaticResource IconButton}">
|
||||
<Setter Property="Margin"
|
||||
Value="2" />
|
||||
<Setter Property="Width"
|
||||
Value="32" />
|
||||
<Setter Property="Height"
|
||||
Value="32" />
|
||||
</Style>
|
||||
</ItemsControl.Resources>
|
||||
|
||||
<Button Command="{x:Static nodify:EditorCommands.ZoomIn}"
|
||||
CommandTarget="{Binding ElementName=Editor}"
|
||||
ContentTemplate="{StaticResource PlusIcon}" />
|
||||
|
||||
<Button Command="{x:Static nodify:EditorCommands.ZoomOut}"
|
||||
CommandTarget="{Binding ElementName=Editor}"
|
||||
ContentTemplate="{StaticResource MinusIcon}" />
|
||||
|
||||
<Button Command="{x:Static nodify:EditorCommands.FitToScreen}"
|
||||
CommandTarget="{Binding ElementName=Editor}"
|
||||
ContentTemplate="{StaticResource MaximizeIcon}" />
|
||||
|
||||
<Button Command="{Binding ToggleLockCommand}"
|
||||
DataContext="{Binding CanvasToolbar}">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button"
|
||||
BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource LockOpenIcon}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Locked}"
|
||||
Value="True">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource LockIcon}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
|
||||
<Border VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 0 20 20"
|
||||
Height="38"
|
||||
CornerRadius="3">
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Cursors}"
|
||||
Focusable="True"
|
||||
KeyboardNavigation.TabNavigation="Cycle"
|
||||
KeyboardNavigation.ControlTabNavigation="None">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:UserCursorViewModel}">
|
||||
<Border CornerRadius="50"
|
||||
Height="28"
|
||||
Width="28"
|
||||
Margin="-3"
|
||||
Background="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||
Padding="6 2">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="1" />
|
||||
</Border.Effect>
|
||||
<Button Command="{x:Static nodify:EditorCommands.BringIntoView}"
|
||||
CommandParameter="{Binding Location}"
|
||||
CommandTarget="{Binding ElementName=Editor}"
|
||||
Style="{StaticResource IconButton}"
|
||||
ToolTip="{Binding Name}"
|
||||
Content="{Binding Name[0]}"
|
||||
Foreground="White"
|
||||
Margin="-6 -2" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
146
Examples/Nodify.Shapes/Canvas/CanvasView.xaml.cs
Normal file
146
Examples/Nodify.Shapes/Canvas/CanvasView.xaml.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using Nodify.Events;
|
||||
using Nodify.Shapes.Canvas.UndoRedo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public partial class CanvasView : UserControl
|
||||
{
|
||||
private readonly DispatcherTimer _moveToLocationTimer;
|
||||
private readonly DispatcherTimer _generateLocationTimer;
|
||||
private readonly Random _random = new Random();
|
||||
private readonly Dictionary<UserCursorViewModel, Point> _moveToLocations = new Dictionary<UserCursorViewModel, Point>();
|
||||
|
||||
public CanvasView()
|
||||
{
|
||||
InitializeComponent();
|
||||
_moveToLocationTimer = new DispatcherTimer(TimeSpan.FromSeconds(1d / 60d), DispatcherPriority.Background, OnMoveToLocationTick, Dispatcher);
|
||||
_generateLocationTimer = new DispatcherTimer(TimeSpan.FromSeconds(3), DispatcherPriority.Background, OnGenerateNewLocation, Dispatcher);
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_moveToLocationTimer.Start();
|
||||
_generateLocationTimer.Start();
|
||||
|
||||
OnGenerateNewLocation(this, EventArgs.Empty);
|
||||
|
||||
base.OnApplyTemplate();
|
||||
}
|
||||
|
||||
private void OnGenerateNewLocation(object? sender, EventArgs e)
|
||||
{
|
||||
var canvasVM = (CanvasViewModel)DataContext;
|
||||
|
||||
foreach (var cursor in canvasVM.Cursors)
|
||||
{
|
||||
_moveToLocations[cursor] = Editor.MouseLocation + new Vector(_random.Next(-1500, 1500), _random.Next(-1000, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMoveToLocationTick(object? sender, EventArgs e)
|
||||
{
|
||||
var canvasVM = (CanvasViewModel)DataContext;
|
||||
double speed = 0.015d;
|
||||
|
||||
for (int i = 0; i < canvasVM.Cursors.Count; i++)
|
||||
{
|
||||
var user = canvasVM.Cursors[i];
|
||||
var targetLocation = _moveToLocations[user];
|
||||
|
||||
Vector dir = targetLocation - user.Location;
|
||||
|
||||
var newLocation = user.Location + dir * speed;
|
||||
user.Location = newLocation;
|
||||
}
|
||||
}
|
||||
|
||||
#region Drawing shapes
|
||||
|
||||
private ShapeViewModel? _drawingShape;
|
||||
private Point _initialLocation;
|
||||
|
||||
private void Editor_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var toolbarVm = ((CanvasViewModel)DataContext).CanvasToolbar;
|
||||
if (toolbarVm.SelectedTool != CanvasTool.None && DrawingGesturesMappings.Instance.Draw.Matches(this, e))
|
||||
{
|
||||
_initialLocation = Editor.MouseLocation;
|
||||
_drawingShape = toolbarVm.CreateShapeAtLocation(Editor.MouseLocation);
|
||||
}
|
||||
}
|
||||
|
||||
private void Editor_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (_drawingShape != null)
|
||||
{
|
||||
_drawingShape.Width = Math.Abs(Editor.MouseLocation.X - _initialLocation.X);
|
||||
_drawingShape.Height = Math.Abs(Editor.MouseLocation.Y - _initialLocation.Y);
|
||||
|
||||
if (Editor.MouseLocation.X < _initialLocation.X)
|
||||
{
|
||||
_drawingShape.Location = new Point(Editor.MouseLocation.X, _drawingShape.Location.Y);
|
||||
}
|
||||
|
||||
if (Editor.MouseLocation.Y < _initialLocation.Y)
|
||||
{
|
||||
_drawingShape.Location = new Point(_drawingShape.Location.X, Editor.MouseLocation.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Editor_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_drawingShape = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void Minimap_Zoom(object sender, ZoomEventArgs e)
|
||||
{
|
||||
Editor.ZoomAtPosition(e.Zoom, e.Location);
|
||||
}
|
||||
|
||||
private void Editor_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var selectedNodes = e.AddedItems.OfType<ShapeViewModel>().ToList();
|
||||
var deselectedNodes = e.RemovedItems.OfType<ShapeViewModel>().ToList();
|
||||
|
||||
if (selectedNodes.Count == 0 && deselectedNodes.Count == 0)
|
||||
{
|
||||
// The selection event most likely contains the selected connections
|
||||
return;
|
||||
}
|
||||
|
||||
string batchLabel = selectedNodes.Count > 0 ? "Select shapes" : "Deselect shapes";
|
||||
var canvasVM = (CanvasViewModel)DataContext;
|
||||
|
||||
using (canvasVM.UndoRedo.Batch(batchLabel))
|
||||
{
|
||||
var selectNodes = new SelectShapesAction(selectedNodes, canvasVM);
|
||||
var deselectNodes = new DeselectShapesAction(deselectedNodes, canvasVM);
|
||||
|
||||
canvasVM.UndoRedo.Record(deselectNodes);
|
||||
canvasVM.UndoRedo.Record(selectNodes);
|
||||
}
|
||||
}
|
||||
|
||||
private void Editor_ItemsMoved(object sender, ItemsMovedEventArgs e)
|
||||
{
|
||||
var shapes = e.Items.Cast<ShapeViewModel>().ToList();
|
||||
|
||||
var canvasVM = (CanvasViewModel)DataContext;
|
||||
using (canvasVM.UndoRedo.Batch("Move shapes"))
|
||||
{
|
||||
var moveShapes = new MoveShapesAction(shapes, e.Offset);
|
||||
canvasVM.UndoRedo.Record(moveShapes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
221
Examples/Nodify.Shapes/Canvas/CanvasViewModel.cs
Normal file
221
Examples/Nodify.Shapes/Canvas/CanvasViewModel.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
using Nodify.Shapes.Canvas.UndoRedo;
|
||||
using Nodify.UndoRedo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class CanvasViewModel : ObservableObject
|
||||
{
|
||||
private readonly NodifyObservableCollection<ShapeViewModel> _shapes = new NodifyObservableCollection<ShapeViewModel>();
|
||||
public IReadOnlyCollection<ShapeViewModel> Shapes => _shapes;
|
||||
public NodifyObservableCollection<ShapeViewModel> SelectedShapes { get; } = new NodifyObservableCollection<ShapeViewModel>();
|
||||
|
||||
public NodifyObservableCollection<ICanvasDecorator> Decorators { get; } = new NodifyObservableCollection<ICanvasDecorator>();
|
||||
public NodifyObservableCollection<ConnectionViewModel> Connections { get; } = new NodifyObservableCollection<ConnectionViewModel>();
|
||||
|
||||
private ShapeToolbarViewModel ShapeToolbar { get; } = new ShapeToolbarViewModel();
|
||||
public CanvasToolbarViewModel CanvasToolbar { get; }
|
||||
|
||||
public ICommand MoveShapesStartedCommand { get; }
|
||||
public ICommand MoveShapesCompletedCommand { get; }
|
||||
public ICommand ResizeShapeStartedCommand { get; }
|
||||
public ICommand ResizeShapeCompletedCommand { get; }
|
||||
public ICommand CreateConnectionCommand { get; }
|
||||
public ICommand RemoveConnectionCommand { get; }
|
||||
public ICommand DeleteSelectionCommand { get; }
|
||||
|
||||
public ICommand UndoCommand { get; }
|
||||
public ICommand RedoCommand { get; }
|
||||
|
||||
public NodifyObservableCollection<UserCursorViewModel> Cursors { get; } = new NodifyObservableCollection<UserCursorViewModel>();
|
||||
public IActionsHistory UndoRedo { get; } = ActionsHistory.Global;
|
||||
|
||||
private readonly Random _rand = new Random();
|
||||
|
||||
public CanvasViewModel()
|
||||
{
|
||||
CanvasToolbar = new CanvasToolbarViewModel(this);
|
||||
|
||||
UndoCommand = new RequeryCommand(UndoRedo.Undo, () => UndoRedo.CanUndo && !CanvasToolbar.Locked);
|
||||
RedoCommand = new RequeryCommand(UndoRedo.Redo, () => UndoRedo.CanRedo && !CanvasToolbar.Locked);
|
||||
|
||||
MoveShapesStartedCommand = new DelegateCommand(MoveShapesStartedHandler);
|
||||
MoveShapesCompletedCommand = new DelegateCommand(MoveShapesCompletedHandler);
|
||||
|
||||
ResizeShapeStartedCommand = new DelegateCommand(ResizeShapeStartedHandler);
|
||||
ResizeShapeCompletedCommand = new DelegateCommand(ResizeShapeCompletedHandler);
|
||||
|
||||
DeleteSelectionCommand = new DelegateCommand(DeleteSelection, () => !CanvasToolbar.Locked);
|
||||
|
||||
CreateConnectionCommand = new DelegateCommand<(object Source, object Target)>(
|
||||
((object Source, object Target) pendingConnection) => AddConnection((ConnectorViewModel)pendingConnection.Source, (ConnectorViewModel)pendingConnection.Target),
|
||||
((object Source, object Target) pendingConnection) => CanConnect((ConnectorViewModel)pendingConnection.Source, (ConnectorViewModel)pendingConnection.Target));
|
||||
|
||||
RemoveConnectionCommand = new DelegateCommand<ConnectionViewModel>(RemoveConnection);
|
||||
|
||||
SelectedShapes.WhenAdded(shape =>
|
||||
{
|
||||
ShapeToolbar.Shape = SelectedShapes.Count == 1 ? shape : null;
|
||||
});
|
||||
|
||||
SelectedShapes.WhenRemoved(shape =>
|
||||
{
|
||||
ShapeToolbar.Shape = SelectedShapes.Count == 1 ? SelectedShapes.Single() : null;
|
||||
});
|
||||
|
||||
SelectedShapes.WhenCleared(shapes => ShapeToolbar.Shape = null);
|
||||
|
||||
FillCanvasWithShapes();
|
||||
}
|
||||
|
||||
private void MoveShapesStartedHandler()
|
||||
{
|
||||
ShapeToolbar.Hide();
|
||||
}
|
||||
|
||||
private void MoveShapesCompletedHandler()
|
||||
{
|
||||
ShapeToolbar.Show();
|
||||
}
|
||||
|
||||
private void ResizeShapeStartedHandler()
|
||||
{
|
||||
UndoRedo.ExecuteAction(new ResizeShapesAction(this));
|
||||
}
|
||||
|
||||
private void ResizeShapeCompletedHandler()
|
||||
{
|
||||
if (UndoRedo.Current is ResizeShapesAction resizeShapes)
|
||||
{
|
||||
resizeShapes.SaveSizes();
|
||||
}
|
||||
}
|
||||
|
||||
private void FillCanvasWithShapes()
|
||||
{
|
||||
// Disable undo redo to avoid recording object construction
|
||||
UndoRedo.IsEnabled = false;
|
||||
|
||||
var cursorCount = _rand.Next(3, 6);
|
||||
for (int i = 0; i < cursorCount; i++)
|
||||
{
|
||||
var color = ShapeViewModel.Colors[_rand.Next(0, ShapeViewModel.Colors.Count)];
|
||||
Cursors.Add(new UserCursorViewModel
|
||||
{
|
||||
Name = $"User {i + 1}",
|
||||
Color = color,
|
||||
Location = new Point(_rand.Next(0, 1000), _rand.Next(0, 1000))
|
||||
});
|
||||
}
|
||||
|
||||
var ellipse = new EllipseViewModel
|
||||
{
|
||||
Location = new Point(100, 50),
|
||||
Width = 150,
|
||||
Height = 150
|
||||
};
|
||||
_shapes.Add(ellipse);
|
||||
|
||||
var rectangle = new RectangleViewModel
|
||||
{
|
||||
Location = new Point(400, 100),
|
||||
Width = 150,
|
||||
Height = 150
|
||||
};
|
||||
_shapes.Add(rectangle);
|
||||
|
||||
Connections.Add(new ConnectionViewModel(ellipse.RightConnector, rectangle.LeftConnector));
|
||||
|
||||
var ellipse2 = new EllipseViewModel
|
||||
{
|
||||
Location = new Point(100, 250),
|
||||
Width = 150,
|
||||
Height = 150
|
||||
};
|
||||
_shapes.Add(ellipse2);
|
||||
|
||||
var rectangle2 = new RectangleViewModel
|
||||
{
|
||||
Location = new Point(450, 400),
|
||||
Width = 150,
|
||||
Height = 150
|
||||
};
|
||||
_shapes.Add(rectangle2);
|
||||
|
||||
var triangle = new TriangleViewModel
|
||||
{
|
||||
Location = new Point(800, 200),
|
||||
Width = 150,
|
||||
Height = 150
|
||||
};
|
||||
_shapes.Add(triangle);
|
||||
|
||||
Connections.Add(new ConnectionViewModel(ellipse2.BottomConnector, rectangle2.TopConnector));
|
||||
Connections.Add(new ConnectionViewModel(rectangle.RightConnector, rectangle2.RightConnector));
|
||||
|
||||
SelectedShapes.Add(triangle);
|
||||
|
||||
Decorators.Add(ShapeToolbar);
|
||||
Decorators.AddRange(Cursors);
|
||||
|
||||
// Re-enable undo redo
|
||||
UndoRedo.IsEnabled = true;
|
||||
}
|
||||
|
||||
public void AddShape(ShapeViewModel shape)
|
||||
{
|
||||
var action = new DelegateAction(() => _shapes.Add(shape), () => _shapes.Remove(shape), "Add shape");
|
||||
UndoRedo.ExecuteAction(action);
|
||||
}
|
||||
|
||||
private void AddConnection(ConnectorViewModel source, ConnectorViewModel target)
|
||||
{
|
||||
var connection = new ConnectionViewModel(source, target);
|
||||
var action = new DelegateAction(() => Connections.Add(connection), () => Connections.Remove(connection), "Connect");
|
||||
UndoRedo.ExecuteAction(action);
|
||||
}
|
||||
|
||||
private void RemoveConnection(ConnectionViewModel connection)
|
||||
{
|
||||
var action = new DelegateAction(() => Connections.Remove(connection), () => Connections.Add(connection), "Disconnect");
|
||||
UndoRedo.ExecuteAction(action);
|
||||
}
|
||||
|
||||
private bool CanConnect(ConnectorViewModel? source, ConnectorViewModel? target)
|
||||
{
|
||||
return source != null
|
||||
&& target != null
|
||||
&& source != target
|
||||
&& !Connections.Contains(new ConnectionViewModel(source, target));
|
||||
}
|
||||
|
||||
public void DeleteSelection()
|
||||
{
|
||||
if (SelectedShapes.Count == 0)
|
||||
return;
|
||||
|
||||
using (UndoRedo.Batch("Delete selection"))
|
||||
{
|
||||
var selection = SelectedShapes.ToList();
|
||||
|
||||
var action = new DelegateAction(() => _shapes.RemoveRange(selection), () => _shapes.AddRange(selection), "Delete shapes");
|
||||
UndoRedo.ExecuteAction(action);
|
||||
|
||||
foreach (var shape in selection)
|
||||
{
|
||||
var connectors = new[] { shape.LeftConnector, shape.RightConnector, shape.TopConnector, shape.BottomConnector };
|
||||
var connectionsToRemove = Connections.Where(x => connectors.Contains(x.Source) || connectors.Contains(x.Target)).ToList();
|
||||
|
||||
foreach (var connection in connectionsToRemove)
|
||||
{
|
||||
RemoveConnection(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Examples/Nodify.Shapes/Canvas/ConnectionViewModel.cs
Normal file
19
Examples/Nodify.Shapes/Canvas/ConnectionViewModel.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class ConnectionViewModel : IEquatable<ConnectionViewModel>
|
||||
{
|
||||
public ConnectionViewModel(ConnectorViewModel source, ConnectorViewModel target)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public ConnectorViewModel Source { get; }
|
||||
public ConnectorViewModel Target { get; }
|
||||
|
||||
public bool Equals(ConnectionViewModel? other)
|
||||
=> other?.Source == Source && other.Target == Target;
|
||||
}
|
||||
}
|
||||
21
Examples/Nodify.Shapes/Canvas/ConnectorViewModel.cs
Normal file
21
Examples/Nodify.Shapes/Canvas/ConnectorViewModel.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class ConnectorViewModel : ObservableObject
|
||||
{
|
||||
public ConnectorViewModel(ConnectorPosition position)
|
||||
{
|
||||
Position = position;
|
||||
}
|
||||
|
||||
private Point _anchor;
|
||||
public Point Anchor
|
||||
{
|
||||
get => _anchor;
|
||||
set => SetProperty(ref _anchor, value);
|
||||
}
|
||||
|
||||
public ConnectorPosition Position { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public interface ICanvasDecorator
|
||||
{
|
||||
Point Location { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class ShapeToolbarViewModel : ObservableObject, ICanvasDecorator
|
||||
{
|
||||
private ShapeViewModel? _shape;
|
||||
public ShapeViewModel? Shape
|
||||
{
|
||||
get => _shape;
|
||||
set
|
||||
{
|
||||
var prevShape = _shape;
|
||||
if (SetProperty(ref _shape, value))
|
||||
{
|
||||
_hiddenShape = null;
|
||||
HookLocationEvents(prevShape, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ShapeViewModel? _hiddenShape;
|
||||
|
||||
private Point _location;
|
||||
public Point Location
|
||||
{
|
||||
get => _location;
|
||||
set => SetProperty(ref _location, value);
|
||||
}
|
||||
|
||||
private void HookLocationEvents(ShapeViewModel? prevShape, ShapeViewModel? newShape)
|
||||
{
|
||||
if (prevShape != null)
|
||||
prevShape.PropertyChanged -= OnLocationChanged;
|
||||
|
||||
if (newShape != null)
|
||||
{
|
||||
newShape.PropertyChanged += OnLocationChanged;
|
||||
Location = newShape.Location;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLocationChanged(object? sender, PropertyChangedEventArgs args)
|
||||
{
|
||||
if (args.PropertyName == nameof(ShapeViewModel.Location))
|
||||
Location = ((ShapeViewModel)sender!).Location;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
_hiddenShape = _shape;
|
||||
_shape = null;
|
||||
|
||||
OnPropertyChanged(nameof(Shape));
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
if (_hiddenShape != null)
|
||||
{
|
||||
_shape = _hiddenShape;
|
||||
_hiddenShape = null;
|
||||
|
||||
OnPropertyChanged(nameof(Shape));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class UserCursorViewModel : ObservableObject, ICanvasDecorator
|
||||
{
|
||||
private Point _location;
|
||||
public Point Location
|
||||
{
|
||||
get => _location;
|
||||
set => SetProperty(ref _location, value);
|
||||
}
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public Color Color { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class DrawingGesturesMappings : EditorGestures
|
||||
{
|
||||
public static readonly DrawingGesturesMappings Instance = new DrawingGesturesMappings();
|
||||
|
||||
public InputGestureRef Draw { get; }
|
||||
|
||||
public DrawingGesturesMappings()
|
||||
{
|
||||
Apply(UnboundGestureMappings.Instance);
|
||||
|
||||
Draw = new Interactivity.MouseGesture(MouseAction.LeftClick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class LockedGestureMappings : EditorGestures
|
||||
{
|
||||
public static readonly LockedGestureMappings Instance = new LockedGestureMappings();
|
||||
|
||||
public LockedGestureMappings()
|
||||
{
|
||||
Apply(UnboundGestureMappings.Instance);
|
||||
|
||||
Editor.Pan.Value = new AnyGesture(new Interactivity.MouseGesture(MouseAction.LeftClick), new Interactivity.MouseGesture(MouseAction.RightClick), new Interactivity.MouseGesture(MouseAction.MiddleClick));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Nodify.Interactivity;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class UnboundGestureMappings : EditorGestures
|
||||
{
|
||||
public static readonly UnboundGestureMappings Instance = new UnboundGestureMappings();
|
||||
|
||||
public UnboundGestureMappings()
|
||||
{
|
||||
Editor.Selection.Unbind();
|
||||
Editor.SelectAll.Unbind();
|
||||
ItemContainer.Selection.Unbind();
|
||||
Connection.Disconnect.Unbind();
|
||||
Connector.Connect.Unbind();
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Examples/Nodify.Shapes/Canvas/Shapes/EllipseViewModel.cs
Normal file
13
Examples/Nodify.Shapes/Canvas/Shapes/EllipseViewModel.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class EllipseViewModel : ShapeViewModel
|
||||
{
|
||||
public EllipseViewModel()
|
||||
{
|
||||
Color = Color.FromRgb(67, 141, 87);
|
||||
Text = "Ellipse";
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Examples/Nodify.Shapes/Canvas/Shapes/RectangleViewModel.cs
Normal file
13
Examples/Nodify.Shapes/Canvas/Shapes/RectangleViewModel.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class RectangleViewModel : ShapeViewModel
|
||||
{
|
||||
public RectangleViewModel()
|
||||
{
|
||||
Color = Color.FromRgb(63, 138, 226);
|
||||
Text = "Rectangle";
|
||||
}
|
||||
}
|
||||
}
|
||||
72
Examples/Nodify.Shapes/Canvas/Shapes/ShapeViewModel.cs
Normal file
72
Examples/Nodify.Shapes/Canvas/Shapes/ShapeViewModel.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Nodify.UndoRedo;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public abstract class ShapeViewModel : Undoable
|
||||
{
|
||||
public ShapeViewModel(IActionsHistory history) : base(history)
|
||||
{
|
||||
RecordProperty<ShapeViewModel>(x => x.Color);
|
||||
RecordProperty<ShapeViewModel>(x => x.Text);
|
||||
}
|
||||
|
||||
public ShapeViewModel() : this(ActionsHistory.Global)
|
||||
{
|
||||
}
|
||||
|
||||
public static readonly IReadOnlyList<Color> Colors = new Color[]
|
||||
{
|
||||
Color.FromRgb(207, 76, 44),
|
||||
Color.FromRgb(234, 156, 65),
|
||||
Color.FromRgb(235, 195, 71),
|
||||
Color.FromRgb(67, 141, 87),
|
||||
Color.FromRgb(63, 138, 226),
|
||||
Color.FromRgb(128, 61, 236),
|
||||
};
|
||||
|
||||
private Point _location;
|
||||
public Point Location
|
||||
{
|
||||
get => _location;
|
||||
set => SetProperty(ref _location, value);
|
||||
}
|
||||
|
||||
private double _width;
|
||||
public double Width
|
||||
{
|
||||
get => _width;
|
||||
set => SetProperty(ref _width, value);
|
||||
}
|
||||
|
||||
private double _height;
|
||||
public double Height
|
||||
{
|
||||
get => _height;
|
||||
set => SetProperty(ref _height, value);
|
||||
}
|
||||
|
||||
private Color _color;
|
||||
public Color Color
|
||||
{
|
||||
get => _color;
|
||||
set => SetProperty(ref _color, value).Then(x => OnPropertyChanged(nameof(BorderColor)));
|
||||
}
|
||||
|
||||
public Color BorderColor => Color * 1.5f;
|
||||
|
||||
private string? _text;
|
||||
public string? Text
|
||||
{
|
||||
get => _text;
|
||||
set => SetProperty(ref _text, value);
|
||||
}
|
||||
|
||||
public ConnectorViewModel LeftConnector { get; } = new ConnectorViewModel(ConnectorPosition.Left);
|
||||
public ConnectorViewModel RightConnector { get; } = new ConnectorViewModel(ConnectorPosition.Right);
|
||||
public ConnectorViewModel TopConnector { get; } = new ConnectorViewModel(ConnectorPosition.Top);
|
||||
public ConnectorViewModel BottomConnector { get; } = new ConnectorViewModel(ConnectorPosition.Bottom);
|
||||
}
|
||||
}
|
||||
13
Examples/Nodify.Shapes/Canvas/Shapes/TriangleViewModel.cs
Normal file
13
Examples/Nodify.Shapes/Canvas/Shapes/TriangleViewModel.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Nodify.Shapes.Canvas
|
||||
{
|
||||
public class TriangleViewModel : ShapeViewModel
|
||||
{
|
||||
public TriangleViewModel()
|
||||
{
|
||||
Color = Color.FromRgb(235, 195, 71);
|
||||
Text = "Triangle";
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Examples/Nodify.Shapes/Canvas/UndoRedo/MoveShapesAction.cs
Normal file
34
Examples/Nodify.Shapes/Canvas/UndoRedo/MoveShapesAction.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Nodify.UndoRedo;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Shapes.Canvas.UndoRedo
|
||||
{
|
||||
public class MoveShapesAction : IAction
|
||||
{
|
||||
private readonly IReadOnlyCollection<ShapeViewModel> _shapes;
|
||||
private readonly Vector _offset;
|
||||
|
||||
public string? Label => "Move shapes";
|
||||
|
||||
public MoveShapesAction(IReadOnlyCollection<ShapeViewModel> shapes, Vector offset)
|
||||
{
|
||||
_shapes = shapes;
|
||||
_offset = offset;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
=> ApplyOffset(_offset);
|
||||
|
||||
public void Undo()
|
||||
=> ApplyOffset(-_offset);
|
||||
|
||||
private void ApplyOffset(Vector offset)
|
||||
{
|
||||
foreach (var shape in _shapes)
|
||||
{
|
||||
shape.Location += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Examples/Nodify.Shapes/Canvas/UndoRedo/ResizeShapesAction.cs
Normal file
53
Examples/Nodify.Shapes/Canvas/UndoRedo/ResizeShapesAction.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using Nodify.UndoRedo;
|
||||
|
||||
namespace Nodify.Shapes.Canvas.UndoRedo
|
||||
{
|
||||
public class ResizeShapesAction : IAction
|
||||
{
|
||||
private readonly IReadOnlyCollection<ShapeViewModel> _resizedShapes;
|
||||
private readonly Dictionary<ShapeViewModel, Size> _initialSizes;
|
||||
private Dictionary<ShapeViewModel, Size>? _finalSizes;
|
||||
private readonly Dictionary<ShapeViewModel, Point> _initialLocations;
|
||||
private Dictionary<ShapeViewModel, Point>? _finalLocations;
|
||||
|
||||
public ResizeShapesAction(CanvasViewModel canvas)
|
||||
{
|
||||
_resizedShapes = canvas.SelectedShapes;
|
||||
_initialSizes = _resizedShapes.ToDictionary(x => x, x => new Size(x.Width, x.Height));
|
||||
_initialLocations = _resizedShapes.ToDictionary(x => x, x => x.Location);
|
||||
}
|
||||
|
||||
public string? Label => "Resize shapes";
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
_finalLocations?.ForEach(x => x.Key.Location = x.Value);
|
||||
|
||||
_finalSizes?.ForEach(x =>
|
||||
{
|
||||
x.Key.Width = x.Value.Width;
|
||||
x.Key.Height = x.Value.Height;
|
||||
});
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
_initialSizes.ForEach(x =>
|
||||
{
|
||||
x.Key.Width = x.Value.Width;
|
||||
x.Key.Height = x.Value.Height;
|
||||
});
|
||||
|
||||
_initialLocations.ForEach(x => x.Key.Location = x.Value);
|
||||
}
|
||||
|
||||
public void SaveSizes()
|
||||
{
|
||||
_finalLocations = _resizedShapes.ToDictionary(x => x, x => x.Location);
|
||||
_finalSizes = _resizedShapes.ToDictionary(x => x, x => new Size(x.Width, x.Height));
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Examples/Nodify.Shapes/Canvas/UndoRedo/SelectShapesAction.cs
Normal file
59
Examples/Nodify.Shapes/Canvas/UndoRedo/SelectShapesAction.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Nodify.UndoRedo;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nodify.Shapes.Canvas.UndoRedo
|
||||
{
|
||||
public class SelectShapesAction : IAction
|
||||
{
|
||||
public string? Label => "Select shapes";
|
||||
|
||||
private readonly IReadOnlyCollection<ShapeViewModel> _nodes;
|
||||
private readonly CanvasViewModel _canvas;
|
||||
|
||||
public SelectShapesAction(IReadOnlyCollection<ShapeViewModel> nodes, CanvasViewModel canvas)
|
||||
{
|
||||
_nodes = nodes;
|
||||
_canvas = canvas;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
_canvas.SelectedShapes.AddRange(_nodes);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
_canvas.SelectedShapes.RemoveRange(_nodes);
|
||||
}
|
||||
|
||||
public override string? ToString()
|
||||
=> Label;
|
||||
}
|
||||
|
||||
public class DeselectShapesAction : IAction
|
||||
{
|
||||
public string? Label => "Deselect shapes";
|
||||
|
||||
private readonly IReadOnlyCollection<ShapeViewModel> _nodes;
|
||||
private readonly CanvasViewModel _canvas;
|
||||
|
||||
public DeselectShapesAction(IReadOnlyCollection<ShapeViewModel> nodes, CanvasViewModel canvas)
|
||||
{
|
||||
_nodes = nodes;
|
||||
_canvas = canvas;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
_canvas.SelectedShapes.RemoveRange(_nodes);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
_canvas.SelectedShapes.AddRange(_nodes);
|
||||
}
|
||||
|
||||
public override string? ToString()
|
||||
=> Label;
|
||||
}
|
||||
}
|
||||
31
Examples/Nodify.Shapes/Controls/ResizableContainer.cs
Normal file
31
Examples/Nodify.Shapes/Controls/ResizableContainer.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Shapes.Controls
|
||||
{
|
||||
internal class ResizableContainer : ResizablePanel
|
||||
{
|
||||
public static readonly DependencyProperty GridCellSizeProperty = NodifyEditor.GridCellSizeProperty.AddOwner(typeof(ResizableContainer));
|
||||
|
||||
public uint GridCellSize
|
||||
{
|
||||
get => (uint)GetValue(GridCellSizeProperty);
|
||||
set => SetValue(GridCellSizeProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnMove(double x, double y)
|
||||
{
|
||||
// we can't use the default behavior because we are not inside a Canvas
|
||||
if (TemplatedParent is ItemContainer item)
|
||||
{
|
||||
item.Location = new Point(item.Location.X + x, item.Location.Y + y);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnProcessDelta(ref double dx, ref double dy)
|
||||
{
|
||||
// snap to grid
|
||||
dx = (int)dx / GridCellSize * GridCellSize;
|
||||
dy = (int)dy / GridCellSize * GridCellSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Examples/Nodify.Shapes/MainWindow.xaml
Normal file
35
Examples/Nodify.Shapes/MainWindow.xaml
Normal file
@@ -0,0 +1,35 @@
|
||||
<Window x:Class="Nodify.Shapes.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:o="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
|
||||
xmlns:local="clr-namespace:Nodify.Shapes"
|
||||
xmlns:canvas="clr-namespace:Nodify.Shapes.Canvas"
|
||||
mc:Ignorable="d"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
Title="Canvas"
|
||||
Height="650"
|
||||
Width="1200">
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Command="{Binding Canvas.RedoCommand}"
|
||||
Key="Y"
|
||||
Modifiers="Ctrl" />
|
||||
<KeyBinding Command="{Binding Canvas.RedoCommand}"
|
||||
Key="Z"
|
||||
Modifiers="Ctrl+Shift" />
|
||||
<KeyBinding Command="{Binding Canvas.UndoCommand}"
|
||||
Key="Z"
|
||||
Modifiers="Ctrl" />
|
||||
<KeyBinding Command="{Binding Canvas.DeleteSelectionCommand}"
|
||||
Key="Delete" />
|
||||
</Window.InputBindings>
|
||||
|
||||
<Window.DataContext>
|
||||
<local:AppShellViewModel />
|
||||
</Window.DataContext>
|
||||
|
||||
<Grid>
|
||||
<canvas:CanvasView DataContext="{Binding Canvas}" />
|
||||
</Grid>
|
||||
</Window>
|
||||
28
Examples/Nodify.Shapes/MainWindow.xaml.cs
Normal file
28
Examples/Nodify.Shapes/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Shapes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
PendingConnection.HotKeysDisplayMode = HotKeysDisplayMode.All;
|
||||
|
||||
EventManager.RegisterClassHandler(
|
||||
typeof(UIElement),
|
||||
Keyboard.PreviewGotKeyboardFocusEvent,
|
||||
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
|
||||
}
|
||||
|
||||
private void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||
{
|
||||
Title = e.NewFocus.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Examples/Nodify.Shapes/Nodify.Shapes.csproj
Normal file
19
Examples/Nodify.Shapes/Nodify.Shapes.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFrameworks>net9-windows;net8-windows;net6-windows;net5-windows;netcoreapp3.1;net48;net472</TargetFrameworks>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net472' OR '$(TargetFramework)'=='net48'">
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Nodify\Nodify.csproj" />
|
||||
<ProjectReference Include="..\Nodify.Shared\Nodify.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user