Browse Source

Added colored sliders and icons to node views

Krzysztof Krysiński 5 months ago
parent
commit
45b49beea1

+ 5 - 0
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/UpdateProperty_Change.cs

@@ -162,4 +162,9 @@ internal class UpdatePropertyValue_Change : Change
 
         return hash.ToHashCode();
     }
+
+    public override bool IsMergeableWith(Change other)
+    {
+        return other is UpdatePropertyValue_Change change && change._nodeId == _nodeId && change._propertyName == _propertyName;
+    }
 }

+ 1 - 0
src/PixiEditor.UI.Common/Controls/Slider.axaml

@@ -22,6 +22,7 @@
                 <ControlTemplate>
                     <Grid Name="grid">
                         <Border Margin="6, 0" CornerRadius="4" Background="{DynamicResource ThemeControlLowBrush}"
+                                Name="TrackBackground"
                                 Height="6"
                                 VerticalAlignment="Center">
                         </Border>

+ 14 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -892,5 +892,18 @@
   "MASK_NODE": "Mask",
   "SEPIA_FILTER_NODE": "Sepia Filter",
   "INTENSITY": "Intensity",
-  "INVERT_FILTER_NODE": "Invert Filter"
+  "INVERT_FILTER_NODE": "Invert Filter",
+  "COLOR_ADJUSTMENTS_FILTER": "Color Adjustments Filter",
+  "ADJUST_BRIGHTNESS": "Adjust Brightness",
+  "ADJUST_CONTRAST": "Adjust Contrast",
+  "ADJUST_SATURATION": "Adjust Saturation",
+  "ADJUST_TEMPERATURE": "Adjust Temperature",
+  "ADJUST_TINT": "Adjust Tint",
+  "ADJUST_HUE": "Adjust Hue",
+  "HUE_VALUE": "Hue",
+  "SATURATION_VALUE": "Saturation",
+  "BRIGHTNESS_VALUE": "Brightness",
+  "CONTRAST_VALUE": "Contrast",
+  "TEMPERATURE_VALUE": "Temperature",
+  "TINT_VALUE": "Tint"
 }

+ 3 - 0
src/PixiEditor/Helpers/ThemeResources.cs

@@ -32,4 +32,7 @@ public static class ThemeResources
     public static Color SelectionFillColor =>
         ResourceLoader.GetResource<Avalonia.Media.Color>("SelectionFillColor", Application.Current.ActualThemeVariant)
             .ToColor();
+
+    public static SolidColorBrush ThemeControlLowBrush =>
+        ResourceLoader.GetResource<SolidColorBrush>("ThemeControlLowBrush", Application.Current.ActualThemeVariant);
 }

+ 1 - 0
src/PixiEditor/Models/Handlers/INodeHandler.cs

@@ -23,6 +23,7 @@ public interface INodeHandler : INotifyPropertyChanged
     public PreviewPainter? ResultPainter { get; set; }
     public VecD PositionBindable { get; set; }
     public bool IsNodeSelected { get; set; }
+    public string Icon { get; }
     public void TraverseBackwards(Func<INodeHandler, bool> func);
     public void TraverseBackwards(Func<INodeHandler, INodeHandler, bool> func);
     public void TraverseBackwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func);

+ 8 - 2
src/PixiEditor/Models/Rendering/PreviewPainter.cs

@@ -71,6 +71,11 @@ public class PreviewPainter
 
     public void ChangeRenderTextureSize(int requestId, VecI size)
     {
+        if (size.X <= 0 || size.Y <= 0)
+        {
+            return;
+        }
+
         if (repaintingTextures.Contains(requestId))
         {
             pendingResizes[requestId] = size;
@@ -161,7 +166,8 @@ public class PreviewPainter
                                 try
                                 {
                                     renderTexture.Dispose();
-                                } catch (Exception) { }
+                                }
+                                catch (Exception) { }
                             }
 
                             renderTextures.Remove(texture);
@@ -170,7 +176,7 @@ public class PreviewPainter
                             dirtyTextures.Remove(texture);
                             return;
                         }
-                        
+
                         if (renderTexture is { IsDisposed: false })
                         {
                             try

+ 1 - 0
src/PixiEditor/Styles/Templates/NodeGraphView.axaml

@@ -43,6 +43,7 @@
                                 <nodes:NodeView
                                     Node="{Binding}"
                                     DisplayName="{Binding NodeNameBindable}"
+                                    Icon="{Binding Icon}"
                                     CategoryBackgroundBrush="{Binding CategoryBackgroundBrush}"
                                     Inputs="{Binding Inputs}"
                                     ActiveFrame="{Binding ActiveFrame, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"

+ 5 - 3
src/PixiEditor/Styles/Templates/NodeView.axaml

@@ -31,8 +31,10 @@
                         <Border Padding="{TemplateBinding Padding}" Grid.ColumnSpan="3" Grid.Row="0"
                                 CornerRadius="4.5, 4.5, 0 ,0"
                                 Background="{TemplateBinding CategoryBackgroundBrush}">
-                            <TextBlock ui:Translator.Key="{TemplateBinding DisplayName}"
-                                       FontWeight="Bold" />
+                            <TextBlock FontWeight="Bold">
+                                <Run Classes="pixi-icon" BaselineAlignment="Center" Text="{TemplateBinding Icon}"/>
+                                <Run BaselineAlignment="Center" ui:Translator.Key="{TemplateBinding DisplayName}"/>
+                            </TextBlock>
                         </Border>
                         <Border Grid.Row="1" Background="{DynamicResource ThemeControlMidBrush}">
                             <StackPanel>
@@ -60,7 +62,7 @@
                                     <ImageBrush Source="/Images/CheckerTile.png"
                                                 TileMode="Tile" DestinationRect="0, 0, 25, 25" />
                                 </Panel.Background>
-                            <visuals:PreviewPainterControl 
+                            <visuals:PreviewPainterControl
                                                            PreviewPainter="{TemplateBinding ResultPreview}"
                                                            FrameToRender="{TemplateBinding ActiveFrame}"
                                                            RenderOptions.BitmapInterpolationMode="None">

+ 1 - 1
src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ColorAdjustmentsFilterNode.cs

@@ -157,7 +157,7 @@ public class ColorAdjustmentsFilterNode : FilterNode
     {
         if (AdjustHue.Value)
         {
-            float value = (float)HueValue.Value * (float)Math.PI / 180f;
+            float value = (float)-HueValue.Value * (float)Math.PI / 180f;
             var cosVal = (float)Math.Cos(value);
             var sinVal = (float)Math.Sin(value);
             float lumR = 0.213f;

+ 109 - 3
src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ColorAdjustmentsFilterNodeViewModel.cs

@@ -1,6 +1,112 @@
-using PixiEditor.ViewModels.Nodes;
+using Avalonia;
+using Avalonia.Media;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Events;
+using PixiEditor.Models.Handlers;
+using PixiEditor.UI.Common.Fonts;
+using PixiEditor.ViewModels.Nodes;
+using PixiEditor.ViewModels.Nodes.Properties;
 
 namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
 
-[NodeViewModel("COLOR_ADJUSTMENTS_FILTER", "FILTERS", "")]
-internal class ColorAdjustmentsFilterNodeViewModel : NodeViewModel<ColorAdjustmentsFilterNode>;
+[NodeViewModel("COLOR_ADJUSTMENTS_FILTER", "FILTERS", PixiPerfectIcons.Sun )]
+internal class ColorAdjustmentsFilterNodeViewModel : NodeViewModel<ColorAdjustmentsFilterNode>
+{
+    private Dictionary<string, List<INodePropertyHandler>> toggleToProperties = new Dictionary<string, List<INodePropertyHandler>>();
+    public override void OnInitialized()
+    {
+        foreach (var input in Inputs)
+        {
+            if(input is BooleanPropertyViewModel booleanProperty)
+            {
+                booleanProperty.ValueChanged += BooleanPropertyOnValueChanged;
+                toggleToProperties.Add(input.PropertyName, new List<INodePropertyHandler>());
+            }
+            if (input is DoublePropertyViewModel doubleProperty)
+            {
+                doubleProperty.NumberPickerMode = NumberPickerMode.Slider;
+                doubleProperty.Min = -1;
+                doubleProperty.Max = 1;
+
+                if(input.PropertyName == "HueValue")
+                {
+                    doubleProperty.Min = -180;
+                    doubleProperty.Max = 180;
+                }
+
+                doubleProperty.IsVisible = false;
+                doubleProperty.SliderSettings.IsColorSlider = true;
+                var background = SolveBackground(doubleProperty.PropertyName);
+
+                doubleProperty.SliderSettings.BackgroundBrush = background;
+                AddToToggleGroup(input);
+            }
+        }
+    }
+
+    private static IBrush SolveBackground(string propertyName)
+    {
+        if (propertyName.Contains("Brightness") || propertyName.Contains("Saturation") || propertyName.Contains("Contrast"))
+        {
+            LinearGradientBrush brush = new LinearGradientBrush();
+            brush.GradientStops.Add(new GradientStop(Colors.Black, 0));
+            brush.GradientStops.Add(new GradientStop(Colors.White, 1));
+
+            return brush;
+        }
+
+        if (propertyName.Contains("Temperature"))
+        {
+            LinearGradientBrush brush = new LinearGradientBrush();
+            brush.GradientStops.Add(new GradientStop(Colors.Blue, 0));
+            brush.GradientStops.Add(new GradientStop(Colors.Red, 1));
+
+            return brush;
+        }
+
+        if (propertyName.Contains("Tint"))
+        {
+            LinearGradientBrush brush = new LinearGradientBrush();
+            brush.GradientStops.Add(new GradientStop(Colors.Green, 0));
+            brush.GradientStops.Add(new GradientStop(Colors.Magenta, 1));
+
+            return brush;
+        }
+
+        if (propertyName.Contains("Hue"))
+        {
+            LinearGradientBrush brush = new LinearGradientBrush();
+            brush.GradientStops.Add(new GradientStop(Colors.Red, 0));
+            brush.GradientStops.Add(new GradientStop(Colors.Yellow, 0.166));
+            brush.GradientStops.Add(new GradientStop(Colors.Green, 0.333));
+            brush.GradientStops.Add(new GradientStop(Colors.Cyan, 0.5));
+            brush.GradientStops.Add(new GradientStop(Colors.Blue, 0.666));
+            brush.GradientStops.Add(new GradientStop(Colors.Magenta, 0.833));
+            brush.GradientStops.Add(new GradientStop(Colors.Red, 1));
+
+            return brush;
+        }
+
+        return ThemeResources.ThemeControlLowBrush;
+    }
+
+    private void BooleanPropertyOnValueChanged(INodePropertyHandler property, NodePropertyValueChangedArgs args)
+    {
+        if (toggleToProperties.TryGetValue(property.PropertyName, out var toProperty))
+        {
+            foreach (var prop in toProperty)
+            {
+                prop.IsVisible = args.NewValue is bool b and true;
+            }
+        }
+    }
+
+    private void AddToToggleGroup(INodePropertyHandler property)
+    {
+        string groupName = "Adjust" + property.PropertyName.Replace("Value", "");
+        if (toggleToProperties.ContainsKey(groupName))
+        {
+            toggleToProperties[groupName].Add(property);
+        }
+    }
+}

+ 3 - 0
src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs

@@ -30,6 +30,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
     private ObservableRangeCollection<INodePropertyHandler> outputs = new();
     private PreviewPainter resultPainter;
     private bool isSelected;
+    private string? icon;
 
     protected Guid id;
 
@@ -165,6 +166,8 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         OnPropertyChanged(nameof(NodeNameBindable));
     }
 
+    public string Icon => icon ??= GetType().GetCustomAttribute<NodeViewModelAttribute>().Icon;
+
     public void TraverseBackwards(Func<INodeHandler, bool> func)
     {
         var visited = new HashSet<INodeHandler>();

+ 85 - 1
src/PixiEditor/ViewModels/Nodes/Properties/DoublePropertyViewModel.cs

@@ -1,8 +1,92 @@
-namespace PixiEditor.ViewModels.Nodes.Properties;
+using System.ComponentModel;
+using Avalonia;
+using Avalonia.Media;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace PixiEditor.ViewModels.Nodes.Properties;
 
 internal class DoublePropertyViewModel : NodePropertyViewModel<double>
 {
+    private double min = double.MinValue;
+    private double max = double.MaxValue;
+
+    private NumberPickerMode numberPickerMode = NumberPickerMode.NumberInput;
+
+    private SliderSettings sliderSettings = new SliderSettings();
+
+    public NumberPickerMode NumberPickerMode
+    {
+        get => numberPickerMode;
+        set => SetProperty(ref numberPickerMode, value);
+    }
+
+    public double DoubleValue
+    {
+        get => Value;
+        set => Value = value;
+    }
+
+    public double Min
+    {
+        get => min;
+        set => SetProperty(ref min, value);
+    }
+
+    public double Max
+    {
+        get => max;
+        set => SetProperty(ref max, value);
+    }
+
+    public SliderSettings SliderSettings
+    {
+        get => sliderSettings;
+        set => SetProperty(ref sliderSettings, value);
+    }
+
     public DoublePropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
     {
     }
+
+    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
+    {
+        base.OnPropertyChanged(e);
+        if (e.PropertyName == nameof(Value))
+        {
+            OnPropertyChanged(nameof(DoubleValue));
+        }
+    }
+}
+
+class SliderSettings : ObservableObject
+{
+    private bool isColorSlider;
+    private IBrush backgroundBrush;
+    private IBrush borderBrush;
+    private Thickness borderThickness;
+    public double thumbSize;
+    public IBrush thumbBackground;
+
+    public bool IsColorSlider
+    {
+        get => isColorSlider;
+        set => SetProperty(ref isColorSlider, value);
+    }
+
+    public IBrush BackgroundBrush
+    {
+        get => backgroundBrush;
+        set => SetProperty(ref backgroundBrush, value);
+    }
+
+    public SliderSettings()
+    {
+
+    }
+}
+
+public enum NumberPickerMode
+{
+    NumberInput,
+    Slider,
 }

+ 21 - 4
src/PixiEditor/Views/Input/NumberInput.cs

@@ -38,6 +38,15 @@ internal partial class NumberInput : TextBox
         AvaloniaProperty.Register<NumberInput, bool>(
             "EnableScrollChange", true);
 
+    public static readonly StyledProperty<bool> EnableGrabberProperty = AvaloniaProperty.Register<NumberInput, bool>(
+        nameof(EnableGrabber), true);
+
+    public bool EnableGrabber
+    {
+        get => GetValue(EnableGrabberProperty);
+        set => SetValue(EnableGrabberProperty, value);
+    }
+
     public string FormattedValue
     {
         get => GetValue(FormattedValueProperty);
@@ -164,14 +173,22 @@ internal partial class NumberInput : TextBox
     {
         base.OnApplyTemplate(e);
 
-        InnerLeftContent = leftGrabber = CreateMouseGrabber();
-        leftGrabber.HorizontalAlignment = HorizontalAlignment.Left;
-        InnerRightContent = rightGrabber = CreateMouseGrabber();
-        rightGrabber.HorizontalAlignment = HorizontalAlignment.Right;
+        if (EnableGrabber)
+        {
+            InnerLeftContent = leftGrabber = CreateMouseGrabber();
+            leftGrabber.HorizontalAlignment = HorizontalAlignment.Left;
+            InnerRightContent = rightGrabber = CreateMouseGrabber();
+            rightGrabber.HorizontalAlignment = HorizontalAlignment.Right;
+        }
     }
 
     protected override void OnSizeChanged(SizeChangedEventArgs e)
     {
+        if (!EnableGrabber)
+        {
+            return;
+        }
+
         if (e.NewSize.Width < 100)
         {
             rightGrabber.IsVisible = false;

+ 9 - 0
src/PixiEditor/Views/Nodes/NodeView.cs

@@ -59,6 +59,15 @@ public class NodeView : TemplatedControl
     public static readonly StyledProperty<ICommand> EndDragCommandProperty =
         AvaloniaProperty.Register<NodeView, ICommand>("EndDragCommand");
 
+    public static readonly StyledProperty<string> IconProperty = AvaloniaProperty.Register<NodeView, string>(
+        nameof(Icon));
+
+    public string Icon
+    {
+        get => GetValue(IconProperty);
+        set => SetValue(IconProperty, value);
+    }
+
     public INodeHandler Node
     {
         get => GetValue(NodeProperty);

+ 63 - 8
src/PixiEditor/Views/Nodes/Properties/DoublePropertyView.axaml

@@ -8,16 +8,71 @@
                              xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
                              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
                              xmlns:properties1="clr-namespace:PixiEditor.ViewModels.Nodes.Properties"
+                             xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours"
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:DataType="properties1:DoublePropertyViewModel"
                              x:Class="PixiEditor.Views.Nodes.Properties.DoublePropertyView">
     <Design.DataContext>
-        <properties1:DoublePropertyViewModel/>
+        <properties1:DoublePropertyViewModel />
     </Design.DataContext>
-    <Grid HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
-        <input:NumberInput EnableScrollChange="False" Name="input"
-                           HorizontalAlignment="Right" MinWidth="100" Decimals="6" IsVisible="{Binding ShowInputField}"
-                           Value="{Binding Value, Mode=TwoWay}" />
-    </Grid>
-</properties:NodePropertyView>
+    <StackPanel Orientation="Vertical"
+                HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
+        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
+        <Panel IsVisible="{Binding ShowInputField}">
+            <DockPanel
+                LastChildFill="True"
+                IsVisible="{Binding NumberPickerMode,
+                                            Converter={converters:EnumBooleanConverter}, ConverterParameter=Slider}">
+                <input:NumberInput DockPanel.Dock="Right"
+                                   EnableScrollChange="False" Name="sliderInput"
+                                   Width="45" Decimals="2"
+                                   EnableGrabber="False"
+                                   Min="{Binding Min}" Max="{Binding Max}"
+                                   Value="{Binding DoubleValue, Mode=TwoWay}" />
+
+                <Slider Value="{Binding DoubleValue, Mode=TwoWay}"
+                        Margin="5, 0"
+                        Classes.colorSlider="{Binding SliderSettings.IsColorSlider}"
+                        Minimum="{Binding Min}" Maximum="{Binding Max}">
+                    <Slider.Styles>
+                        <Style Selector="Slider.colorSlider Border#TrackBackground">
+                            <Setter Property="Background" Value="{Binding SliderSettings.BackgroundBrush}" />
+                            <Setter Property="BorderThickness" Value="0" />
+                            <Setter Property="Height" Value="8" />
+                            <Setter Property="Margin" Value="0"/>
+                        </Style>
+                        <Style Selector="Slider.colorSlider Thumb">
+                            <Setter Property="Width" Value="10" />
+                            <Setter Property="Height" Value="10" />
+                            <Setter Property="MinWidth" Value="10" />
+                            <Setter Property="MinHeight" Value="10" />
+                        </Style>
+                        <Style Selector="Slider.colorSlider Thumb Border">
+                            <Setter Property="Width" Value="10" />
+                            <Setter Property="Height" Value="10" />
+                            <Setter Property="CornerRadius" Value="50" />
+                            <Setter Property="Background">
+                                <Setter.Value>
+                                    <VisualBrush>
+                                        <VisualBrush.Visual>
+                                            <Ellipse Width="10" Height="10" Fill="Transparent" Stroke="White"
+                                                     StrokeThickness="1" />
+                                        </VisualBrush.Visual>
+                                    </VisualBrush>
+                                </Setter.Value>
+                            </Setter>
+                        </Style>
+                    </Slider.Styles>
+                </Slider>
+
+
+            </DockPanel>
+            <input:NumberInput EnableScrollChange="False" Name="input"
+                               MinWidth="100" Decimals="6"
+                               IsVisible="{Binding NumberPickerMode,
+                                            Converter={converters:EnumBooleanConverter}, ConverterParameter=NumberInput}"
+                               Min="{Binding Min}" Max="{Binding Max}"
+                               Value="{Binding DoubleValue, Mode=TwoWay}" />
+        </Panel>
+    </StackPanel>
+</properties:NodePropertyView>