Browse Source

Added template layers

CPKreuz 4 năm trước cách đây
mục cha
commit
c16bb412c8

+ 6 - 0
PixiEditor/Models/Controllers/BitmapOperationsUtility.cs

@@ -114,6 +114,12 @@ namespace PixiEditor.Models.Controllers
                 for (int i = 0; i < modifiedLayers.Length; i++)
                 {
                     Layer layer = Manager.ActiveDocument.Layers.First(x => x.LayerGuid == modifiedLayers[i].LayerGuid);
+
+                    if (layer is TemplateLayer)
+                    {
+                        continue;
+                    }
+
                     oldPixelsValues[i] = ApplyToLayer(layer, modifiedLayers[i]);
 
                     BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(

+ 19 - 1
PixiEditor/Models/DataHolders/Document/Document.Layers.cs

@@ -40,7 +40,7 @@ namespace PixiEditor.Models.DataHolders
                 ActiveLayer.IsActive = false;
             }
 
-            if (Layers.Any(x => x.IsActive))
+            if (Layers.Where(x => x is not TemplateLayer).Any(x => x.IsActive))
             {
                 var guids = Layers.Where(x => x.IsActive).Select(y => y.LayerGuid);
                 guids.ToList().ForEach(x => Layers.First(layer => layer.LayerGuid == x).IsActive = false);
@@ -100,6 +100,24 @@ namespace PixiEditor.Models.DataHolders
             LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
         }
 
+        public void AddNewLayer(Layer layer)
+        {
+            Layers.Add(layer);
+
+            if (Layers.Count > 1)
+            {
+                StorageBasedChange storageChange = new StorageBasedChange(this, new[] { Layers[^1] }, false);
+                UndoManager.AddUndoChange(
+                    storageChange.ToChange(
+                        RemoveLayerProcess,
+                        new object[] { Layers[^1].LayerGuid },
+                        RestoreLayersProcess,
+                        "Add layer"));
+            }
+
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
+        }
+
         public void SetNextLayerAsActive(int lastLayerIndex)
         {
             if (Layers.Count > 0)

+ 6 - 0
PixiEditor/Models/DataHolders/Document/Document.Operations.cs

@@ -5,6 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Windows;
 using PixiEditor.Models.Enums;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Undo;
 
 namespace PixiEditor.Models.DataHolders
@@ -94,6 +95,11 @@ namespace PixiEditor.Models.DataHolders
 
             for (int i = 0; i < Layers.Count; i++)
             {
+                if (Layers[i] is TemplateLayer)
+                {
+                    continue;
+                }
+
                 float widthRatio = (float)newWidth / Width;
                 float heightRatio = (float)newHeight / Height;
                 int layerWidth = (int)(Layers[i].Width * widthRatio);

+ 1 - 1
PixiEditor/Models/IO/Importer.cs

@@ -31,7 +31,7 @@ namespace PixiEditor.Models.IO
         }
 
         /// <summary>
-        ///     Imports image from path and resizes it to given dimensions.
+        ///     Imports image from path.
         /// </summary>
         /// <param name="path">Path of image.</param>
         public static WriteableBitmap ImportImage(string path)

+ 1 - 1
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -92,7 +92,7 @@ namespace PixiEditor.Models.ImageManipulation
             WriteableBitmap previewBitmap = BitmapFactory.New(document.Width, document.Height);
 
             // 0.8 because blit doesn't take into consideration layer opacity. Small images with opacity > 80% are simillar enough.
-            foreach (var layer in document.Layers.Where(x => x.IsVisible && x.Opacity > 0.8f))
+            foreach (var layer in document.Layers.Where(x => x.IsVisible && x.Opacity > 0.8f && x is not TemplateLayer))
             {
                 previewBitmap.Blit(
                     new Rect(layer.OffsetX, layer.OffsetY, layer.Width, layer.Height),

+ 39 - 7
PixiEditor/Models/Layers/Layer.cs

@@ -15,6 +15,10 @@ namespace PixiEditor.Models.Layers
     public class Layer : BasicLayer
     {
         private const int SizeOfArgb = 4;
+
+        private static readonly Brush ActiveBrush = new SolidColorBrush(Color.FromRgb(80, 80, 86));
+        private static readonly Brush InactiveBrush = Brushes.Transparent;
+
         private bool clipRequested;
 
         private bool isActive;
@@ -29,6 +33,8 @@ namespace PixiEditor.Models.Layers
 
         private float opacity = 1f;
 
+        private Brush layerColor;
+
         public Layer(string name)
         {
             Name = name;
@@ -64,7 +70,7 @@ namespace PixiEditor.Models.Layers
             set
             {
                 name = value;
-                RaisePropertyChanged("Name");
+                RaisePropertyChanged(nameof(Name));
             }
         }
 
@@ -74,7 +80,8 @@ namespace PixiEditor.Models.Layers
             set
             {
                 isActive = value;
-                RaisePropertyChanged("IsActive");
+                RaisePropertyChanged(nameof(IsActive));
+                UpdateLayerColor();
             }
         }
 
@@ -95,7 +102,7 @@ namespace PixiEditor.Models.Layers
                             new object[] { LayerGuid },
                             "Change layer visibility"), true);
                     isVisible = value;
-                    RaisePropertyChanged("IsVisible");
+                    RaisePropertyChanged(nameof(IsVisible));
                 }
             }
         }
@@ -106,7 +113,7 @@ namespace PixiEditor.Models.Layers
             set
             {
                 isRenaming = value;
-                RaisePropertyChanged("IsRenaming");
+                RaisePropertyChanged(nameof(IsRenaming));
             }
         }
 
@@ -116,7 +123,7 @@ namespace PixiEditor.Models.Layers
             set
             {
                 layerBitmap = value;
-                RaisePropertyChanged("LayerBitmap");
+                RaisePropertyChanged(nameof(LayerBitmap));
             }
         }
 
@@ -137,7 +144,7 @@ namespace PixiEditor.Models.Layers
                             new object[] { LayerGuid },
                             "Change layer opacity"), true);
                     opacity = value;
-                    RaisePropertyChanged("Opacity");
+                    RaisePropertyChanged(nameof(Opacity));
                 }
             }
         }
@@ -152,7 +159,17 @@ namespace PixiEditor.Models.Layers
             set
             {
                 offset = value;
-                RaisePropertyChanged("Offset");
+                RaisePropertyChanged(nameof(Offset));
+            }
+        }
+
+        public Brush LayerColor
+        {
+            get => layerColor;
+            set
+            {
+                layerColor = value;
+                RaisePropertyChanged(nameof(LayerColor));
             }
         }
 
@@ -388,6 +405,21 @@ namespace PixiEditor.Models.Layers
             return byteArray;
         }
 
+        /// <summary>
+        /// Updates the <see cref="LayerColor"/> corresponding to <see cref="IsVisible"/>.
+        /// </summary>
+        protected virtual void UpdateLayerColor()
+        {
+            if (IsActive)
+            {
+                LayerColor = ActiveBrush;
+            }
+            else
+            {
+                LayerColor = InactiveBrush;
+            }
+        }
+
         private Dictionary<Coordinates, Color> GetRelativePosition(Dictionary<Coordinates, Color> changedPixels)
         {
             return changedPixels.ToDictionary(

+ 129 - 0
PixiEditor/Models/Layers/TemplateLayer.cs

@@ -0,0 +1,129 @@
+using System;
+using System.IO;
+using System.Security.Permissions;
+using System.Timers;
+using System.Windows.Media;
+using System.Windows.Threading;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.IO;
+
+namespace PixiEditor.Models.Layers
+{
+    internal class TemplateLayer : Layer
+    {
+        private static readonly Brush ActiveBrush = new SolidColorBrush(Color.FromRgb(130, 35, 35));
+        private static readonly Brush InactiveBrush = new SolidColorBrush(Color.FromArgb(70, 130, 35, 35));
+
+        private readonly Timer updateTimer = new Timer(300);
+
+        private string path;
+        private FileSystemWatcher watcher = new FileSystemWatcher();
+        private bool timerRunning;
+        private DateTime lastEditTime;
+
+        public string Path
+        {
+            get => path;
+            set
+            {
+                path = value;
+                RaisePropertyChanged(nameof(Path));
+            }
+        }
+
+        public TemplateLayer(string path, int width, int height)
+            : base("Template Layer")
+        {
+            Path = path;
+            WatcherInit();
+
+            UpdateBitmap();
+            Width = width;
+            Height = height;
+
+            UpdateLayerColor();
+            updateTimer.Elapsed += UpdateTimer_Elapsed;
+            updateTimer.AutoReset = true;
+        }
+
+        public void DocumentSizeChanged(object sender, DocumentSizeChangedEventArgs e)
+        {
+            Width = e.NewWidth;
+            Height = e.NewHeight;
+        }
+
+        protected override void UpdateLayerColor()
+        {
+            if (IsActive)
+            {
+                LayerColor = ActiveBrush;
+            }
+            else
+            {
+                LayerColor = InactiveBrush;
+            }
+        }
+
+        private void UpdateBitmap()
+        {
+            App.Current.Dispatcher.Invoke(() => LayerBitmap = Importer.ImportImage(Path));
+        }
+
+        private void WatcherInit()
+        {
+            watcher.Path = System.IO.Path.GetDirectoryName(path);
+            watcher.Filter = System.IO.Path.GetFileName(path);
+
+            watcher.NotifyFilter = NotifyFilters.LastWrite |
+                NotifyFilters.DirectoryName |
+                NotifyFilters.FileName;
+
+            watcher.EnableRaisingEvents = true;
+
+            watcher.Changed += TemplateChanged;
+            watcher.Deleted += TemplateDeleted;
+            watcher.Renamed += TemplateRenamed;
+        }
+
+        private void TemplateRenamed(object sender, RenamedEventArgs e)
+        {
+            path = e.FullPath;
+            watcher.Path = System.IO.Path.GetDirectoryName(path);
+            watcher.Filter = System.IO.Path.GetFileName(path);
+        }
+
+        private void TemplateDeleted(object sender, FileSystemEventArgs e)
+        {
+            path = null;
+            watcher.Dispose();
+            watcher = null;
+        }
+
+        private void TemplateChanged(object sender, FileSystemEventArgs e)
+        {
+            lastEditTime = DateTime.Now;
+
+            if (!timerRunning)
+            {
+                updateTimer.Start();
+                timerRunning = true;
+            }
+        }
+
+        private void UpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            if ((DateTime.Now - lastEditTime).TotalMilliseconds > 500)
+            {
+                UpdateBitmap();
+                updateTimer.Stop();
+                timerRunning = false;
+            }
+        }
+
+        ~TemplateLayer()
+        {
+            watcher.Dispose();
+            updateTimer.Dispose();
+        }
+    }
+}

+ 17 - 0
PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -1,4 +1,6 @@
 using PixiEditor.Helpers;
+using PixiEditor.Models.Dialogs;
+using PixiEditor.Models.Layers;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -8,6 +10,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand NewLayerCommand { get; set; }
 
+        public RelayCommand NewTemplateLayerCommand { get; set; }
+
         public RelayCommand DeleteLayerCommand { get; set; }
 
         public RelayCommand RenameLayerCommand { get; set; }
@@ -25,6 +29,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         {
             SetActiveLayerCommand = new RelayCommand(SetActiveLayer);
             NewLayerCommand = new RelayCommand(NewLayer, CanCreateNewLayer);
+            NewTemplateLayerCommand = new RelayCommand(NewTemplateLayer, CanCreateNewLayer);
             DeleteLayerCommand = new RelayCommand(DeleteLayer, CanDeleteLayer);
             MoveToBackCommand = new RelayCommand(MoveLayerToBack, CanMoveToBack);
             MoveToFrontCommand = new RelayCommand(MoveLayerToFront, CanMoveToFront);
@@ -33,6 +38,18 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             MergeWithBelowCommand = new RelayCommand(MergeWithBelow, CanMergeWithBelow);
         }
 
+        public void NewTemplateLayer(object parameter)
+        {
+            ImportFileDialog dialog = new ImportFileDialog();
+
+            if (dialog.ShowDialog())
+            {
+                TemplateLayer template = new TemplateLayer(dialog.FilePath, Owner.BitmapManager.ActiveDocument.Width, Owner.BitmapManager.ActiveDocument.Height);
+                Owner.BitmapManager.ActiveDocument.DocumentSizeChanged += template.DocumentSizeChanged;
+                Owner.BitmapManager.ActiveDocument.Layers.Add(template);
+            }
+        }
+
         public void NewLayer(object parameter)
         {
             Owner.BitmapManager.ActiveDocument.AddNewLayer($"New Layer {Owner.BitmapManager.ActiveDocument.Layers.Count}");

+ 10 - 5
PixiEditor/Views/MainWindow.xaml

@@ -284,7 +284,8 @@
                                                          CanDockAsTabbedDocument="True" CanFloat="True">
                                         <Grid>
                                             <Grid.RowDefinitions>
-                                                <RowDefinition Height="40"/>
+                                                <RowDefinition Height="Auto"/>
+                                                <RowDefinition Height="Auto"/>
                                                 <RowDefinition Height="30"/>
                                                 <RowDefinition Height="15"/>
                                                 <RowDefinition Height="1*"/>
@@ -292,15 +293,18 @@
                                             <Button Grid.Row="0" Command="{Binding LayersSubViewModel.NewLayerCommand}" Height="30" Content="New Layer"
                                             HorizontalAlignment="Stretch" Margin="5"
                                             Style="{StaticResource DarkRoundButton}" />
-                                            <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10,0">
+                                            <Button Grid.Row="1" Command="{Binding LayersSubViewModel.NewTemplateLayerCommand}" Height="30" Content="New Template Layer"
+                                            HorizontalAlignment="Stretch" Margin="5"
+                                            Style="{StaticResource DarkRoundButton}" />
+                                            <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="10,0">
                                                 <Label Content="Opacity" Foreground="White" VerticalAlignment="Center"/>
                                                 <vws:NumberInput Min="0" Max="100" Width="40" Height="20" VerticalAlignment="Center"
                                                          Value="{Binding BitmapManager.ActiveDocument.ActiveLayer.Opacity, Mode=TwoWay, 
                                             Converter={StaticResource FloatNormalizeConverter}}" />
                                                 <Label Content="%" Foreground="White" VerticalAlignment="Center"/>
                                             </StackPanel>
-                                            <Separator Grid.Row="2" Background="{StaticResource BrighterAccentColor}"/>
-                                            <ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
+                                            <Separator Grid.Row="3" Background="{StaticResource BrighterAccentColor}"/>
+                                            <ScrollViewer Grid.Row="4" VerticalScrollBarVisibility="Auto">
                                                 <ItemsControl ItemsSource="{Binding BitmapManager.ActiveDocument.Layers}"
                                                       x:Name="layersItemsControl" AlternationCount="9999">
                                                     <ItemsControl.ItemsPanel>
@@ -316,7 +320,8 @@
                                                                    IsRenaming="{Binding IsRenaming, Mode=TwoWay}"
                                                                    PreviewImage="{Binding LayerBitmap}"
                                                                    MoveToBackCommand="{Binding DataContext.LayersSubViewModel.MoveToBackCommand, ElementName=mainWindow}"
-                                                                   MoveToFrontCommand="{Binding DataContext.LayersSubViewModel.MoveToFrontCommand, ElementName=mainWindow}">
+                                                                   MoveToFrontCommand="{Binding DataContext.LayersSubViewModel.MoveToFrontCommand, ElementName=mainWindow}"
+                                                                   LayerColor="{Binding LayerColor}">
                                                                 <vws:LayerItem.ContextMenu>
                                                                     <ContextMenu>
                                                                         <MenuItem Header="Delete"

+ 1 - 1
PixiEditor/Views/UserControls/LayerItem.xaml

@@ -13,7 +13,7 @@
         <converters:BoolToColorConverter x:Key="BoolToColorConverter" />
     </UserControl.Resources>
     <Border BorderThickness="0 0 0 0.5" BorderBrush="Gray" MinWidth="60"
-            Background="{Binding IsActive, Mode=TwoWay, Converter={StaticResource BoolToColorConverter}}">
+            Background="{Binding Path=LayerColor, ElementName=uc}">
         <i:Interaction.Triggers>
             <i:EventTrigger EventName="MouseDown">
                 <i:InvokeCommandAction Command="{Binding ElementName=uc, 

+ 9 - 0
PixiEditor/Views/UserControls/LayerItem.xaml.cs

@@ -72,6 +72,15 @@ namespace PixiEditor.Views
         public static readonly DependencyProperty ControlButtonsVisibleProperty = DependencyProperty.Register(
             "ControlButtonsVisible", typeof(Visibility), typeof(LayerItem), new PropertyMetadata(System.Windows.Visibility.Hidden));
 
+        public static readonly DependencyProperty LayerColorProperty = DependencyProperty.Register(
+            nameof(LayerColor), typeof(Brush), typeof(LayerItem), new PropertyMetadata(Brushes.Transparent));
+
+        public Brush LayerColor
+        {
+            get => (Brush)GetValue(LayerColorProperty);
+            set => SetValue(LayerColorProperty, value);
+        }
+
         public WriteableBitmap PreviewImage
         {
             get { return (WriteableBitmap)GetValue(PreviewImageProperty); }