Browse Source

Replace ColorSlider.IsSaturationValueMaxForced with more powerful/general-purpose IsPerceptive

robloo 2 years ago
parent
commit
0b952ea5e0

+ 31 - 22
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs

@@ -49,20 +49,20 @@ namespace Avalonia.Controls.Primitives
                 false);
 
         /// <summary>
-        /// Defines the <see cref="IsRoundingEnabled"/> property.
+        /// Defines the <see cref="IsPerceptive"/> property.
         /// </summary>
-        public static readonly StyledProperty<bool> IsRoundingEnabledProperty =
+        public static readonly StyledProperty<bool> IsPerceptiveProperty =
             AvaloniaProperty.Register<ColorSlider, bool>(
-                nameof(IsRoundingEnabled),
-                false);
+                nameof(IsPerceptive),
+                true);
 
         /// <summary>
-        /// Defines the <see cref="IsSaturationValueMaxForced"/> property.
+        /// Defines the <see cref="IsRoundingEnabled"/> property.
         /// </summary>
-        public static readonly StyledProperty<bool> IsSaturationValueMaxForcedProperty =
+        public static readonly StyledProperty<bool> IsRoundingEnabledProperty =
             AvaloniaProperty.Register<ColorSlider, bool>(
-                nameof(IsSaturationValueMaxForced),
-                true);
+                nameof(IsRoundingEnabled),
+                false);
 
         /// <summary>
         /// Gets or sets the currently selected color in the RGB color model.
@@ -109,9 +109,9 @@ namespace Avalonia.Controls.Primitives
         }
 
         /// <summary>
-        /// Gets or sets a value indicating whether the alpha component is visible and rendered in the spectrum.
-        /// When false, this ensures that the spectrum is always visible and never transparent regardless of
-        /// the actual color.
+        /// Gets or sets a value indicating whether the alpha component is visible and rendered.
+        /// When false, this ensures that the gradient is always visible and never transparent regardless of
+        /// the actual color. This property is ignored when the alpha component itself is being displayed.
         /// </summary>
         /// <remarks>
         /// Setting to false means the alpha component is always forced to maximum for components other than
@@ -124,6 +124,26 @@ namespace Avalonia.Controls.Primitives
             set => SetValue(IsAlphaVisibleProperty, value);
         }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether the slider adapts rendering to improve user-perception
+        /// over exactness. When true in the HSVA color model, this ensures that the gradient is always visible and
+        /// never washed out regardless of the actual color. When true in the RGBA color model, this ensures
+        /// the components always appear as red, green or blue.
+        /// </summary>
+        /// <remarks>
+        /// For example, with Hue in the HSVA color model, the Saturation and Value components are always forced
+        /// to maximum values during rendering. In the RGBA color model, all components other than
+        /// <see cref="ColorComponent"/> are forced to minimum values during rendering.
+        /// <br/><br/>
+        /// Also note this property will only adjust components other than <see cref="ColorComponent"/> during rendering.
+        /// This doesn't change the values of any components in the color – it is only for display.
+        /// </remarks>
+        public bool IsPerceptive
+        {
+            get => GetValue(IsPerceptiveProperty);
+            set => SetValue(IsPerceptiveProperty, value);
+        }
+
         /// <summary>
         /// Gets or sets a value indicating whether rounding of color component values is enabled.
         /// </summary>
@@ -136,16 +156,5 @@ namespace Avalonia.Controls.Primitives
             get => GetValue(IsRoundingEnabledProperty);
             set => SetValue(IsRoundingEnabledProperty, value);
         }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether the saturation and value components are always forced to maximum values
-        /// when using the HSVA color model. Only component values other than <see cref="ColorComponent"/> will be changed.
-        /// This ensures, for example, that the Hue background is always visible and never washed out regardless of the actual color.
-        /// </summary>
-        public bool IsSaturationValueMaxForced
-        {
-            get => GetValue(IsSaturationValueMaxForcedProperty);
-            set => SetValue(IsSaturationValueMaxForcedProperty, value);
-        }
     }
 }

+ 43 - 30
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs

@@ -63,11 +63,11 @@ namespace Avalonia.Controls.Primitives
 
                 if (ColorModel == ColorModel.Hsva)
                 {
-                    perceivedColor = GetEquivalentBackgroundColor(HsvColor).ToRgb();
+                    perceivedColor = GetPerceptiveBackgroundColor(HsvColor).ToRgb();
                 }
                 else
                 {
-                    perceivedColor = GetEquivalentBackgroundColor(Color);
+                    perceivedColor = GetPerceptiveBackgroundColor(Color);
                 }
 
                 if (ColorHelper.GetRelativeLuminance(perceivedColor) <= 0.5)
@@ -107,7 +107,7 @@ namespace Avalonia.Controls.Primitives
             {
                 // As a fallback, attempt to calculate using the overall control size
                 // This shouldn't happen as a track is a required template part of a slider
-                // However, if it does, the spectrum will still be shown
+                // However, if it does, the spectrum gradient will still be shown
                 pixelWidth = Convert.ToInt32(Bounds.Width * scale);
                 pixelHeight = Convert.ToInt32(Bounds.Height * scale);
             }
@@ -122,7 +122,7 @@ namespace Avalonia.Controls.Primitives
                     ColorComponent,
                     HsvColor,
                     IsAlphaVisible,
-                    IsSaturationValueMaxForced);
+                    IsPerceptive);
 
                 if (_backgroundBitmap != null)
                 {
@@ -315,11 +315,11 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         /// <param name="hsvColor">The actual color to get the equivalent background color for.</param>
         /// <returns>The equivalent, perceived background color.</returns>
-        private HsvColor GetEquivalentBackgroundColor(HsvColor hsvColor)
+        private HsvColor GetPerceptiveBackgroundColor(HsvColor hsvColor)
         {
             var component = ColorComponent;
             var isAlphaVisible = IsAlphaVisible;
-            var isSaturationValueMaxForced = IsSaturationValueMaxForced;
+            var isPerceptive = IsPerceptive;
 
             if (isAlphaVisible == false &&
                 component != ColorComponent.Alpha)
@@ -327,28 +327,23 @@ namespace Avalonia.Controls.Primitives
                 hsvColor = new HsvColor(1.0, hsvColor.H, hsvColor.S, hsvColor.V);
             }
 
-            switch (component)
+            if (isPerceptive)
             {
-                case ColorComponent.Component1:
-                    return new HsvColor(
-                        hsvColor.A,
-                        hsvColor.H,
-                        isSaturationValueMaxForced ? 1.0 : hsvColor.S,
-                        isSaturationValueMaxForced ? 1.0 : hsvColor.V);
-                case ColorComponent.Component2:
-                    return new HsvColor(
-                        hsvColor.A,
-                        hsvColor.H,
-                        hsvColor.S,
-                        isSaturationValueMaxForced ? 1.0 : hsvColor.V);
-                case ColorComponent.Component3:
-                    return new HsvColor(
-                        hsvColor.A,
-                        hsvColor.H,
-                        isSaturationValueMaxForced ? 1.0 : hsvColor.S,
-                        hsvColor.V);
-                default:
-                    return hsvColor;
+                switch (component)
+                {
+                    case ColorComponent.Component1:
+                        return new HsvColor(hsvColor.A, hsvColor.H, 1.0, 1.0);
+                    case ColorComponent.Component2:
+                        return new HsvColor(hsvColor.A, hsvColor.H, hsvColor.S, 1.0);
+                    case ColorComponent.Component3:
+                        return new HsvColor(hsvColor.A, hsvColor.H, 1.0, hsvColor.V);
+                    default:
+                        return hsvColor;
+                }
+            }
+            else
+            {
+                return hsvColor;
             }
         }
 
@@ -358,10 +353,11 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         /// <param name="rgbColor">The actual color to get the equivalent background color for.</param>
         /// <returns>The equivalent, perceived background color.</returns>
-        private Color GetEquivalentBackgroundColor(Color rgbColor)
+        private Color GetPerceptiveBackgroundColor(Color rgbColor)
         {
             var component = ColorComponent;
             var isAlphaVisible = IsAlphaVisible;
+            var isPerceptive = IsPerceptive;
 
             if (isAlphaVisible == false &&
                 component != ColorComponent.Alpha)
@@ -369,7 +365,24 @@ namespace Avalonia.Controls.Primitives
                 rgbColor = new Color(255, rgbColor.R, rgbColor.G, rgbColor.B);
             }
 
-            return rgbColor;
+            if (isPerceptive)
+            {
+                switch (component)
+                {
+                    case ColorComponent.Component1:
+                        return new Color(rgbColor.A, rgbColor.R, 0, 0);
+                    case ColorComponent.Component2:
+                        return new Color(rgbColor.A, 0, rgbColor.G, 0);
+                    case ColorComponent.Component3:
+                        return new Color(rgbColor.A, 0, 0, rgbColor.B);
+                    default:
+                        return rgbColor;
+                }
+            }
+            else
+            {
+                return rgbColor;
+            }
         }
 
         /// <inheritdoc/>
@@ -401,7 +414,7 @@ namespace Avalonia.Controls.Primitives
             else if (change.Property == ColorComponentProperty ||
                      change.Property == ColorModelProperty ||
                      change.Property == IsAlphaVisibleProperty ||
-                     change.Property == IsSaturationValueMaxForcedProperty)
+                     change.Property == IsPerceptiveProperty)
             {
                 ignorePropertyChanged = true;
 

+ 35 - 17
src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs

@@ -31,9 +31,8 @@ namespace Avalonia.Controls.Primitives
         /// <param name="baseHsvColor">The base HSV color used for components not being changed.</param>
         /// <param name="isAlphaVisible">Whether the alpha component is visible and rendered in the bitmap.
         /// This property is ignored when the alpha component itself is being rendered.</param>
-        /// <param name="isSaturationValueMaxForced">Fix the saturation and value components to maximum
-        /// during calculation with the HSVA color model.
-        /// This will ensure colors are always discernible regardless of saturation/value.</param>
+        /// <param name="isPerceptive">Whether the slider adapts rendering to improve user-perception over exactness.
+        /// This will ensure colors are always discernible.</param>
         /// <returns>A new bitmap representing a gradient of color component values.</returns>
         public static async Task<ArrayList<byte>> CreateComponentBitmapAsync(
             int width,
@@ -43,7 +42,7 @@ namespace Avalonia.Controls.Primitives
             ColorComponent component,
             HsvColor baseHsvColor,
             bool isAlphaVisible,
-            bool isSaturationValueMaxForced)
+            bool isPerceptive)
         {
             if (width == 0 || height == 0)
             {
@@ -79,22 +78,41 @@ namespace Avalonia.Controls.Primitives
                     baseRgbColor = baseHsvColor.ToRgb();
                 }
 
-                // Maximize Saturation and Value components when in HSVA mode
-                if (isSaturationValueMaxForced &&
-                    colorModel == ColorModel.Hsva &&
+                // Apply any perceptive adjustments to the color
+                if (isPerceptive &&
                     component != ColorComponent.Alpha)
                 {
-                    switch (component)
+                    if (colorModel == ColorModel.Hsva)
                     {
-                        case ColorComponent.Component1:
-                            baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, 1.0);
-                            break;
-                        case ColorComponent.Component2:
-                            baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, baseHsvColor.S, 1.0);
-                            break;
-                        case ColorComponent.Component3:
-                            baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, baseHsvColor.V);
-                            break;
+                        // Maximize Saturation and Value components
+                        switch (component)
+                        {
+                            case ColorComponent.Component1: // Hue
+                                baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, 1.0);
+                                break;
+                            case ColorComponent.Component2: // Saturation
+                                baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, baseHsvColor.S, 1.0);
+                                break;
+                            case ColorComponent.Component3: // Value
+                                baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, baseHsvColor.V);
+                                break;
+                        }
+                    }
+                    else
+                    {
+                        // Minimize component values other than the current one
+                        switch (component)
+                        {
+                            case ColorComponent.Component1: // Red
+                                baseRgbColor = new Color(baseRgbColor.A, baseRgbColor.R, 0, 0);
+                                break;
+                            case ColorComponent.Component2: // Green
+                                baseRgbColor = new Color(baseRgbColor.A, 0, baseRgbColor.G, 0);
+                                break;
+                            case ColorComponent.Component3: // Blue
+                                baseRgbColor = new Color(baseRgbColor.A, 0, 0, baseRgbColor.B);
+                                break;
+                        }
                     }
                 }
 

+ 16 - 1
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@@ -114,7 +114,7 @@
                                               AutomationProperties.Name="Third Component"
                                               Grid.Column="0"
                                               IsAlphaVisible="False"
-                                              IsSaturationValueMaxForced="True"
+                                              IsPerceptive="False"
                                               Orientation="Vertical"
                                               ColorModel="Hsva"
                                               ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
@@ -502,6 +502,21 @@
         </DropDownButton>
       </ControlTemplate>
     </Setter>
+
+    <Style Selector="^ /template/ primitives|ColorSlider#ColorSpectrumThirdComponentSlider[ColorComponent=Component1]">
+      <Setter Property="IsPerceptive" Value="True" />
+    </Style>
+
+    <Style Selector="^ /template/ primitives|ColorSlider#Component1Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+    <Style Selector="^ /template/ primitives|ColorSlider#Component2Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+    <Style Selector="^ /template/ primitives|ColorSlider#Component3Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+
   </ControlTheme>
 
 </ResourceDictionary>

+ 16 - 1
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@@ -361,7 +361,7 @@
                                         AutomationProperties.Name="Third Component"
                                         Grid.Column="0"
                                         IsAlphaVisible="False"
-                                        IsSaturationValueMaxForced="True"
+                                        IsPerceptive="False"
                                         Orientation="Vertical"
                                         ColorModel="Hsva"
                                         ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
@@ -746,6 +746,21 @@
         </Grid>
       </ControlTemplate>
     </Setter>
+
+    <Style Selector="^ /template/ primitives|ColorSlider#ColorSpectrumThirdComponentSlider[ColorComponent=Component1]">
+      <Setter Property="IsPerceptive" Value="True" />
+    </Style>
+
+    <Style Selector="^ /template/ primitives|ColorSlider#Component1Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+    <Style Selector="^ /template/ primitives|ColorSlider#Component2Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+    <Style Selector="^ /template/ primitives|ColorSlider#Component3Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+
   </ControlTheme>
 
 </ResourceDictionary>

+ 16 - 1
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml

@@ -113,7 +113,7 @@
                                               AutomationProperties.Name="Third Component"
                                               Grid.Column="0"
                                               IsAlphaVisible="False"
-                                              IsSaturationValueMaxForced="True"
+                                              IsPerceptive="False"
                                               Orientation="Vertical"
                                               ColorModel="Hsva"
                                               ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
@@ -501,6 +501,21 @@
         </DropDownButton>
       </ControlTemplate>
     </Setter>
+
+    <Style Selector="^ /template/ primitives|ColorSlider#ColorSpectrumThirdComponentSlider[ColorComponent=Component1]">
+      <Setter Property="IsPerceptive" Value="True" />
+    </Style>
+
+    <Style Selector="^ /template/ primitives|ColorSlider#Component1Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+    <Style Selector="^ /template/ primitives|ColorSlider#Component2Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+    <Style Selector="^ /template/ primitives|ColorSlider#Component3Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+
   </ControlTheme>
 
 </ResourceDictionary>

+ 16 - 1
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

@@ -323,7 +323,7 @@
                                         AutomationProperties.Name="Third Component"
                                         Grid.Column="0"
                                         IsAlphaVisible="False"
-                                        IsSaturationValueMaxForced="True"
+                                        IsPerceptive="False"
                                         Orientation="Vertical"
                                         ColorModel="Hsva"
                                         ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
@@ -708,6 +708,21 @@
         </Grid>
       </ControlTemplate>
     </Setter>
+
+    <Style Selector="^ /template/ primitives|ColorSlider#ColorSpectrumThirdComponentSlider[ColorComponent=Component1]">
+      <Setter Property="IsPerceptive" Value="True" />
+    </Style>
+
+    <Style Selector="^ /template/ primitives|ColorSlider#Component1Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+    <Style Selector="^ /template/ primitives|ColorSlider#Component2Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+    <Style Selector="^ /template/ primitives|ColorSlider#Component3Slider[ColorModel=Rgba]">
+      <Setter Property="IsPerceptive" Value="False" />
+    </Style>
+
   </ControlTheme>
 
 </ResourceDictionary>