Equbuxu 3 years ago
parent
commit
ea9222a04d

+ 21 - 0
src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs

@@ -1,8 +1,10 @@
 using System.Windows.Input;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 using PixiEditor.Models.Enums;
 using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.Views.UserControls.SymmetryOverlay;
 
 namespace PixiEditor.Models.DocumentModels;
 #nullable enable
@@ -13,6 +15,8 @@ internal class ChangeExecutionController
     public VecI LastPixelPosition => lastPixelPos;
     public VecD LastPrecisePosition => lastPrecisePos;
     public float LastOpacityValue = 1f;
+    public int LastHorizontalSymmetryAxisPosition { get; private set; }
+    public int LastVerticalSymmetryAxisPosition { get; private set; }
     public bool IsChangeActive => currentSession is not null;
 
     private readonly DocumentViewModel document;
@@ -123,6 +127,23 @@ internal class ChangeExecutionController
     }
     public void OpacitySliderDragEndedInlet() => currentSession?.OnOpacitySliderDragEnded();
 
+    public void SymmetryDragStartedInlet(SymmetryAxisDirection dir) => currentSession?.OnSymmetryDragStarted(dir);
+    public void SymmetryDraggedInlet(SymmetryAxisDragInfo info)
+    {
+        switch (info.Direction)
+        {
+            case SymmetryAxisDirection.Horizontal:
+                LastHorizontalSymmetryAxisPosition = info.NewPosition;
+                break;
+            case SymmetryAxisDirection.Vertical:
+                LastVerticalSymmetryAxisPosition = info.NewPosition;
+                break;
+        }
+        currentSession?.OnSymmetryDragged(info);
+    }
+
+    public void SymmetryDragEndedInlet(SymmetryAxisDirection dir) => currentSession?.OnSymmetryDragEnded(dir);
+
     public void LeftMouseButtonDownInlet(VecD canvasPos)
     {
         //update internal state

+ 5 - 0
src/PixiEditor/Models/DocumentModels/Public/DocumentEventsModule.cs

@@ -1,7 +1,9 @@
 using System.Windows.Input;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Models.Events;
 using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.Views.UserControls.SymmetryOverlay;
 
 namespace PixiEditor.Models.DocumentModels.Public;
 internal class DocumentEventsModule
@@ -40,4 +42,7 @@ internal class DocumentEventsModule
     public void OnOpacitySliderDragged(float newValue) => Internals.ChangeController.OpacitySliderDraggedInlet(newValue);
     public void OnOpacitySliderDragEnded() => Internals.ChangeController.OpacitySliderDragEndedInlet();
     public void OnApplyTransform() => Internals.ChangeController.TransformAppliedInlet();
+    public void OnSymmetryDragStarted(SymmetryAxisDirection dir) => Internals.ChangeController.SymmetryDragStartedInlet(dir);
+    public void OnSymmetryDragged(SymmetryAxisDragInfo info) => Internals.ChangeController.SymmetryDraggedInlet(info);
+    public void OnSymmetryDragEnded(SymmetryAxisDirection dir) => Internals.ChangeController.SymmetryDragEndedInlet(dir);
 }

+ 4 - 1
src/PixiEditor/Models/DocumentModels/Public/DocumentToolsModule.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Models.DocumentModels;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 using PixiEditor.Models.Enums;
 using PixiEditor.ViewModels.SubViewModels.Document;
@@ -15,6 +16,8 @@ internal class DocumentToolsModule
         this.Internals = internals;
     }
 
+    public void UseSymmetry(SymmetryAxisDirection dir) => Internals.ChangeController.TryStartExecutor(new SymmetryExecutor(dir));
+
     public void UseOpacitySlider() => Internals.ChangeController.TryStartExecutor<StructureMemberOpacityExecutor>();
 
     public void UseShiftLayerTool() => Internals.ChangeController.TryStartExecutor<ShiftLayerExecutor>();

+ 49 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SymmetryExecutor.cs

@@ -0,0 +1,49 @@
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Models.Enums;
+using PixiEditor.Views.UserControls.SymmetryOverlay;
+
+namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
+internal class SymmetryExecutor : UpdateableChangeExecutor
+{
+    private readonly SymmetryAxisDirection dir;
+
+    public SymmetryExecutor(SymmetryAxisDirection dir)
+    {
+        this.dir = dir;
+    }
+
+    public override ExecutionState Start()
+    {
+        if (!document.HorizontalSymmetryAxisEnabledBindable && dir == SymmetryAxisDirection.Horizontal ||
+            !document.VerticalSymmetryAxisEnabledBindable && dir == SymmetryAxisDirection.Vertical)
+            return ExecutionState.Error;
+
+        int lastPos = dir switch
+        {
+            SymmetryAxisDirection.Horizontal => controller.LastHorizontalSymmetryAxisPosition,
+            SymmetryAxisDirection.Vertical => controller.LastVerticalSymmetryAxisPosition,
+            _ => throw new NotImplementedException(),
+        };
+        internals.ActionAccumulator.AddActions(new SymmetryAxisPosition_Action(dir, lastPos));
+
+        return ExecutionState.Success;
+    }
+
+    public override void OnSymmetryDragged(SymmetryAxisDragInfo info)
+    {
+        if (info.Direction != dir)
+            return;
+        internals.ActionAccumulator.AddActions(new SymmetryAxisPosition_Action(dir, info.NewPosition));
+    }
+
+    public override void OnSymmetryDragEnded(SymmetryAxisDirection dir)
+    {
+        internals.ActionAccumulator.AddFinishedActions(new EndSymmetryAxisPosition_Action());
+        onEnded!(this);
+    }
+
+    public override void ForceStop()
+    {
+        internals.ActionAccumulator.AddFinishedActions(new EndSymmetryAxisPosition_Action());
+    }
+}

+ 5 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/UpdateableChangeExecutor.cs

@@ -1,7 +1,9 @@
 using System.Windows.Input;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Models.Enums;
 using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.Views.UserControls.SymmetryOverlay;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 #nullable enable
@@ -36,6 +38,9 @@ internal abstract class UpdateableChangeExecutor
     public virtual void OnOpacitySliderDragStarted() { }
     public virtual void OnOpacitySliderDragged(float newValue) { }
     public virtual void OnOpacitySliderDragEnded() { }
+    public virtual void OnSymmetryDragStarted(SymmetryAxisDirection dir) { }
+    public virtual void OnSymmetryDragged(SymmetryAxisDragInfo info) { }
+    public virtual void OnSymmetryDragEnded(SymmetryAxisDirection dir) { }
     public virtual void OnConvertedKeyDown(Key key) { }
     public virtual void OnConvertedKeyUp(Key key) { }
     public virtual void OnTransformMoved(ShapeCorners corners) { }

+ 41 - 1
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentManagerViewModel.cs

@@ -1,11 +1,11 @@
 using System.Collections.ObjectModel;
 using System.Windows.Input;
-using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Events;
 using PixiEditor.ViewModels.SubViewModels.Tools;
+using PixiEditor.Views.UserControls.SymmetryOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Document;
 #nullable enable
@@ -101,6 +101,46 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>
         
     }
 */
+    [Command.Basic("PixiEditor.Document.ToggleVerticalSymmetryAxis", "Toggle vertical symmetry axis", "Toggle vertical symmetry axis", CanExecute = "PixiEditor.HasDocument")]
+    public void ToggleVerticalSymmetryAxis()
+    {
+        if (ActiveDocument is null)
+            return;
+        ActiveDocument.VerticalSymmetryAxisEnabledBindable ^= true;
+    }
+
+    [Command.Basic("PixiEditor.Document.ToggleHorizontalSymmetryAxis", "Toggle horizontal symmetry axis", "Toggle horizontal symmetry axis", CanExecute = "PixiEditor.HasDocument")]
+    public void ToggleHorizontalSymmetryAxis()
+    {
+        if (ActiveDocument is null)
+            return;
+        ActiveDocument.HorizontalSymmetryAxisEnabledBindable ^= true;
+    }
+
+    [Command.Internal("PixiEditor.Document.DragSymmetry", CanExecute = "PixiEditor.HasDocument")]
+    public void DragSymmetry(SymmetryAxisDragInfo info)
+    {
+        if (ActiveDocument is null)
+            return;
+        ActiveDocument.EventInlet.OnSymmetryDragged(info);
+    }
+
+    [Command.Internal("PixiEditor.Document.StartDragSymmetry", CanExecute = "PixiEditor.HasDocument")]
+    public void StartDragSymmetry(SymmetryAxisDirection dir)
+    {
+        if (ActiveDocument is null)
+            return;
+        ActiveDocument.EventInlet.OnSymmetryDragStarted(dir);
+        ActiveDocument.Tools.UseSymmetry(dir);
+    }
+
+    [Command.Internal("PixiEditor.Document.EndDragSymmetry", CanExecute = "PixiEditor.HasDocument")]
+    public void EndDragSymmetry(SymmetryAxisDirection dir)
+    {
+        if (ActiveDocument is null)
+            return;
+        ActiveDocument.EventInlet.OnSymmetryDragEnded(dir);
+    }
 
     [Command.Basic("PixiEditor.Document.DeletePixels", "Delete pixels", "Delete selected pixels", CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.Delete, IconPath = "Tools/EraserImage.png")]
     public void DeletePixels()

+ 17 - 9
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -66,14 +66,22 @@ internal class DocumentViewModel : NotifyableObject
     public bool HorizontalSymmetryAxisEnabledBindable
     {
         get => horizontalSymmetryAxisEnabled;
-        set => Internals.ActionAccumulator.AddFinishedActions(new SymmetryAxisState_Action(SymmetryAxisDirection.Horizontal, value));
+        set
+        {
+            if (!Internals.ChangeController.IsChangeActive)
+                Internals.ActionAccumulator.AddFinishedActions(new SymmetryAxisState_Action(SymmetryAxisDirection.Horizontal, value));
+        }
     }
 
     private bool verticalSymmetryAxisEnabled;
     public bool VerticalSymmetryAxisEnabledBindable
     {
         get => verticalSymmetryAxisEnabled;
-        set => Internals.ActionAccumulator.AddFinishedActions(new SymmetryAxisState_Action(SymmetryAxisDirection.Vertical, value));
+        set
+        {
+            if (!Internals.ChangeController.IsChangeActive)
+                Internals.ActionAccumulator.AddFinishedActions(new SymmetryAxisState_Action(SymmetryAxisDirection.Vertical, value));
+        }
     }
 
     private VecI size = new VecI(64, 64);
@@ -179,7 +187,7 @@ internal class DocumentViewModel : NotifyableObject
         var acc = viewModel.Internals.ActionAccumulator;
 
         AddMembers(viewModel.StructureRoot.GuidValue, builderInstance.Children);
-        
+
         acc.AddFinishedActions(new DeleteRecordedChanges_Action());
 
         return viewModel;
@@ -190,7 +198,7 @@ internal class DocumentViewModel : NotifyableObject
                 new CreateStructureMember_Action(parentGuid, member.GuidValue, 0, member is DocumentViewModelBuilder.LayerBuilder ? StructureMemberType.Layer : StructureMemberType.Folder),
                 new StructureMemberName_Action(member.GuidValue, member.Name)
             );
-            
+
             if (!member.IsVisible)
                 acc.AddActions(new StructureMemberIsVisible_Action(member.IsVisible, member.GuidValue));
 
@@ -202,17 +210,17 @@ internal class DocumentViewModel : NotifyableObject
             if (member.HasMask)
             {
                 var maskSurface = member.Mask.Surface.Surface;
-                
+
                 acc.AddActions(new CreateStructureMemberMask_Action(member.GuidValue));
-                
+
                 if (!member.Mask.IsVisible)
                     acc.AddActions(new StructureMemberMaskIsVisible_Action(member.Mask.IsVisible, member.GuidValue));
-                
+
                 PasteImage(member.GuidValue, member.Mask.Surface, maskSurface.Size.X, maskSurface.Size.Y, 0, 0, true);
             }
-            
+
             acc.AddFinishedActions();
-            
+
             if (member is DocumentViewModelBuilder.FolderBuilder { Children: not null } folder)
             {
                 AddMembers(member.GuidValue, folder.Children);

+ 15 - 4
src/PixiEditor/Views/MainWindow.xaml

@@ -236,15 +236,26 @@
                             Header="Resize _Image..."
                             cmds:Menu.Command="PixiEditor.Document.ResizeDocument" />
                         <MenuItem
-                            Header="_Resize Canvas..."
+                            Header="Resize _Canvas..."
                             cmds:Menu.Command="PixiEditor.Document.ResizeCanvas" />
+                        <Separator />
                         <MenuItem
-                            Header="_Clip Canvas"
+                            Header="Cli_p Canvas"
                             cmds:Menu.Command="PixiEditor.Document.ClipCanvas" />
-                        <Separator />
                         <MenuItem
-                            Header="_Center Content"
+                            Header="Cente_r Content"
                             cmds:Menu.Command="PixiEditor.Document.CenterContent" />
+                        <Separator />
+                        <MenuItem
+                            IsCheckable="True"
+                            IsEnabled="{Binding DocumentManagerSubViewModel.ActiveDocument, Source={vm:MainVM}, Converter={converters:NotNullToBoolConverter}}"
+                            IsChecked="{Binding DocumentManagerSubViewModel.ActiveDocument.HorizontalSymmetryAxisEnabledBindable}"
+                            Header="_Horizontal Line Symmetry"/>
+                        <MenuItem
+                            IsCheckable="True"
+                            IsEnabled="{Binding DocumentManagerSubViewModel.ActiveDocument, Source={vm:MainVM}, Converter={converters:NotNullToBoolConverter}}"
+                            IsChecked="{Binding DocumentManagerSubViewModel.ActiveDocument.VerticalSymmetryAxisEnabledBindable}"
+                            Header="_Vertical Line Symmetry"/>
                         <!--<Separator/>
                     <MenuItem Header="_Rotate to right 90&#186;" Command="{Binding DocumentSubViewModel.RotateToRightCommand}">
                         <MenuItem.CommandParameter>

+ 15 - 0
src/PixiEditor/Views/UserControls/SymmetryOverlay/SymmetryOverlay.cs

@@ -77,6 +77,15 @@ internal class SymmetryOverlay : Control
         set => SetValue(DragEndCommandProperty, value);
     }
 
+    public static readonly DependencyProperty DragStartCommandProperty =
+        DependencyProperty.Register(nameof(DragStartCommand), typeof(ICommand), typeof(SymmetryOverlay), new(null));
+
+    public ICommand? DragStartCommand
+    {
+        get => (ICommand?)GetValue(DragStartCommandProperty);
+        set => SetValue(DragStartCommandProperty, value);
+    }
+
     private const double HandleSize = 16;
     private PathGeometry handleGeometry = new()
     {
@@ -163,6 +172,7 @@ internal class SymmetryOverlay : Control
         capturedDirection = dir.Value;
         CaptureMouse();
         e.Handled = true;
+        CallSymmetryDragStartCommand(dir.Value);
     }
 
     private void CallSymmetryDragCommand(SymmetryAxisDirection direction, int position)
@@ -176,6 +186,11 @@ internal class SymmetryOverlay : Control
         if (DragEndCommand is not null && DragEndCommand.CanExecute(direction))
             DragEndCommand.Execute(direction);
     }
+    private void CallSymmetryDragStartCommand(SymmetryAxisDirection direction)
+    {
+        if (DragStartCommand is not null && DragStartCommand.CanExecute(direction))
+            DragStartCommand.Execute(direction);
+    }
 
     protected override void OnMouseUp(MouseButtonEventArgs e)
     {

+ 3 - 2
src/PixiEditor/Views/UserControls/Viewport.xaml

@@ -108,8 +108,9 @@
                         VerticalAxisVisible="{Binding Document.VerticalSymmetryAxisEnabledBindable}"
                         HorizontalAxisY="{Binding Document.HorizontalSymmetryAxisYBindable, Mode=OneWay}"
                         VerticalAxisX="{Binding Document.VerticalSymmetryAxisXBindable, Mode=OneWay}"
-                        DragCommand="{Binding Document.DragSymmetryCommand}"
-                        DragEndCommand="{Binding Document.EndDragSymmetryCommand}" />
+                        DragCommand="{cmds:Command PixiEditor.Document.DragSymmetry, UseProvided=True}"
+                        DragEndCommand="{cmds:Command PixiEditor.Document.EndDragSymmetry, UseProvided=True}" 
+                        DragStartCommand="{cmds:Command PixiEditor.Document.StartDragSymmetry, UseProvided=True}" />
                     <uc:SelectionOverlay
                         ShowFill="{Binding ToolsSubViewModel.ActiveTool, Source={vm:MainVM}, Converter={StaticResource IsSelectToolConverter}}"
                         Path="{Binding Document.SelectionPathBindable}"