From 9250ccb18afb5d538b6e300a05c606da2d38d3a6 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 13:35:13 -0400 Subject: [PATCH 01/37] Add missing base constructor call --- .../ColorSpectrum/ColorSpectrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index bd44161a42..200a652d3b 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -95,7 +95,7 @@ namespace Avalonia.Controls.Primitives /// /// Initializes a new instance of the class. /// - public ColorSpectrum() + public ColorSpectrum() : base() { _shapeFromLastBitmapCreation = Shape; _componentsFromLastBitmapCreation = Components; From 5c3a56a9533eab9060a216831df2a9a8c5e08e12 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:25:02 -0400 Subject: [PATCH 02/37] Fix the color spectrum selection ellipse position Specifically, when updated and not part of the visual tree --- .../ColorSpectrum/ColorSpectrum.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 200a652d3b..9261e5892d 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -171,6 +171,18 @@ namespace Avalonia.Controls.Primitives { base.OnAttachedToVisualTree(e); + // If the color was updated while this ColorSpectrum was not part of the visual tree, + // the selection ellipse may be in an incorrect position. This is because the spectrum + // renders based on layout scaling to avoid color banding; however, layout scale is only + // available when the control is attached to the visual tree. The ColorSpectrum's color + // may be updated from code-behind or from binding with another control when it's not + // part of the visual tree. + // + // See discussion: https://github.com/AvaloniaUI/Avalonia/discussions/9077 + // + // To work-around this issue the selection ellipse is refreshed here. + UpdateEllipse(); + // OnAttachedToVisualTree is called after OnApplyTemplate so events cannot be connected here } @@ -832,6 +844,8 @@ namespace Avalonia.Controls.Primitives } // Remember the bitmap size follows physical device pixels + // Warning: LayoutHelper.GetLayoutScale() doesn't work unless the control is visible + // This will not be true in all cases if the color is updated from another control or code-behind var scale = LayoutHelper.GetLayoutScale(this); Canvas.SetLeft(_selectionEllipsePanel, (xPosition / scale) - (_selectionEllipsePanel.Width / 2)); Canvas.SetTop(_selectionEllipsePanel, (yPosition / scale) - (_selectionEllipsePanel.Height / 2)); From 55e27213f958304679d449ef4aeb25ca9e8743ea Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:25:26 -0400 Subject: [PATCH 03/37] Comment additional ColorSpectrum methods --- .../ColorSpectrum/ColorSpectrum.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 9261e5892d..f272ce850e 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -600,6 +600,10 @@ namespace Avalonia.Controls.Primitives RaiseColorChanged(); } + /// + /// Updates the selected and based on a point within the color spectrum. + /// + /// The point on the spectrum representing the color. private void UpdateColorFromPoint(PointerPoint point) { // If we haven't initialized our HSV value array yet, then we should just ignore any user input - @@ -676,6 +680,9 @@ namespace Avalonia.Controls.Primitives UpdateColor(hsvAtPoint); } + /// + /// Updates the position of the selection ellipse on the spectrum which indicates the selected color. + /// private void UpdateEllipse() { if (_selectionEllipsePanel == null) From 8b070919398878acda991efc0140f7a02cd2a8b7 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 9 Oct 2022 12:21:08 -0400 Subject: [PATCH 04/37] Fix formatting --- .../Themes/Fluent/ColorView.xaml | 30 +++++++++---------- .../Themes/Simple/ColorView.xaml | 30 +++++++++---------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 16bc2acdd1..cf836aff4a 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -241,23 +241,21 @@ - + - + - + - + Date: Sun, 2 Oct 2022 11:51:25 -0400 Subject: [PATCH 05/37] Implement WriteableBitmap caching/reuse and disposal in ColorSlider --- .../ColorSlider/ColorSlider.cs | 42 ++++++++++++++++++- .../Helpers/ColorPickerHelpers.cs | 19 ++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index b662d20223..4e907f610d 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Metadata; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Media.Imaging; using Avalonia.Utilities; namespace Avalonia.Controls.Primitives @@ -31,6 +32,8 @@ namespace Avalonia.Controls.Primitives protected bool ignorePropertyChanged = false; + private WriteableBitmap? _backgroundBitmap; + /// /// Initializes a new instance of the class. /// @@ -38,6 +41,26 @@ namespace Avalonia.Controls.Primitives { } + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + // Bitmaps were released when detached from the visual tree so they must be re-built + UpdateBackground(); + } + + /// + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + + // Clean-up all bitmaps + // https://github.com/AvaloniaUI/Avalonia/issues/9051 + _backgroundBitmap?.Dispose(); + _backgroundBitmap = null; + } + /// /// Updates the visual state of the control by applying latest PseudoClasses. /// @@ -110,7 +133,19 @@ namespace Avalonia.Controls.Primitives if (bitmap != null) { - Background = new ImageBrush(ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight)); + if (_backgroundBitmap != null) + { + // Re-use the existing WriteableBitmap + // This assumes the height, width and byte counts are the same and must be set to null + // elsewhere if that assumption is ever not true. + ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bitmap); + } + else + { + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight); + } + + Background = new ImageBrush(_backgroundBitmap); } } } @@ -399,6 +434,11 @@ namespace Avalonia.Controls.Primitives } else if (change.Property == BoundsProperty) { + // If the control's overall dimensions have changed the background bitmap size also needs to change. + // This means the existing bitmap must be released to be recreated correctly in UpdateBackground(). + _backgroundBitmap?.Dispose(); + _backgroundBitmap = null; + UpdateBackground(); } else if (change.Property == ValueProperty || diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index c1904a3c30..f2683e4677 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -617,7 +617,6 @@ namespace Avalonia.Controls.Primitives PixelFormat.Bgra8888, AlphaFormat.Premul); - // Warning: This is highly questionable using (var frameBuffer = bitmap.Lock()) { Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); @@ -625,5 +624,23 @@ namespace Avalonia.Controls.Primitives return bitmap; } + + /// + /// Updates the given with new, raw BGRA pre-multiplied alpha pixel data. + /// WARNING: The bitmap's width, height and byte count MUST not have changed and MUST be enforced externally. + /// + /// The existing to update. + /// The bitmap (in raw BGRA pre-multiplied alpha pixels). + public static void UpdateBitmapFromPixelData( + WriteableBitmap bitmap, + IList bgraPixelData) + { + using (var frameBuffer = bitmap.Lock()) + { + Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); + } + + return; + } } } From ca7543f1d613f9d7b9f2cfed978f8e23379d6684 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:24:10 -0400 Subject: [PATCH 06/37] Add new ArrayList to avoid an extra copy when creating spectrum bitmaps --- .../ColorSlider/ColorSlider.cs | 8 +-- .../ColorSpectrum/ColorSpectrum.cs | 61 +++++++++------- .../Helpers/ArrayList.cs | 71 +++++++++++++++++++ .../Helpers/ColorPickerHelpers.cs | 18 ++--- 4 files changed, 118 insertions(+), 40 deletions(-) create mode 100644 src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 4e907f610d..8bcdef5558 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -121,7 +121,7 @@ namespace Avalonia.Controls.Primitives if (pixelWidth != 0 && pixelHeight != 0) { - var bitmap = await ColorPickerHelpers.CreateComponentBitmapAsync( + ArrayList bgraPixelData = await ColorPickerHelpers.CreateComponentBitmapAsync( pixelWidth, pixelHeight, Orientation, @@ -131,18 +131,18 @@ namespace Avalonia.Controls.Primitives IsAlphaMaxForced, IsSaturationValueMaxForced); - if (bitmap != null) + if (bgraPixelData != null) { if (_backgroundBitmap != null) { // Re-use the existing WriteableBitmap // This assumes the height, width and byte counts are the same and must be set to null // elsewhere if that assumption is ever not true. - ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bitmap); + ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); } else { - _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight); + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); } Background = new ImageBrush(_backgroundBitmap); diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index f272ce850e..3604894833 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -994,13 +994,13 @@ namespace Avalonia.Controls.Primitives // The middle 4 are only needed and used in the case of hue as the third dimension. // Saturation and luminosity need only a min and max. - List bgraMinPixelData = new List(); - List bgraMiddle1PixelData = new List(); - List bgraMiddle2PixelData = new List(); - List bgraMiddle3PixelData = new List(); - List bgraMiddle4PixelData = new List(); - List bgraMaxPixelData = new List(); - List newHsvValues = new List(); + ArrayList bgraMinPixelData; + ArrayList bgraMiddle1PixelData; + ArrayList bgraMiddle2PixelData; + ArrayList bgraMiddle3PixelData; + ArrayList bgraMiddle4PixelData; + ArrayList bgraMaxPixelData; + List newHsvValues; // In Avalonia, Bounds returns the actual device-independent pixel size of a control. // However, this is not necessarily the size of the control rendered on a display. @@ -1011,20 +1011,27 @@ namespace Avalonia.Controls.Primitives int pixelDimension = (int)Math.Round(minDimension * scale); var pixelCount = pixelDimension * pixelDimension; var pixelDataSize = pixelCount * 4; - bgraMinPixelData.Capacity = pixelDataSize; + + bgraMinPixelData = new ArrayList(pixelDataSize); + bgraMaxPixelData = new ArrayList(pixelDataSize); + newHsvValues = new List(pixelCount); // We'll only save pixel data for the middle bitmaps if our third dimension is hue. if (components == ColorSpectrumComponents.ValueSaturation || components == ColorSpectrumComponents.SaturationValue) { - bgraMiddle1PixelData.Capacity = pixelDataSize; - bgraMiddle2PixelData.Capacity = pixelDataSize; - bgraMiddle3PixelData.Capacity = pixelDataSize; - bgraMiddle4PixelData.Capacity = pixelDataSize; + bgraMiddle1PixelData = new ArrayList(pixelDataSize); + bgraMiddle2PixelData = new ArrayList(pixelDataSize); + bgraMiddle3PixelData = new ArrayList(pixelDataSize); + bgraMiddle4PixelData = new ArrayList(pixelDataSize); + } + else + { + bgraMiddle1PixelData = new ArrayList(0); + bgraMiddle2PixelData = new ArrayList(0); + bgraMiddle3PixelData = new ArrayList(0); + bgraMiddle4PixelData = new ArrayList(0); } - - bgraMaxPixelData.Capacity = pixelDataSize; - newHsvValues.Capacity = pixelCount; await Task.Run(() => { @@ -1132,12 +1139,12 @@ namespace Avalonia.Controls.Primitives double maxSaturation, double minValue, double maxValue, - List bgraMinPixelData, - List bgraMiddle1PixelData, - List bgraMiddle2PixelData, - List bgraMiddle3PixelData, - List bgraMiddle4PixelData, - List bgraMaxPixelData, + ArrayList bgraMinPixelData, + ArrayList bgraMiddle1PixelData, + ArrayList bgraMiddle2PixelData, + ArrayList bgraMiddle3PixelData, + ArrayList bgraMiddle4PixelData, + ArrayList bgraMaxPixelData, List newHsvValues) { double hMin = minHue; @@ -1292,12 +1299,12 @@ namespace Avalonia.Controls.Primitives double maxSaturation, double minValue, double maxValue, - List bgraMinPixelData, - List bgraMiddle1PixelData, - List bgraMiddle2PixelData, - List bgraMiddle3PixelData, - List bgraMiddle4PixelData, - List bgraMaxPixelData, + ArrayList bgraMinPixelData, + ArrayList bgraMiddle1PixelData, + ArrayList bgraMiddle2PixelData, + ArrayList bgraMiddle3PixelData, + ArrayList bgraMiddle4PixelData, + ArrayList bgraMaxPixelData, List newHsvValues) { double hMin = minHue; diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs new file mode 100644 index 0000000000..0b4c2f8579 --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs @@ -0,0 +1,71 @@ +namespace Avalonia.Controls.Primitives +{ + /// + /// A thin wrapper over an that allows some additional list-like functionality. + /// + /// + /// This is only for internal ColorPicker-related functionality and should not be used elsewhere. + /// It is added for performance to enjoy the simplicity of the IList.Add() method without requiring + /// an additional copy to turn a list into an array for bitmaps. + /// + /// The type of items in the array. + internal class ArrayList + { + private int _nextIndex = 0; + + /// + /// Initializes a new instance of the class. + /// + public ArrayList(int capacity) + { + Capacity = capacity; + Array = new T[capacity]; + } + + /// + /// Provides access to the underlying array by index. + /// This exists for simplification and the property + /// may also be used. + /// + /// The index of the item to get or set. + /// The item at the given index. + public T this[int i] + { + get => Array[i]; + set => Array[i] = value; + } + + /// + /// Gets the underlying array. + /// + public T[] Array { get; private set; } + + /// + /// Gets the fixed capacity/size of the array. + /// This must be set during construction. + /// + public int Capacity { get; private set; } + + /// + /// Adds the given item to the array at the next available index. + /// WARNING: This must be used carefully and only once, in sequence. + /// + /// The item to add. + public void Add(T item) + { + if (_nextIndex >= 0 && + _nextIndex < Capacity) + { + Array[_nextIndex] = item; + _nextIndex++; + } + else + { + // If necessary an exception could be thrown here + // throw new IndexOutOfRangeException(); + } + + return; + } + } +} diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index f2683e4677..2378813f52 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -37,7 +37,7 @@ namespace Avalonia.Controls.Primitives /// during calculation with the HSVA color model. /// This will ensure colors are always discernible regardless of saturation/value. /// A new bitmap representing a gradient of color component values. - public static async Task CreateComponentBitmapAsync( + public static async Task> CreateComponentBitmapAsync( int width, int height, Orientation orientation, @@ -49,14 +49,14 @@ namespace Avalonia.Controls.Primitives { if (width == 0 || height == 0) { - return new byte[0]; + return new ArrayList(0); } - var bitmap = await Task.Run(() => + var bitmap = await Task.Run>(() => { int pixelDataIndex = 0; double componentStep; - byte[] bgraPixelData; + ArrayList bgraPixelData; Color baseRgbColor = Colors.White; Color rgbColor; int bgraPixelDataHeight; @@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives // Allocate the buffer // BGRA formatted color components 1 byte each (4 bytes in a pixel) - bgraPixelData = new byte[width * height * 4]; + bgraPixelData = new ArrayList(width * height * 4); bgraPixelDataHeight = height * 4; bgraPixelDataWidth = width * 4; @@ -604,7 +604,7 @@ namespace Avalonia.Controls.Primitives /// The pixel height of the bitmap. /// A new . public static WriteableBitmap CreateBitmapFromPixelData( - IList bgraPixelData, + ArrayList bgraPixelData, int pixelWidth, int pixelHeight) { @@ -619,7 +619,7 @@ namespace Avalonia.Controls.Primitives using (var frameBuffer = bitmap.Lock()) { - Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); + Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length); } return bitmap; @@ -633,11 +633,11 @@ namespace Avalonia.Controls.Primitives /// The bitmap (in raw BGRA pre-multiplied alpha pixels). public static void UpdateBitmapFromPixelData( WriteableBitmap bitmap, - IList bgraPixelData) + ArrayList bgraPixelData) { using (var frameBuffer = bitmap.Lock()) { - Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); + Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length); } return; From 98a58217ce22d55f9343216fd4fbc727618c7306 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:25:24 -0400 Subject: [PATCH 07/37] Fix ColorSlider update/refresh for all property changes --- .../ColorSlider/ColorSlider.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 8bcdef5558..6f20238b70 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -385,11 +385,11 @@ namespace Avalonia.Controls.Primitives return; } - // Always keep the two color properties in sync if (change.Property == ColorProperty) { ignorePropertyChanged = true; + // Always keep the two color properties in sync HsvColor = Color.ToHsv(); SetColorToSliderValues(); @@ -402,7 +402,10 @@ namespace Avalonia.Controls.Primitives ignorePropertyChanged = false; } - else if (change.Property == ColorModelProperty) + else if (change.Property == ColorComponentProperty || + change.Property == ColorModelProperty || + change.Property == IsAlphaMaxForcedProperty || + change.Property == IsSaturationValueMaxForcedProperty) { ignorePropertyChanged = true; @@ -416,6 +419,7 @@ namespace Avalonia.Controls.Primitives { ignorePropertyChanged = true; + // Always keep the two color properties in sync Color = HsvColor.ToRgb(); SetColorToSliderValues(); @@ -440,6 +444,7 @@ namespace Avalonia.Controls.Primitives _backgroundBitmap = null; UpdateBackground(); + UpdatePseudoClasses(); } else if (change.Property == ValueProperty || change.Property == MinimumProperty || From 4f1d315b4b11cf84ca7c3b4ffc65c41d35e24436 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:26:28 -0400 Subject: [PATCH 08/37] Make all ColorSpectrum bitmaps globally accessible by the control for future disposal --- .../ColorSpectrum/ColorSpectrum.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 3604894833..6b06e1eac1 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -73,6 +73,8 @@ namespace Avalonia.Controls.Primitives private WriteableBitmap? _saturationMaximumBitmap; private WriteableBitmap? _valueBitmap; + private WriteableBitmap? _minBitmap; + private WriteableBitmap? _maxBitmap; // Fields used by UpdateEllipse() to ensure that it's using the data // associated with the last call to CreateBitmapsAndColorMap(), @@ -1084,28 +1086,28 @@ namespace Avalonia.Controls.Primitives ColorSpectrumComponents components2 = Components; - WriteableBitmap minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight); - WriteableBitmap maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight); + _minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight); + _maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight); switch (components2) { case ColorSpectrumComponents.HueValue: case ColorSpectrumComponents.ValueHue: - _saturationMinimumBitmap = minBitmap; - _saturationMaximumBitmap = maxBitmap; + _saturationMinimumBitmap = _minBitmap; + _saturationMaximumBitmap = _maxBitmap; break; case ColorSpectrumComponents.HueSaturation: case ColorSpectrumComponents.SaturationHue: - _valueBitmap = maxBitmap; + _valueBitmap = _maxBitmap; break; case ColorSpectrumComponents.ValueSaturation: case ColorSpectrumComponents.SaturationValue: - _hueRedBitmap = minBitmap; + _hueRedBitmap = _minBitmap; _hueYellowBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle1PixelData, pixelWidth, pixelHeight); _hueGreenBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle2PixelData, pixelWidth, pixelHeight); _hueCyanBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle3PixelData, pixelWidth, pixelHeight); _hueBlueBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle4PixelData, pixelWidth, pixelHeight); - _huePurpleBitmap = maxBitmap; + _huePurpleBitmap = _maxBitmap; break; } From 667b02d2c0d6e713980f0ad05c843d96e0c5a6f4 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:33:24 -0400 Subject: [PATCH 09/37] Disable WriteableBitmap re-use in ColorSlider due to crashes --- .../ColorSlider/ColorSlider.cs | 8 +++++++- .../Helpers/ColorPickerHelpers.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 6f20238b70..8b624958dd 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -135,10 +135,16 @@ namespace Avalonia.Controls.Primitives { if (_backgroundBitmap != null) { + // CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER + // // Re-use the existing WriteableBitmap // This assumes the height, width and byte counts are the same and must be set to null // elsewhere if that assumption is ever not true. - ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); + // ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); + + // ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES + //_backgroundBitmap?.Dispose(); + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); } else { diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index 2378813f52..01e5a0868f 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -627,6 +627,7 @@ namespace Avalonia.Controls.Primitives /// /// Updates the given with new, raw BGRA pre-multiplied alpha pixel data. + /// WARNING: THIS METHOD IS CURRENTLY PROVIDED AS REFERENCE BUT CAUSES INTERMITTENT CRASHES IF USED. /// WARNING: The bitmap's width, height and byte count MUST not have changed and MUST be enforced externally. /// /// The existing to update. From bc927d03129fa201248d2bcab78143f7a1c8f93f Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 9 Oct 2022 21:07:58 -0400 Subject: [PATCH 10/37] Remove ColorSlider background disposal on attach/detach from visual tree --- .../ColorSlider/ColorSlider.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 8b624958dd..0b9f7f9146 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -45,20 +45,12 @@ namespace Avalonia.Controls.Primitives protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - - // Bitmaps were released when detached from the visual tree so they must be re-built - UpdateBackground(); } /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); - - // Clean-up all bitmaps - // https://github.com/AvaloniaUI/Avalonia/issues/9051 - _backgroundBitmap?.Dispose(); - _backgroundBitmap = null; } /// From 5f9b193b311f030df6463c1188adf4e698239655 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 12 Oct 2022 22:04:13 +0100 Subject: [PATCH 11/37] fix build on net7 sdks with scoped keyword. --- src/Avalonia.Base/Utilities/IdentifierParser.cs | 2 +- .../Markup/Parsers/BindingExpressionGrammar.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Utilities/IdentifierParser.cs b/src/Avalonia.Base/Utilities/IdentifierParser.cs index b105d0746b..ee176a6b85 100644 --- a/src/Avalonia.Base/Utilities/IdentifierParser.cs +++ b/src/Avalonia.Base/Utilities/IdentifierParser.cs @@ -8,7 +8,7 @@ namespace Avalonia.Utilities #endif static class IdentifierParser { - public static ReadOnlySpan ParseIdentifier(this ref CharacterReader r) + public static ReadOnlySpan ParseIdentifier(this scoped ref CharacterReader r) { if (IsValidIdentifierStart(r.Peek)) { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs index 439bc15243..0a9fbcfacb 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs @@ -168,7 +168,7 @@ namespace Avalonia.Markup.Parsers } } - private static State ParseAttachedProperty(ref CharacterReader r, List nodes) + private static State ParseAttachedProperty(scoped ref CharacterReader r, List nodes) { var (ns, owner) = ParseTypeName(ref r); @@ -318,7 +318,7 @@ namespace Avalonia.Markup.Parsers return State.AfterMember; } - private static TypeName ParseTypeName(ref CharacterReader r) + private static TypeName ParseTypeName(scoped ref CharacterReader r) { ReadOnlySpan ns, typeName; ns = ReadOnlySpan.Empty; From d1e58bfde540935691c7515138982fed7ede3269 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 12 Oct 2022 22:08:18 +0100 Subject: [PATCH 12/37] use net7 rc2 sdk. --- azure-pipelines.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 33b2dc670a..903f9e3843 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -35,9 +35,9 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12' + displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.1.22431.12 + version: 7.0.100-rc.2.22477.23 - task: CmdLine@2 displayName: 'Install Workloads' @@ -72,9 +72,9 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12' + displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.1.22431.12 + version: 7.0.100-rc.2.22477.23 - task: CmdLine@2 displayName: 'Install Workloads' @@ -143,9 +143,9 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12' + displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.1.22431.12 + version: 7.0.100-rc.2.22477.23 - task: CmdLine@2 displayName: 'Install Workloads' From 7bf48cf7de6350dae540949573858b36ab94ffb2 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 17:15:51 +0200 Subject: [PATCH 13/37] feat: Enable CA1847 --- .editorconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.editorconfig b/.editorconfig index 337760636b..dfc28388fc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -141,6 +141,8 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme dotnet_diagnostic.CA1802.severity = warning # CA1825: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = warning +#CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters +dotnet_diagnostic.CA1847.severity = warning # Wrapping preferences csharp_wrap_before_ternary_opsigns = false From 2f8273ae6484aad6712f07daf24f0caf4a36a821 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 17:16:23 +0200 Subject: [PATCH 14/37] fix: Address CA1847 rule fix: CA1874 fix: CA1847 --- src/Avalonia.Base/AvaloniaProperty.cs | 8 +++++++- src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 2 +- src/Avalonia.Base/Media/UnicodeRange.cs | 7 ++++++- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 2 +- .../NumericUpDown/NumericUpDown.cs | 4 ++-- src/Avalonia.Remote.Protocol/MetsysBson.cs | 14 +++++++------- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 8 +++++++- .../Converters/TimeSpanTypeConverter.cs | 10 ++++++++-- 8 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index fd43ced196..b3c3de919a 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -12,6 +12,12 @@ namespace Avalonia /// public abstract class AvaloniaProperty : IEquatable, IPropertyInfo { +#if NET6_0_OR_GREATER + internal const char Period = '.'; +#else + internal const string Period = "."; +#endif + /// /// Represents an unset property value. /// @@ -41,7 +47,7 @@ namespace Avalonia { _ = name ?? throw new ArgumentNullException(nameof(name)); - if (name.Contains(".")) + if (name.Contains(Period)) { throw new ArgumentException("'name' may not contain periods."); } diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index d86e723b38..01c7160129 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -228,7 +228,7 @@ namespace Avalonia _ = type ?? throw new ArgumentNullException(nameof(type)); _ = name ?? throw new ArgumentNullException(nameof(name)); - if (name.Contains(".")) + if (name.Contains(AvaloniaProperty.Period)) { throw new InvalidOperationException("Attached properties not supported."); } diff --git a/src/Avalonia.Base/Media/UnicodeRange.cs b/src/Avalonia.Base/Media/UnicodeRange.cs index e2338b9b26..33f7f656ba 100644 --- a/src/Avalonia.Base/Media/UnicodeRange.cs +++ b/src/Avalonia.Base/Media/UnicodeRange.cs @@ -104,6 +104,11 @@ namespace Avalonia.Media public readonly struct UnicodeRangeSegment { +#if NET6_0_OR_GREATER + private const char Question = '?'; +#else + private const string Question = "?"; +#endif private static Regex s_regex = new Regex(@"^(?:[uU]\+)?(?:([0-9a-fA-F](?:[0-9a-fA-F?]{1,5})?))$"); public UnicodeRangeSegment(int start, int end) @@ -163,7 +168,7 @@ namespace Avalonia.Media throw new FormatException("Could not parse specified Unicode range segment."); } - if (!single.Value.Contains("?")) + if (!single.Value.Contains(Question)) { start = int.Parse(single.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); end = start; diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 00ebcab70e..65ec4cc54c 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -597,7 +597,7 @@ namespace Avalonia.Controls.Primitives for (int i = presenter.Classes.Count - 1; i >= 0; i--) { if (!classes.Contains(presenter.Classes[i]) && - !presenter.Classes[i].Contains(":")) + !presenter.Classes[i].Contains(':')) { presenter.Classes.RemoveAt(i); } diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index b607ced10a..21373c6b76 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -1151,8 +1151,8 @@ namespace Avalonia.Controls if (PIndex >= 0) { //stringToTest contains a "P" between 2 "'", it's considered as text, not percent - var isText = stringToTest.Substring(0, PIndex).Contains("'") - && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains("'"); + var isText = stringToTest.Substring(0, PIndex).Contains('\'') + && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains('\''); return !isText; } diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index 524ec09ca9..6abece6bf3 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -1364,13 +1364,13 @@ namespace Metsys.Bson var optionsString = ReadName(); var options = RegexOptions.None; - if (optionsString.Contains("e")) options = options | RegexOptions.ECMAScript; - if (optionsString.Contains("i")) options = options | RegexOptions.IgnoreCase; - if (optionsString.Contains("l")) options = options | RegexOptions.CultureInvariant; - if (optionsString.Contains("m")) options = options | RegexOptions.Multiline; - if (optionsString.Contains("s")) options = options | RegexOptions.Singleline; - if (optionsString.Contains("w")) options = options | RegexOptions.IgnorePatternWhitespace; - if (optionsString.Contains("x")) options = options | RegexOptions.ExplicitCapture; + if (optionsString.Contains('e')) options = options | RegexOptions.ECMAScript; + if (optionsString.Contains('i')) options = options | RegexOptions.IgnoreCase; + if (optionsString.Contains('l')) options = options | RegexOptions.CultureInvariant; + if (optionsString.Contains('m')) options = options | RegexOptions.Multiline; + if (optionsString.Contains('s')) options = options | RegexOptions.Singleline; + if (optionsString.Contains('w')) options = options | RegexOptions.IgnorePatternWhitespace; + if (optionsString.Contains('x')) options = options | RegexOptions.ExplicitCapture; return new Regex(pattern, options); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index d907bcbef9..48a7ba99e3 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -14,6 +14,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { class AvaloniaXamlIlLanguageParseIntrinsics { +#if NET6_0_OR_GREATER + private const string Colon = ":"; +#else + private const string Colon = ":"; +#endif + public static bool TryConvert(AstTransformationContext context, IXamlAstValueNode node, string text, IXamlType type, AvaloniaXamlIlWellKnownTypes types, out IXamlAstValueNode result) { if (type.FullName == "System.TimeSpan") @@ -23,7 +29,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions if (!TimeSpan.TryParse(tsText, CultureInfo.InvariantCulture, out var timeSpan)) { // // shorthand seconds format (ie. "0.25") - if (!tsText.Contains(":") && double.TryParse(tsText, + if (!tsText.Contains(Colon) && double.TryParse(tsText, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var seconds)) timeSpan = TimeSpan.FromSeconds(seconds); diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs index 1e42a46875..3fd653be55 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs @@ -7,6 +7,12 @@ namespace Avalonia.Markup.Xaml.Converters public class TimeSpanTypeConverter : System.ComponentModel.TimeSpanConverter { +#if NET6_0_OR_GREATER + private const char Colon = ':'; +#else + private const string Colon = ":"; +#endif + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return base.CanConvertFrom(context, sourceType); @@ -15,7 +21,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { var valueStr = (string)value; - if (!valueStr.Contains(":")) + if (!valueStr.Contains(Colon)) { // shorthand seconds format (ie. "0.25") var secs = double.Parse(valueStr, CultureInfo.InvariantCulture); @@ -25,4 +31,4 @@ namespace Avalonia.Markup.Xaml.Converters return base.ConvertFrom(context, culture, value); } } -} \ No newline at end of file +} From 11a73142c08af4a7c74a61c4493c7ab050a75771 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 15 Oct 2022 14:31:04 -0400 Subject: [PATCH 15/37] Add additional ColorPicker sample to control catalog created from code-behind --- .../ControlCatalog/Pages/ColorPickerPage.xaml | 7 +++---- .../ControlCatalog/Pages/ColorPickerPage.xaml.cs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml index ad54eb95fc..3cb761fb34 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml @@ -14,13 +14,15 @@ - @@ -56,8 +58,5 @@ IsAccentColorsVisible="False" HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> - - diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs index 6e017e381f..4671bbdb7c 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs @@ -1,6 +1,8 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Layout; using Avalonia.Markup.Xaml; +using Avalonia.Media; namespace ControlCatalog.Pages { @@ -9,6 +11,20 @@ namespace ControlCatalog.Pages public ColorPickerPage() { InitializeComponent(); + + var layoutRoot = this.GetControl("LayoutRoot"); + + // ColorPicker added from code-behind + var colorPicker = new ColorPicker() + { + Color = Colors.Blue, + Margin = new Thickness(0, 50, 0, 0), + HorizontalAlignment = HorizontalAlignment.Center, + }; + Grid.SetColumn(colorPicker, 2); + Grid.SetRow(colorPicker, 1); + + layoutRoot.Children.Add(colorPicker); } private void InitializeComponent() From 2575a10fb09677ac3f131ab3715b20c46b588397 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 15 Oct 2022 14:33:47 -0400 Subject: [PATCH 16/37] Add and implement IsHostedInColorPicker property to ColorView This works-around some lifecycle issues by allowing the ColorView to specially handle how it handles property changes based on whether or not it is within a ColorPicker. --- .../ColorPicker/ColorPicker.cs | 3 --- .../ColorView/ColorView.Properties.cs | 24 +++++++++++++++++++ .../ColorView/ColorView.cs | 20 ++++++++++++++++ .../Themes/Fluent/ColorPicker.xaml | 4 ++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs index 39369bcbdb..92d8535272 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs @@ -11,9 +11,6 @@ /// public ColorPicker() : base() { - // Completely ignore property changes here - // The ColorView in the control template is responsible to manage this - base.ignorePropertyChanged = true; } } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs index b76059037b..d1ee375e28 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs @@ -148,6 +148,14 @@ namespace Avalonia.Controls nameof(IsHexInputVisible), true); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHostedInColorPickerProperty = + AvaloniaProperty.Register( + nameof(IsHostedInColorPicker), + false); + /// /// Defines the property. /// @@ -395,6 +403,22 @@ namespace Avalonia.Controls set => SetValue(IsHexInputVisibleProperty, value); } + /// + /// Gets or sets a value indicating whether this is hosted + /// inside a 's control template. + /// + /// + /// This is a special property to change how the internally + /// processes property updates. When a is hosted within a + /// , it does not need to handle most property changes. + /// Instead, the primary calculations are done within the itself. + /// + public bool IsHostedInColorPicker + { + get => GetValue(IsHostedInColorPickerProperty); + set => SetValue(IsHostedInColorPickerProperty, value); + } + /// public int MaxHue { diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index 89f1afb1ac..b0d5250c50 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -206,6 +206,26 @@ namespace Avalonia.Controls return; } + // Special handling for when hosted within a ColorPicker and the ColorPicker is + // responsible for most of the internal processing and property updates. + if (IsHostedInColorPicker) + { + if (change.Property == ColorProperty || + change.Property == HsvColorProperty) + { + ignorePropertyChanged = true; + + // The Hex text isn't currently bound with the ColorPicker and therefore + // must still be updated here. + SetColorToHexTextBox(); + + ignorePropertyChanged = false; + } + + base.OnPropertyChanged(change); + return; + } + // Always keep the two color properties in sync if (change.Property == ColorProperty) { diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 74a1df4991..acdd06480f 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -43,7 +43,11 @@ + Date: Sat, 15 Oct 2022 14:58:22 -0400 Subject: [PATCH 17/37] Make some todo-areas of the code more clear --- src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs | 4 ++-- .../Helpers/ColorPickerHelpers.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 0b9f7f9146..ec08e96d87 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -127,14 +127,14 @@ namespace Avalonia.Controls.Primitives { if (_backgroundBitmap != null) { - // CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER + // TODO: CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER // // Re-use the existing WriteableBitmap // This assumes the height, width and byte counts are the same and must be set to null // elsewhere if that assumption is ever not true. // ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); - // ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES + // TODO: ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES //_backgroundBitmap?.Dispose(); _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); } diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index 01e5a0868f..819d745772 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -627,7 +627,7 @@ namespace Avalonia.Controls.Primitives /// /// Updates the given with new, raw BGRA pre-multiplied alpha pixel data. - /// WARNING: THIS METHOD IS CURRENTLY PROVIDED AS REFERENCE BUT CAUSES INTERMITTENT CRASHES IF USED. + /// TODO: THIS METHOD IS CURRENTLY PROVIDED AS REFERENCE BUT CAUSES INTERMITTENT CRASHES IF USED. /// WARNING: The bitmap's width, height and byte count MUST not have changed and MUST be enforced externally. /// /// The existing to update. From e96772e7076713f18e6104e73f8d2658016743d2 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 15 Oct 2022 15:29:54 -0400 Subject: [PATCH 18/37] Remove ThirdComponentConverter replacing it with ColorSpectrum.ThirdComponent property --- .../ControlCatalog/Pages/ColorPickerPage.xaml | 1 - .../ColorSpectrum/ColorSpectrum.Properties.cs | 24 +++++++++ .../ColorSpectrum/ColorSpectrum.cs | 17 ++++++ .../Converters/ThirdComponentConverter.cs | 53 ------------------- .../Themes/Fluent/ColorView.xaml | 3 +- .../Themes/Simple/ColorView.xaml | 3 +- 6 files changed, 43 insertions(+), 58 deletions(-) delete mode 100644 src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml index 3cb761fb34..69ceaea328 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml @@ -11,7 +11,6 @@ x:Class="ControlCatalog.Pages.ColorPickerPage"> - + /// Defines the property. + /// + public static readonly StyledProperty ThirdComponentProperty = + AvaloniaProperty.Register( + nameof(ThirdComponent), + ColorComponent.Component3); // Value + /// /// Gets or sets the currently selected color in the RGB color model. /// @@ -218,5 +226,21 @@ namespace Avalonia.Controls.Primitives get => GetValue(ShapeProperty); set => SetValue(ShapeProperty, value); } + + /// + /// Gets the third HSV color component that is NOT displayed by the spectrum. + /// This is automatically calculated from the property. + /// + /// + /// This property should be used for any external color slider that represents the + /// third component of the color. Note that this property uses the generic + /// type instead of the more accurate + /// to allow direct usage by the generalized color sliders. + /// + public ColorComponent ThirdComponent + { + get => GetValue(ThirdComponentProperty); + private set => SetValue(ThirdComponentProperty, value); + } } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 6b06e1eac1..f0ed89fb3a 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -503,6 +503,23 @@ namespace Avalonia.Controls.Primitives } else if (change.Property == ComponentsProperty) { + // Calculate and update the ThirdComponent value + switch (Components) + { + case ColorSpectrumComponents.HueSaturation: + case ColorSpectrumComponents.SaturationHue: + ThirdComponent = (ColorComponent)HsvComponent.Value; + break; + case ColorSpectrumComponents.HueValue: + case ColorSpectrumComponents.ValueHue: + ThirdComponent = (ColorComponent)HsvComponent.Saturation; + break; + case ColorSpectrumComponents.SaturationValue: + case ColorSpectrumComponents.ValueSaturation: + ThirdComponent = (ColorComponent)HsvComponent.Hue; + break; + } + CreateBitmapsAndColorMap(); } diff --git a/src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs b/src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs deleted file mode 100644 index 11e33c74f0..0000000000 --- a/src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Globalization; -using Avalonia.Data.Converters; - -namespace Avalonia.Controls.Primitives.Converters -{ - /// - /// Gets the third corresponding with a given - /// that represents the other two components. - /// - /// - /// This is a highly-specialized converter for the color picker. - /// - public class ThirdComponentConverter : IValueConverter - { - /// - public object? Convert( - object? value, - Type targetType, - object? parameter, - CultureInfo culture) - { - if (value is ColorSpectrumComponents components) - { - // Note: Alpha is not relevant here - switch (components) - { - case ColorSpectrumComponents.HueSaturation: - case ColorSpectrumComponents.SaturationHue: - return (ColorComponent)HsvComponent.Value; - case ColorSpectrumComponents.HueValue: - case ColorSpectrumComponents.ValueHue: - return (ColorComponent)HsvComponent.Saturation; - case ColorSpectrumComponents.SaturationValue: - case ColorSpectrumComponents.ValueSaturation: - return (ColorComponent)HsvComponent.Hue; - } - } - - return AvaloniaProperty.UnsetValue; - } - - /// - public object? ConvertBack( - object? value, - Type targetType, - object? parameter, - CultureInfo culture) - { - return AvaloniaProperty.UnsetValue; - } - } -} diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index cf836aff4a..b3c6d1a430 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -8,7 +8,6 @@ x:CompileBindings="True"> - @@ -368,7 +367,7 @@ IsSaturationValueMaxForced="False" Orientation="Vertical" ColorModel="Hsva" - ColorComponent="{Binding Components, ElementName=ColorSpectrum, Converter={StaticResource ThirdComponentConverter}}" + ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}" HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}" HorizontalAlignment="Center" VerticalAlignment="Stretch" diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml index 380e655949..860dbe11d0 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml @@ -8,7 +8,6 @@ x:CompileBindings="True"> - @@ -330,7 +329,7 @@ IsSaturationValueMaxForced="False" Orientation="Vertical" ColorModel="Hsva" - ColorComponent="{Binding Components, ElementName=ColorSpectrum, Converter={StaticResource ThirdComponentConverter}}" + ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}" HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}" HorizontalAlignment="Center" VerticalAlignment="Stretch" From 7b69d2ae5744f97257a56d1d09dac98b6edd5e31 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 16 Oct 2022 12:17:47 -0400 Subject: [PATCH 19/37] Revert "Add and implement IsHostedInColorPicker property to ColorView" This reverts commit 2575a10fb09677ac3f131ab3715b20c46b588397. --- .../ColorPicker/ColorPicker.cs | 3 +++ .../ColorView/ColorView.Properties.cs | 24 ------------------- .../ColorView/ColorView.cs | 20 ---------------- .../Themes/Fluent/ColorPicker.xaml | 4 ---- 4 files changed, 3 insertions(+), 48 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs index 92d8535272..39369bcbdb 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs @@ -11,6 +11,9 @@ /// public ColorPicker() : base() { + // Completely ignore property changes here + // The ColorView in the control template is responsible to manage this + base.ignorePropertyChanged = true; } } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs index d1ee375e28..b76059037b 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs @@ -148,14 +148,6 @@ namespace Avalonia.Controls nameof(IsHexInputVisible), true); - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHostedInColorPickerProperty = - AvaloniaProperty.Register( - nameof(IsHostedInColorPicker), - false); - /// /// Defines the property. /// @@ -403,22 +395,6 @@ namespace Avalonia.Controls set => SetValue(IsHexInputVisibleProperty, value); } - /// - /// Gets or sets a value indicating whether this is hosted - /// inside a 's control template. - /// - /// - /// This is a special property to change how the internally - /// processes property updates. When a is hosted within a - /// , it does not need to handle most property changes. - /// Instead, the primary calculations are done within the itself. - /// - public bool IsHostedInColorPicker - { - get => GetValue(IsHostedInColorPickerProperty); - set => SetValue(IsHostedInColorPickerProperty, value); - } - /// public int MaxHue { diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index b0d5250c50..89f1afb1ac 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -206,26 +206,6 @@ namespace Avalonia.Controls return; } - // Special handling for when hosted within a ColorPicker and the ColorPicker is - // responsible for most of the internal processing and property updates. - if (IsHostedInColorPicker) - { - if (change.Property == ColorProperty || - change.Property == HsvColorProperty) - { - ignorePropertyChanged = true; - - // The Hex text isn't currently bound with the ColorPicker and therefore - // must still be updated here. - SetColorToHexTextBox(); - - ignorePropertyChanged = false; - } - - base.OnPropertyChanged(change); - return; - } - // Always keep the two color properties in sync if (change.Property == ColorProperty) { diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index acdd06480f..74a1df4991 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -43,11 +43,7 @@ - Date: Sun, 16 Oct 2022 12:35:25 -0400 Subject: [PATCH 20/37] Hand-off control to the ColorView after template is applied in ColorPicker This solution doesn't require the hacky IsHostedInColorPicker property --- .../ColorPicker/ColorPicker.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs index 39369bcbdb..29f9f3c571 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Controls +using Avalonia.Controls.Primitives; + +namespace Avalonia.Controls { /// /// Presents a color for user editing using a spectrum, palette and component sliders within a drop down. @@ -11,8 +13,33 @@ /// public ColorPicker() : base() { - // Completely ignore property changes here - // The ColorView in the control template is responsible to manage this + } + + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + // Until this point the ColorPicker itself is responsible to process property updates. + // This, for example, syncs Color with HsvColor and updates primitive controls. + // + // However, when the template is created, hand-off this change processing to the + // ColorView within the control template itself. Remember ColorPicker derives from + // ColorView so we don't want two instances of the same logic fighting each other. + // It is best to hand-off to the ColorView in the control template because that is the + // primary point of user-interaction for the overall control. It also simplifies binding. + // + // Keep in mind this hand-off is not possible until the template controls are created + // which is done after the ColorPicker is instantiated. The ColorPicker must still + // process updates before the template is applied to ensure all property changes in + // XAML or object initializers are handled correctly. Otherwise, there can be bugs + // such as setting the Color property doesn't work because the HsvColor is never updated + // and then the Color value is lost once the template loads (and the template ColorView + // takes over). + // + // In order to complete this hand-off, completely ignore property changes here in the + // ColorPicker. This means the ColorView in the control template is now responsible to + // process property changes and handle primary calculations. base.ignorePropertyChanged = true; } } From 221bec69d4e453fdceeac34895ca583ab4aa0831 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 17 Oct 2022 12:39:06 +0200 Subject: [PATCH 21/37] Revert "fix: Address CA1847 rule" This reverts commit 2f8273ae6484aad6712f07daf24f0caf4a36a821. --- src/Avalonia.Base/AvaloniaProperty.cs | 8 +------- src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 2 +- src/Avalonia.Base/Media/UnicodeRange.cs | 7 +------ src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 2 +- .../NumericUpDown/NumericUpDown.cs | 4 ++-- src/Avalonia.Remote.Protocol/MetsysBson.cs | 14 +++++++------- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 8 +------- .../Converters/TimeSpanTypeConverter.cs | 10 ++-------- 8 files changed, 16 insertions(+), 39 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index b3c3de919a..fd43ced196 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -12,12 +12,6 @@ namespace Avalonia /// public abstract class AvaloniaProperty : IEquatable, IPropertyInfo { -#if NET6_0_OR_GREATER - internal const char Period = '.'; -#else - internal const string Period = "."; -#endif - /// /// Represents an unset property value. /// @@ -47,7 +41,7 @@ namespace Avalonia { _ = name ?? throw new ArgumentNullException(nameof(name)); - if (name.Contains(Period)) + if (name.Contains(".")) { throw new ArgumentException("'name' may not contain periods."); } diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 01c7160129..d86e723b38 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -228,7 +228,7 @@ namespace Avalonia _ = type ?? throw new ArgumentNullException(nameof(type)); _ = name ?? throw new ArgumentNullException(nameof(name)); - if (name.Contains(AvaloniaProperty.Period)) + if (name.Contains(".")) { throw new InvalidOperationException("Attached properties not supported."); } diff --git a/src/Avalonia.Base/Media/UnicodeRange.cs b/src/Avalonia.Base/Media/UnicodeRange.cs index 33f7f656ba..e2338b9b26 100644 --- a/src/Avalonia.Base/Media/UnicodeRange.cs +++ b/src/Avalonia.Base/Media/UnicodeRange.cs @@ -104,11 +104,6 @@ namespace Avalonia.Media public readonly struct UnicodeRangeSegment { -#if NET6_0_OR_GREATER - private const char Question = '?'; -#else - private const string Question = "?"; -#endif private static Regex s_regex = new Regex(@"^(?:[uU]\+)?(?:([0-9a-fA-F](?:[0-9a-fA-F?]{1,5})?))$"); public UnicodeRangeSegment(int start, int end) @@ -168,7 +163,7 @@ namespace Avalonia.Media throw new FormatException("Could not parse specified Unicode range segment."); } - if (!single.Value.Contains(Question)) + if (!single.Value.Contains("?")) { start = int.Parse(single.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); end = start; diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 65ec4cc54c..00ebcab70e 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -597,7 +597,7 @@ namespace Avalonia.Controls.Primitives for (int i = presenter.Classes.Count - 1; i >= 0; i--) { if (!classes.Contains(presenter.Classes[i]) && - !presenter.Classes[i].Contains(':')) + !presenter.Classes[i].Contains(":")) { presenter.Classes.RemoveAt(i); } diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index 21373c6b76..b607ced10a 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -1151,8 +1151,8 @@ namespace Avalonia.Controls if (PIndex >= 0) { //stringToTest contains a "P" between 2 "'", it's considered as text, not percent - var isText = stringToTest.Substring(0, PIndex).Contains('\'') - && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains('\''); + var isText = stringToTest.Substring(0, PIndex).Contains("'") + && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains("'"); return !isText; } diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index 6abece6bf3..524ec09ca9 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -1364,13 +1364,13 @@ namespace Metsys.Bson var optionsString = ReadName(); var options = RegexOptions.None; - if (optionsString.Contains('e')) options = options | RegexOptions.ECMAScript; - if (optionsString.Contains('i')) options = options | RegexOptions.IgnoreCase; - if (optionsString.Contains('l')) options = options | RegexOptions.CultureInvariant; - if (optionsString.Contains('m')) options = options | RegexOptions.Multiline; - if (optionsString.Contains('s')) options = options | RegexOptions.Singleline; - if (optionsString.Contains('w')) options = options | RegexOptions.IgnorePatternWhitespace; - if (optionsString.Contains('x')) options = options | RegexOptions.ExplicitCapture; + if (optionsString.Contains("e")) options = options | RegexOptions.ECMAScript; + if (optionsString.Contains("i")) options = options | RegexOptions.IgnoreCase; + if (optionsString.Contains("l")) options = options | RegexOptions.CultureInvariant; + if (optionsString.Contains("m")) options = options | RegexOptions.Multiline; + if (optionsString.Contains("s")) options = options | RegexOptions.Singleline; + if (optionsString.Contains("w")) options = options | RegexOptions.IgnorePatternWhitespace; + if (optionsString.Contains("x")) options = options | RegexOptions.ExplicitCapture; return new Regex(pattern, options); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 48a7ba99e3..d907bcbef9 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -14,12 +14,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { class AvaloniaXamlIlLanguageParseIntrinsics { -#if NET6_0_OR_GREATER - private const string Colon = ":"; -#else - private const string Colon = ":"; -#endif - public static bool TryConvert(AstTransformationContext context, IXamlAstValueNode node, string text, IXamlType type, AvaloniaXamlIlWellKnownTypes types, out IXamlAstValueNode result) { if (type.FullName == "System.TimeSpan") @@ -29,7 +23,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions if (!TimeSpan.TryParse(tsText, CultureInfo.InvariantCulture, out var timeSpan)) { // // shorthand seconds format (ie. "0.25") - if (!tsText.Contains(Colon) && double.TryParse(tsText, + if (!tsText.Contains(":") && double.TryParse(tsText, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var seconds)) timeSpan = TimeSpan.FromSeconds(seconds); diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs index 3fd653be55..1e42a46875 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs @@ -7,12 +7,6 @@ namespace Avalonia.Markup.Xaml.Converters public class TimeSpanTypeConverter : System.ComponentModel.TimeSpanConverter { -#if NET6_0_OR_GREATER - private const char Colon = ':'; -#else - private const string Colon = ":"; -#endif - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return base.CanConvertFrom(context, sourceType); @@ -21,7 +15,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { var valueStr = (string)value; - if (!valueStr.Contains(Colon)) + if (!valueStr.Contains(":")) { // shorthand seconds format (ie. "0.25") var secs = double.Parse(valueStr, CultureInfo.InvariantCulture); @@ -31,4 +25,4 @@ namespace Avalonia.Markup.Xaml.Converters return base.ConvertFrom(context, culture, value); } } -} +} \ No newline at end of file From 79754b9feb567e4f379c4dcff9e7b2f8e75e3da3 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 17 Oct 2022 14:20:35 +0200 Subject: [PATCH 22/37] fix: Address CA1847 rule --- src/Avalonia.Base/AvaloniaProperty.cs | 2 +- src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 2 +- .../Compatibility/StringCompatibilityExtensions.cs | 12 ++++++++++++ src/Avalonia.Base/Media/UnicodeRange.cs | 2 +- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 2 +- .../NumericUpDown/NumericUpDown.cs | 4 ++-- src/Avalonia.Remote.Protocol/MetsysBson.cs | 14 +++++++------- .../Converters/TimeSpanTypeConverter.cs | 4 ++-- 8 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index fd43ced196..46ba4082fb 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -41,7 +41,7 @@ namespace Avalonia { _ = name ?? throw new ArgumentNullException(nameof(name)); - if (name.Contains(".")) + if (name.Contains('.')) { throw new ArgumentException("'name' may not contain periods."); } diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index d86e723b38..62265d3c59 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -228,7 +228,7 @@ namespace Avalonia _ = type ?? throw new ArgumentNullException(nameof(type)); _ = name ?? throw new ArgumentNullException(nameof(name)); - if (name.Contains(".")) + if (name.Contains('.')) { throw new InvalidOperationException("Attached properties not supported."); } diff --git a/src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs b/src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs new file mode 100644 index 0000000000..45e41b44d6 --- /dev/null +++ b/src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs @@ -0,0 +1,12 @@ +using System.Runtime.CompilerServices; + +namespace System; + +#if !NET6_0_OR_GREATER +public static class StringCompatibilityExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Contains(this string str, char search) => + str.Contains(search.ToString()); +} +#endif diff --git a/src/Avalonia.Base/Media/UnicodeRange.cs b/src/Avalonia.Base/Media/UnicodeRange.cs index e2338b9b26..344b85bae9 100644 --- a/src/Avalonia.Base/Media/UnicodeRange.cs +++ b/src/Avalonia.Base/Media/UnicodeRange.cs @@ -163,7 +163,7 @@ namespace Avalonia.Media throw new FormatException("Could not parse specified Unicode range segment."); } - if (!single.Value.Contains("?")) + if (!single.Value.Contains('?')) { start = int.Parse(single.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); end = start; diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 00ebcab70e..65ec4cc54c 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -597,7 +597,7 @@ namespace Avalonia.Controls.Primitives for (int i = presenter.Classes.Count - 1; i >= 0; i--) { if (!classes.Contains(presenter.Classes[i]) && - !presenter.Classes[i].Contains(":")) + !presenter.Classes[i].Contains(':')) { presenter.Classes.RemoveAt(i); } diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index b607ced10a..21373c6b76 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -1151,8 +1151,8 @@ namespace Avalonia.Controls if (PIndex >= 0) { //stringToTest contains a "P" between 2 "'", it's considered as text, not percent - var isText = stringToTest.Substring(0, PIndex).Contains("'") - && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains("'"); + var isText = stringToTest.Substring(0, PIndex).Contains('\'') + && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains('\''); return !isText; } diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index 524ec09ca9..6abece6bf3 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -1364,13 +1364,13 @@ namespace Metsys.Bson var optionsString = ReadName(); var options = RegexOptions.None; - if (optionsString.Contains("e")) options = options | RegexOptions.ECMAScript; - if (optionsString.Contains("i")) options = options | RegexOptions.IgnoreCase; - if (optionsString.Contains("l")) options = options | RegexOptions.CultureInvariant; - if (optionsString.Contains("m")) options = options | RegexOptions.Multiline; - if (optionsString.Contains("s")) options = options | RegexOptions.Singleline; - if (optionsString.Contains("w")) options = options | RegexOptions.IgnorePatternWhitespace; - if (optionsString.Contains("x")) options = options | RegexOptions.ExplicitCapture; + if (optionsString.Contains('e')) options = options | RegexOptions.ECMAScript; + if (optionsString.Contains('i')) options = options | RegexOptions.IgnoreCase; + if (optionsString.Contains('l')) options = options | RegexOptions.CultureInvariant; + if (optionsString.Contains('m')) options = options | RegexOptions.Multiline; + if (optionsString.Contains('s')) options = options | RegexOptions.Singleline; + if (optionsString.Contains('w')) options = options | RegexOptions.IgnorePatternWhitespace; + if (optionsString.Contains('x')) options = options | RegexOptions.ExplicitCapture; return new Regex(pattern, options); } diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs index 1e42a46875..1b6fbcef5c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs @@ -15,7 +15,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { var valueStr = (string)value; - if (!valueStr.Contains(":")) + if (!valueStr.Contains(':')) { // shorthand seconds format (ie. "0.25") var secs = double.Parse(valueStr, CultureInfo.InvariantCulture); @@ -25,4 +25,4 @@ namespace Avalonia.Markup.Xaml.Converters return base.ConvertFrom(context, culture, value); } } -} \ No newline at end of file +} From 6cc1215989ad440bc989a730956e6e51ee7d674e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Oct 2022 15:13:40 +0200 Subject: [PATCH 23/37] Initialize DevTools MainWindow theme in code. Previously if no theme was included in App.xaml, then DevTools would fail to open because the `StaticResource` lookup would fail due to the window's XAML not being parsed yet. --- src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml | 3 +-- .../Diagnostics/Views/MainWindow.xaml.cs | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 1270dbaa62..d2b31fdb20 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -3,8 +3,7 @@ xmlns:views="clr-namespace:Avalonia.Diagnostics.Views" xmlns:diag="clr-namespace:Avalonia.Diagnostics" Title="Avalonia DevTools" - x:Class="Avalonia.Diagnostics.Views.MainWindow" - Theme="{StaticResource {x:Type Window}}"> + x:Class="Avalonia.Diagnostics.Views.MainWindow"> diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index c81997f2cb..e6e630112b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -28,6 +28,11 @@ namespace Avalonia.Diagnostics.Views { InitializeComponent(); + // Apply the SimpleTheme.Window theme; this must be done after the XAML is parsed as + // the theme is included in the MainWindow's XAML. + if (Theme is null && this.FindResource(typeof(Window)) is ControlTheme windowTheme) + Theme = windowTheme; + _keySubscription = InputManager.Instance?.Process .OfType() .Where(x => x.Type == RawKeyEventType.KeyDown) From 2435d2e87b585832f76fb7ad7f5b1e397dd49789 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Oct 2022 15:34:22 +0200 Subject: [PATCH 24/37] Fix DataGrid cell contents lookup in DevTools. --- .../MarkupExtensions/StaticResourceExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index d462a2210e..f28f7bc626 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -56,7 +56,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions // We need to implement compile-time merging of resource dictionaries and this // hack can be removed. if (previousWasControlTheme && - parent is ResourceDictionary hack && + parent is IResourceProvider hack && hack.Owner?.GetType().FullName == "Avalonia.Diagnostics.Views.MainWindow" && hack.Owner.TryGetResource(ResourceKey, out value)) { From 8dab69663f649d75312e23ba173374d931f933b0 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 18 Oct 2022 00:42:48 +0800 Subject: [PATCH 25/37] fix: fix caption button fluent theme and default theme. --- .../Controls/CaptionButtons.xaml | 10 +++++----- .../Controls/CaptionButtons.xaml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml index c91e008e02..e06fa0c8d4 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml @@ -41,7 +41,7 @@ Theme="{StaticResource FluentCaptionButton}" IsVisible="False"> - @@ -61,7 +61,7 @@ - @@ -80,17 +80,17 @@ - - - diff --git a/src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml b/src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml index 379b92ea17..1badffef33 100644 --- a/src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml @@ -47,7 +47,7 @@ Theme="{StaticResource SimpleCaptionButton}"> - @@ -69,7 +69,7 @@ - @@ -89,17 +89,17 @@ - - - From 3184252633e0c7828c340eacf57920ecbda230ad Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 18 Oct 2022 01:24:35 +0800 Subject: [PATCH 26/37] fix: revert FullScreenButtonPath. --- src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml | 4 ++-- src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml index e06fa0c8d4..3b1071b36a 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml @@ -41,7 +41,7 @@ Theme="{StaticResource FluentCaptionButton}" IsVisible="False"> - @@ -83,7 +83,7 @@ - diff --git a/src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml b/src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml index 1badffef33..8c263d0231 100644 --- a/src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml @@ -47,7 +47,7 @@ Theme="{StaticResource SimpleCaptionButton}"> - @@ -92,7 +92,7 @@ - From 2f124a9a480e8a20373531d4e605d096572fc669 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 18 Oct 2022 01:31:08 +0800 Subject: [PATCH 27/37] fix: align with naming convention. --- src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml | 4 ++-- src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml index 3b1071b36a..71ae012289 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml @@ -61,7 +61,7 @@ - @@ -80,7 +80,7 @@ -