Преглед на файлове

Layer control mask manipulation

Equbuxu преди 3 години
родител
ревизия
3129cd60fc

+ 33 - 0
src/PixiEditor/Helpers/Converters/BoolToBrushConverter.cs

@@ -0,0 +1,33 @@
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.Converters;
+internal class BoolToBrushConverter : IMultiValueConverter
+{
+    public Brush FalseBrush { get; set; } = Brushes.Black;
+    public Brush TrueBrush { get; set; } = Brushes.White;
+
+    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (values.Length == 1)
+        {
+            if (values[0] is not bool conv)
+                return DependencyProperty.UnsetValue;
+            return conv ? TrueBrush : FalseBrush;
+        }
+        else if (values.Length == 2)
+        {
+            if (values[0] is not bool conv || values[1] is not bool conv2)
+                return DependencyProperty.UnsetValue;
+            return (conv || !conv2) ? TrueBrush : FalseBrush;
+        }
+        return DependencyProperty.UnsetValue;
+    }
+
+    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 2 - 1
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -1,7 +1,6 @@
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using ChunkyImageLib.DataHolders;
-using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
@@ -229,6 +228,8 @@ internal class DocumentUpdater
         }
         memberVm.SetHasMask(info.HasMask);
         memberVm.RaisePropertyChanged(nameof(memberVm.MaskPreviewBitmap));
+        if (!info.HasMask)
+            memberVm.ShouldDrawOnMask = false;
     }
 
     private void ProcessRefreshViewport(RefreshViewport_PassthroughAction info)

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

@@ -40,8 +40,8 @@ internal class DocumentViewModel : NotifyableObject
     }
 
     private string? fullFilePath = null;
-    public string? FullFilePath 
-    { 
+    public string? FullFilePath
+    {
         get => fullFilePath;
         set
         {
@@ -290,6 +290,20 @@ internal class DocumentViewModel : NotifyableObject
         Helpers.ActionAccumulator.AddFinishedActions(new ReplaceColor_Action(oldColor, newColor));
     }
 
+    public void CreateMask(Guid memberGuid)
+    {
+        if (Helpers.ChangeController.IsChangeActive)
+            return;
+        Helpers.ActionAccumulator.AddFinishedActions(new CreateStructureMemberMask_Action(memberGuid));
+    }
+
+    public void DeleteMask(Guid memberGuid)
+    {
+        if (Helpers.ChangeController.IsChangeActive)
+            return;
+        Helpers.ActionAccumulator.AddFinishedActions(new DeleteStructureMemberMask_Action(memberGuid));
+    }
+
     public void SetSelectedMember(Guid memberGuid) => Helpers.ActionAccumulator.AddActions(new SetSelectedMember_PassthroughAction(memberGuid));
 
     public void AddSoftSelectedMember(Guid memberGuid) => Helpers.ActionAccumulator.AddActions(new AddSoftSelectedMember_PassthroughAction(memberGuid));
@@ -303,7 +317,7 @@ internal class DocumentViewModel : NotifyableObject
     public void UsePenTool() => Helpers.ChangeController.TryStartUpdateableChange<PenToolExecutor>();
 
     public void UseRectangleTool() => Helpers.ChangeController.TryStartUpdateableChange<RectangleToolExecutor>();
-    
+
     public void UseEllipseTool() => Helpers.ChangeController.TryStartUpdateableChange<EllipseToolExecutor>();
 
     public void UseLineTool() => Helpers.ChangeController.TryStartUpdateableChange<LineToolExecutor>();

+ 6 - 3
src/PixiEditor/ViewModels/SubViewModels/Document/LayerViewModel.cs

@@ -1,5 +1,4 @@
-using PixiEditor.ChangeableDocument.Actions.Generated;
-using PixiEditor.Models.DocumentModels;
+using PixiEditor.Models.DocumentModels;
 
 namespace PixiEditor.ViewModels.SubViewModels.Document;
 #nullable enable
@@ -14,7 +13,11 @@ internal class LayerViewModel : StructureMemberViewModel
     public bool LockTransparencyBindable
     {
         get => lockTransparency;
-        set => Helpers.ActionAccumulator.AddFinishedActions(new LayerLockTransparency_Action(GuidValue, value));
+        set
+        {
+            if (!Document.UpdateableChangeActive)
+                Helpers.ActionAccumulator.AddFinishedActions(new LayerLockTransparency_Action(GuidValue, value));
+        }
     }
     public LayerViewModel(DocumentViewModel doc, DocumentHelpers helpers, Guid guidValue) : base(doc, helpers, guidValue)
     {

+ 33 - 5
src/PixiEditor/ViewModels/SubViewModels/Document/StructureMemberViewModel.cs

@@ -24,7 +24,11 @@ internal abstract class StructureMemberViewModel : INotifyPropertyChanged
     public string NameBindable
     {
         get => name;
-        set => Helpers.ActionAccumulator.AddFinishedActions(new StructureMemberName_Action(GuidValue, value));
+        set
+        {
+            if (!Document.UpdateableChangeActive)
+                Helpers.ActionAccumulator.AddFinishedActions(new StructureMemberName_Action(GuidValue, value));
+        }
     }
 
     private bool isVisible;
@@ -36,7 +40,11 @@ internal abstract class StructureMemberViewModel : INotifyPropertyChanged
     public bool IsVisibleBindable
     {
         get => isVisible;
-        set => Helpers.ActionAccumulator.AddFinishedActions(new StructureMemberIsVisible_Action(value, GuidValue));
+        set
+        {
+            if (!Document.UpdateableChangeActive)
+                Helpers.ActionAccumulator.AddFinishedActions(new StructureMemberIsVisible_Action(value, GuidValue));
+        }
     }
 
     private bool maskIsVisible;
@@ -60,7 +68,11 @@ internal abstract class StructureMemberViewModel : INotifyPropertyChanged
     public BlendMode BlendModeBindable
     {
         get => blendMode;
-        set => Helpers.ActionAccumulator.AddFinishedActions(new StructureMemberBlendMode_Action(value, GuidValue));
+        set
+        {
+            if (!Document.UpdateableChangeActive)
+                Helpers.ActionAccumulator.AddFinishedActions(new StructureMemberBlendMode_Action(value, GuidValue));
+        }
     }
 
     private bool clipToMemberBelowEnabled;
@@ -72,7 +84,11 @@ internal abstract class StructureMemberViewModel : INotifyPropertyChanged
     public bool ClipToMemberBelowEnabledBindable
     {
         get => clipToMemberBelowEnabled;
-        set => Helpers.ActionAccumulator.AddFinishedActions(new StructureMemberClipToMemberBelow_Action(value, GuidValue));
+        set
+        {
+            if (!Document.UpdateableChangeActive)
+                Helpers.ActionAccumulator.AddFinishedActions(new StructureMemberClipToMemberBelow_Action(value, GuidValue));
+        }
     }
 
     private bool hasMask;
@@ -111,7 +127,19 @@ internal abstract class StructureMemberViewModel : INotifyPropertyChanged
     }
 
     public StructureMemberSelectionType Selection { get; set; }
-    public bool ShouldDrawOnMask { get; set; }
+
+    private bool shouldDrawOnMask = false;
+    public bool ShouldDrawOnMask
+    {
+        get => shouldDrawOnMask;
+        set
+        {
+            if (value == shouldDrawOnMask)
+                return;
+            shouldDrawOnMask = value;
+            PropertyChanged?.Invoke(this, new(nameof(ShouldDrawOnMask)));
+        }
+    }
 
     public const int PreviewSize = 48;
     public WriteableBitmap PreviewBitmap { get; set; }

+ 50 - 1
src/PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -1,6 +1,5 @@
 using System.Windows.Input;
 using PixiEditor.ChangeableDocument.Enums;
-using PixiEditor.Helpers;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Enums;
 using PixiEditor.ViewModels.SubViewModels.Document;
@@ -190,6 +189,56 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         }
     }
 
+    [Evaluator.CanExecute("PixiEditor.Layer.ActiveLayerHasMask")]
+    public bool ActiveMemberHasMask() => Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember?.HasMaskBindable ?? false;
+
+    [Evaluator.CanExecute("PixiEditor.Layer.ActiveLayerHasNoMask")]
+    public bool ActiveLayerHasNoMask() => !ActiveMemberHasMask();
+
+    private bool? DoesMaskVisibilityEqual(bool value)
+    {
+        var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
+        if (member is null || !member.HasMaskBindable)
+            return false;
+        return member.MaskIsVisibleBindable == value;
+    }
+
+    [Evaluator.CanExecute("PixiEditor.Layer.CanEnableMask")]
+    public bool CanEnableMask() => DoesMaskVisibilityEqual(false) ?? false;
+
+    [Evaluator.CanExecute("PixiEditor.Layer.CanDisableMask")]
+    public bool CanDisableMask() => DoesMaskVisibilityEqual(true) ?? false;
+
+    [Command.Basic("PixiEditor.Layer.EnableMask", "Enable mask", "Enable mask", CanExecute = "PixiEditor.Layer.CanEnableMask", Parameter = true)]
+    [Command.Basic("PixiEditor.Layer.DisableMask", "Disable mask", "Disable mask", CanExecute = "PixiEditor.Layer.CanDisableMask", Parameter = false)]
+    public void ToggleMaskVisibility(bool newVisibility)
+    {
+        var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
+        if (member is null || !member.HasMaskBindable || member.MaskIsVisibleBindable == newVisibility)
+            return;
+        member.MaskIsVisibleBindable = newVisibility;
+    }
+
+    [Command.Basic("PixiEditor.Layer.CreateMask", "Create mask", "Create mask", CanExecute = "PixiEditor.Layer.ActiveLayerHasNoMask")]
+    public void CreateMask(object parameter)
+    {
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        var member = doc?.SelectedStructureMember;
+        if (member is null || member.HasMaskBindable)
+            return;
+        doc!.CreateMask(member.GuidValue);
+    }
+
+    [Command.Basic("PixiEditor.Layer.DeleteMask", "Delete mask", "Delete mask", CanExecute = "PixiEditor.Layer.ActiveLayerHasMask")]
+    public void DeleteMask(object parameter)
+    {
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        var member = doc?.SelectedStructureMember;
+        if (member is null || !member.HasMaskBindable)
+            return;
+        doc!.DeleteMask(member.GuidValue);
+    }
+
     [Evaluator.CanExecute("PixiEditor.Layer.HasMemberAbove")]
     public bool HasMemberAbove(object property) => HasSelectedMember(true);
     [Evaluator.CanExecute("PixiEditor.Layer.HasMemberBelow")]

+ 37 - 8
src/PixiEditor/Views/UserControls/Layers/LayerControl.xaml

@@ -16,6 +16,10 @@
              Focusable="True"
              d:DesignHeight="35" d:DesignWidth="250" Name="uc"
              MouseLeave="LayerItem_OnMouseLeave" MouseEnter="LayerItem_OnMouseEnter">
+    <UserControl.Resources>
+        <conv:BoolToBrushConverter x:Key="LayerBorderConverter" FalseBrush="White" TrueBrush="Black"/>
+        <conv:BoolToBrushConverter x:Key="MaskBorderConverter" FalseBrush="Black" TrueBrush="White"/>
+    </UserControl.Resources>
     <Border BorderThickness="0 0 0 0.5" BorderBrush="Gray" MinWidth="60" Focusable="True">
         <Border.Background>
             <Binding ElementName="uc" Path="Layer.Selection">
@@ -39,7 +43,7 @@
             <Grid AllowDrop="True" DragEnter="Grid_DragEnter" Drop="Grid_Drop_Top" DragLeave="Grid_DragLeave" Grid.Row="0" Grid.ColumnSpan="3" Background="Transparent"/>
             <Grid Grid.Row="1" Grid.RowSpan="3" Margin="0,-10,0,0" VerticalAlignment="Center" AllowDrop="False">
                 <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="22"/>
+                    <ColumnDefinition Width="24"/>
                     <ColumnDefinition Width="*"/>
                 </Grid.ColumnDefinitions>
                 <CheckBox Style="{StaticResource ImageCheckBox}" VerticalAlignment="Center"
@@ -48,22 +52,42 @@
                       Grid.Column="0" Height="16" />
                 <StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Left">
                     <Rectangle Width="{Binding Path=(helpers:TreeViewItemHelper.Indent).Value, Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}}" Fill="Transparent" StrokeThickness="0"/>
-                    <Border Width="30" Height="30" BorderThickness="1" BorderBrush="Black" Background="{StaticResource MainColor}">
+                    <Border 
+                        Width="30" Height="30" 
+                        BorderThickness="1"
+                        Background="{StaticResource MainColor}"
+                        MouseDown="LayerMouseDown">
+                        <Border.BorderBrush>
+                            <MultiBinding Converter="{StaticResource LayerBorderConverter}">
+                                <Binding ElementName="uc" Path="Layer.ShouldDrawOnMask"/>
+                                <Binding ElementName="uc" Path="Layer.HasMaskBindable"/>
+                            </MultiBinding>
+                        </Border.BorderBrush>
                         <Image Source="{Binding Layer.PreviewBitmap,ElementName=uc}" Stretch="Uniform" Width="20" Height="20"
-                       RenderOptions.BitmapScalingMode="NearestNeighbor"/>
+                           RenderOptions.BitmapScalingMode="NearestNeighbor" IsHitTestVisible="False"/>
                     </Border>
-                    <Border Width="30" Height="30" BorderThickness="1" BorderBrush="Black" Background="{StaticResource MainColor}"
-                            Visibility="{Binding Layer.MaskIsVisibleBindable, ElementName=uc, Converter={conv:BoolToVisibilityConverter}}">
-                        <Grid>
+                    <Border 
+                        Width="30" Height="30" 
+                        BorderThickness="1" 
+                        Background="{StaticResource MainColor}"
+                        Margin="3,0,0,0"
+                        Visibility="{Binding Layer.HasMaskBindable, ElementName=uc, Converter={conv:BoolToVisibilityConverter}}"
+                        MouseDown="MaskMouseDown">
+                        <Border.BorderBrush>
+                            <MultiBinding Converter="{StaticResource MaskBorderConverter}">
+                                <Binding ElementName="uc" Path="Layer.ShouldDrawOnMask"/>
+                            </MultiBinding>
+                        </Border.BorderBrush>
+                        <Grid IsHitTestVisible="False">
                             <Image Source="{Binding Layer.MaskPreviewBitmap,ElementName=uc}" Stretch="Uniform" Width="20" Height="20"
-                           RenderOptions.BitmapScalingMode="NearestNeighbor"/>
+                           RenderOptions.BitmapScalingMode="NearestNeighbor" IsHitTestVisible="False"/>
                             <Path 
                                 Data="M 2 0 L 10 8 L 18 0 L 20 2 L 12 10 L 20 18 L 18 20 L 10 12 L 2 20 L 0 18 L 8 10 L 0 2 Z" 
                                 Fill="{StaticResource PixiRed}" HorizontalAlignment="Center" VerticalAlignment="Center"
                                 Visibility="{Binding Layer.MaskIsVisibleBindable, ElementName=uc, Converter={InverseBoolToVisibilityConverter}}"/>
                         </Grid>
                     </Border>
-                    <StackPanel Margin="3,0,0,0">
+                    <StackPanel Margin="3,0,5,0">
                         <StackPanel Orientation="Horizontal">
                             <TextBlock d:Text="100" Foreground="White">
                                 <TextBlock.Text>
@@ -106,6 +130,11 @@
                 <MenuItem Header="Move upwards" Command="{cmds:Command PixiEditor.Layer.MoveSelectedMemberUpwards}"/>
                 <MenuItem Header="Move downwards" Command="{cmds:Command PixiEditor.Layer.MoveSelectedMemberDownwards}"/>
                 <Separator/>
+                <MenuItem Header="Create mask" Command="{cmds:Command PixiEditor.Layer.CreateMask}"/>
+                <MenuItem Header="Delete mask" Command="{cmds:Command PixiEditor.Layer.DeleteMask}"/>
+                <MenuItem Header="Enable mask" Command="{cmds:Command PixiEditor.Layer.EnableMask}"/>
+                <MenuItem Header="Disable mask" Command="{cmds:Command PixiEditor.Layer.DisableMask}"/>
+                <Separator/>
                 <MenuItem Header="Merge selected" Command="{cmds:Command PixiEditor.Layer.MergeSelected}"/>
                 <MenuItem Header="Merge with above" Command="{cmds:Command PixiEditor.Layer.MergeWithAbove}"/>
                 <MenuItem Header="Merge with below" Command="{cmds:Command PixiEditor.Layer.MergeWithBelow}"/>

+ 12 - 0
src/PixiEditor/Views/UserControls/Layers/LayerControl.xaml.cs

@@ -136,4 +136,16 @@ internal partial class LayerControl : UserControl
     {
         editableTextBlock.EnableEditing();
     }
+
+    private void MaskMouseDown(object sender, MouseButtonEventArgs e)
+    {
+        if (Layer is not null)
+            Layer.ShouldDrawOnMask = true;
+    }
+
+    private void LayerMouseDown(object sender, MouseButtonEventArgs e)
+    {
+        if (Layer is not null)
+            Layer.ShouldDrawOnMask = false;
+    }
 }