Browse Source

Merge remote-tracking branch 'upstream/master' into bump-tmds.dbus.sourcegenerator

pull/10826/head
affederaffe 3 years ago
parent
commit
9fc70cfcd5
  1. 35
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  2. 4
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  3. 2
      src/Avalonia.Base/Layout/Layoutable.cs
  4. 2
      src/Avalonia.Base/Media/Color.cs
  5. 2
      src/Avalonia.Base/Media/HsvColor.cs
  6. 46
      src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
  7. 21
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  8. 8
      src/Avalonia.Base/Platform/PixelFormat.cs
  9. 8
      src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs
  10. 2
      src/Avalonia.Base/Styling/ITemplate.cs
  11. 70
      src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
  12. 53
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  13. 216
      src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs
  14. 14
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs
  15. 51
      src/Avalonia.Base/Threading/Dispatcher.cs
  16. 125
      src/Avalonia.Base/Threading/DispatcherFrame.cs
  17. 127
      src/Avalonia.Base/Threading/DispatcherOperation.cs
  18. 4
      src/Avalonia.Base/Threading/DispatcherPriority.cs
  19. 24
      src/Avalonia.Base/Threading/NonPumpingSyncContext.cs
  20. 11
      src/Avalonia.Base/Utilities/NonPumpingLockHelper.cs
  21. 2
      src/Avalonia.Base/Visual.cs
  22. 4
      src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs
  23. 2
      src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.cs
  24. 30
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  25. 10
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs
  26. 11
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
  27. 26
      src/Avalonia.Controls.ColorPicker/ColorView/AlphaComponentPosition.cs
  28. 18
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs
  29. 35
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs
  30. 169
      src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs
  31. 146
      src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs
  32. 2
      src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
  33. 5
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  34. 67
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml
  35. 3
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  36. 4
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
  37. 67
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml
  38. 3
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  39. 2
      src/Avalonia.Controls.ItemsRepeater/Layout/UniformGridLayout.cs
  40. 1
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  41. 104
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs
  42. 65
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  43. 7
      src/Avalonia.Controls/Carousel.cs
  44. 7
      src/Avalonia.Controls/ComboBox.cs
  45. 5
      src/Avalonia.Controls/ContextMenu.cs
  46. 8
      src/Avalonia.Controls/Control.cs
  47. 12
      src/Avalonia.Controls/ItemsControl.cs
  48. 7
      src/Avalonia.Controls/ListBox.cs
  49. 6
      src/Avalonia.Controls/Menu.cs
  50. 5
      src/Avalonia.Controls/MenuItem.cs
  51. 12
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  52. 2
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  53. 6
      src/Avalonia.Controls/Primitives/TabStrip.cs
  54. 19
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  55. 2
      src/Avalonia.Controls/ScrollViewer.cs
  56. 10
      src/Avalonia.Controls/Slider.cs
  57. 7
      src/Avalonia.Controls/TabControl.cs
  58. 4
      src/Avalonia.Controls/Templates/FuncTemplate`1.cs
  59. 4
      src/Avalonia.Controls/Templates/IControlTemplate.cs
  60. 2
      src/Avalonia.Controls/TopLevel.cs
  61. 4
      src/Avalonia.Controls/TreeView.cs
  62. 23
      src/Avalonia.Controls/TreeViewItem.cs
  63. 2
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  64. 2
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  65. 2
      src/Avalonia.Themes.Simple/Controls/MenuItem.xaml
  66. 2
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  67. 5
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  68. 14
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  69. 6
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs
  70. 11
      src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs
  71. 8
      src/Markup/Avalonia.Markup.Xaml/Converters/ColorToBrushConverter.cs
  72. 10
      src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs
  73. 13
      src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs
  74. 9
      src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs
  75. 13
      src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs
  76. 33
      src/Markup/Avalonia.Markup.Xaml/Extensions.cs
  77. 12
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
  78. 10
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ArrayElementPlugin.cs
  79. 36
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs
  80. 54
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  81. 12
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs
  82. 6
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs
  83. 14
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs
  84. 52
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs
  85. 11
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs
  86. 6
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs
  87. 18
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs
  88. 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  89. 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/On.cs
  90. 6
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs
  91. 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs
  92. 27
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs
  93. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs
  94. 11
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs
  95. 34
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  96. 8
      src/Markup/Avalonia.Markup.Xaml/Parsers/PropertyParser.cs
  97. 2
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs
  98. 3
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs
  99. 3
      src/Markup/Avalonia.Markup.Xaml/Styling/MergeResourceInclude.cs
  100. 2
      src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs

35
samples/ControlCatalog/Pages/ColorPickerPage.xaml

@ -28,6 +28,41 @@
<Grid Grid.Column="2"
Grid.Row="0"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid.Resources>
<x:Double x:Key="ColorSliderSize">24</x:Double>
<x:Double x:Key="ColorSliderTrackSize">18</x:Double>
<CornerRadius x:Key="ColorSliderCornerRadius">12</CornerRadius>
<CornerRadius x:Key="ColorSliderTrackCornerRadius">9</CornerRadius>
<!-- Due to 'SystemControlForegroundBaseHighBrush' usage this only works in Fluent theme. -->
<!-- Otherwise it would be necessary to make custom light/dark resources. -->
<ControlTheme x:Key="ColorSliderThumbTheme"
TargetType="Thumb">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="BorderThickness" Value="5" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}" />
<Ellipse Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
Fill="Transparent"
Stroke="{TemplateBinding Foreground}"
StrokeThickness="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</ControlTheme>
</Grid.Resources>
<ColorSpectrum x:Name="ColorSpectrum1"
Grid.Row="0"
Color="Red"

4
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -1,7 +1,6 @@
using System;
using Android.Content;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
@ -99,6 +98,9 @@ namespace Avalonia.Android
{
_host.InitEditorInfo((topLevel, outAttrs) =>
{
if (_client == null)
return null;
_inputConnection = new AvaloniaInputConnection(topLevel, this);
outAttrs.InputType = options.ContentType switch

2
src/Avalonia.Base/Layout/Layoutable.cs

@ -125,7 +125,7 @@ namespace Avalonia.Layout
AvaloniaProperty.Register<Layoutable, VerticalAlignment>(nameof(VerticalAlignment));
/// <summary>
/// Defines the <see cref="UseLayoutRoundingProperty"/> property.
/// Defines the <see cref="UseLayoutRounding"/> property.
/// </summary>
public static readonly StyledProperty<bool> UseLayoutRoundingProperty =
AvaloniaProperty.Register<Layoutable, bool>(nameof(UseLayoutRounding), defaultValue: true, inherits: true);

2
src/Avalonia.Base/Media/Color.cs

@ -309,7 +309,7 @@ namespace Avalonia.Media
if (input.Length == 3 || input.Length == 4)
{
var extendedLength = 2 * input.Length;
#if !BUILDTASK
Span<char> extended = stackalloc char[extendedLength];
#else

2
src/Avalonia.Base/Media/HsvColor.cs

@ -131,7 +131,7 @@ namespace Avalonia.Media
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>0 is a shade of gray (no color).</item>
/// <item>0 is fully white (or a shade of gray) and shows no color.</item>
/// <item>1 is the full color.</item>
/// </list>
/// </remarks>

46
src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs

@ -228,6 +228,44 @@ static unsafe class PixelFormatReader
public void Reset(IntPtr address) => _address = (Rgba64*)address;
}
public unsafe struct Rgb24PixelFormatReader : IPixelFormatReader
{
private byte* _address;
public Rgba8888Pixel ReadNext()
{
var addr = _address;
_address += 3;
return new Rgba8888Pixel
{
R = addr[0],
G = addr[1],
B = addr[2],
A = 255,
};
}
public void Reset(IntPtr address) => _address = (byte*)address;
}
public unsafe struct Bgr24PixelFormatReader : IPixelFormatReader
{
private byte* _address;
public Rgba8888Pixel ReadNext()
{
var addr = _address;
_address += 3;
return new Rgba8888Pixel
{
R = addr[2],
G = addr[1],
B = addr[0],
A = 255,
};
}
public void Reset(IntPtr address) => _address = (byte*)address;
}
public static void Transcode(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst,
PixelFormat format)
@ -242,6 +280,10 @@ static unsafe class PixelFormatReader
Transcode<Gray8PixelReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Gray16)
Transcode<Gray16PixelReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Rgb24)
Transcode<Rgb24PixelFormatReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Bgr24)
Transcode<Bgr24PixelFormatReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Gray32Float)
Transcode<Gray32FloatPixelReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Rgba64)
@ -258,7 +300,9 @@ static unsafe class PixelFormatReader
|| format == PixelFormats.Gray8
|| format == PixelFormats.Gray16
|| format == PixelFormats.Gray32Float
|| format == PixelFormats.Rgba64;
|| format == PixelFormats.Rgba64
|| format == PixelFormats.Bgr24
|| format == PixelFormats.Rgb24;
}
public static void Transcode<TReader>(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst) where TReader : struct, IPixelFormatReader

21
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -697,13 +697,18 @@ namespace Avalonia.Media.TextFormatting
i = lastRunIndex;
//Possible overlap at runs of different direction
if (directionalWidth == 0)
{
continue;
//In case a run only contains a linebreak we don't want to skip it.
if (currentRun is ShapedTextRun shaped && currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0)
{
continue;
}
}
var coveredLength = 0;
TextBounds? textBounds = null;
int coveredLength;
TextBounds? textBounds;
switch (currentDirection)
{
@ -831,14 +836,18 @@ namespace Avalonia.Media.TextFormatting
i = firstRunIndex;
//Possible overlap at runs of different direction
if (directionalWidth == 0)
{
continue;
//In case a run only contains a linebreak we don't want to skip it.
if (currentRun is ShapedTextRun shaped && currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0)
{
continue;
}
}
var coveredLength = 0;
TextBounds? textBounds = null;
int coveredLength;
switch (currentDirection)
{

8
src/Avalonia.Base/Platform/PixelFormat.cs

@ -13,7 +13,9 @@ namespace Avalonia.Platform
Gray8,
Gray16,
Gray32Float,
Rgba64
Rgba64,
Rgb24,
Bgr24
}
public record struct PixelFormat
@ -35,6 +37,8 @@ namespace Avalonia.Platform
else if (FormatEnum == PixelFormatEnum.Rgb565
|| FormatEnum == PixelFormatEnum.Gray16)
return 16;
else if (FormatEnum is PixelFormatEnum.Bgr24 or PixelFormatEnum.Rgb24)
return 24;
else if (FormatEnum == PixelFormatEnum.Rgba64)
return 64;
@ -70,5 +74,7 @@ namespace Avalonia.Platform
public static PixelFormat Gray8 { get; } = new PixelFormat(PixelFormatEnum.Gray8);
public static PixelFormat Gray16 { get; } = new PixelFormat(PixelFormatEnum.Gray16);
public static PixelFormat Gray32Float { get; } = new PixelFormat(PixelFormatEnum.Gray32Float);
public static PixelFormat Rgb24 { get; } = new PixelFormat(PixelFormatEnum.Rgb24);
public static PixelFormat Bgr24 { get; } = new PixelFormat(PixelFormatEnum.Bgr24);
}
}

8
src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs

@ -72,6 +72,11 @@ internal abstract class BatchStreamPoolBase<T> : IDisposable
protected abstract T CreateItem();
protected virtual void ClearItem(T item)
{
}
protected virtual void DestroyItem(T item)
{
@ -94,6 +99,7 @@ internal abstract class BatchStreamPoolBase<T> : IDisposable
public void Return(T item)
{
ClearItem(item);
lock (_pool)
{
_usage--;
@ -138,7 +144,7 @@ internal sealed class BatchStreamObjectPool<T> : BatchStreamPoolBase<T[]> where
return new T[ArraySize];
}
protected override void DestroyItem(T[] item)
protected override void ClearItem(T[] item)
{
Array.Clear(item, 0, item.Length);
}

2
src/Avalonia.Base/Styling/ITemplate.cs

@ -2,6 +2,6 @@
{
public interface ITemplate
{
object Build();
object? Build();
}
}

70
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@ -1,6 +1,7 @@
using System;
using System.Runtime.ConstrainedExecution;
using System.Threading;
using Avalonia.Utilities;
namespace Avalonia.Threading
{
@ -9,6 +10,28 @@ namespace Avalonia.Threading
/// </summary>
public class AvaloniaSynchronizationContext : SynchronizationContext
{
internal readonly DispatcherPriority Priority;
private readonly NonPumpingLockHelper.IHelperImpl? _nonPumpingHelper =
AvaloniaLocator.Current.GetService<NonPumpingLockHelper.IHelperImpl>();
public AvaloniaSynchronizationContext(): this(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
}
// This constructor is here to enforce STA behavior for unit tests
internal AvaloniaSynchronizationContext(bool isStaThread)
{
if (_nonPumpingHelper != null
&& isStaThread)
SetWaitNotificationRequired();
}
public AvaloniaSynchronizationContext(DispatcherPriority priority)
{
Priority = priority;
}
/// <summary>
/// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer.
/// </summary>
@ -24,13 +47,13 @@ namespace Avalonia.Threading
return;
}
SetSynchronizationContext(new AvaloniaSynchronizationContext());
SetSynchronizationContext(Dispatcher.UIThread.GetContextWithPriority(DispatcherPriority.Normal));
}
/// <inheritdoc/>
public override void Post(SendOrPostCallback d, object? state)
{
Dispatcher.UIThread.Post(d, state, DispatcherPriority.Background);
Dispatcher.UIThread.Post(d, state, Priority);
}
/// <inheritdoc/>
@ -41,7 +64,50 @@ namespace Avalonia.Threading
else
Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).GetAwaiter().GetResult();
}
#if !NET6_0_OR_GREATER
[PrePrepareMethod]
#endif
public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
if (
_nonPumpingHelper != null
&& Dispatcher.UIThread.CheckAccess()
&& Dispatcher.UIThread.DisabledProcessingCount > 0)
return _nonPumpingHelper.Wait(waitHandles, waitAll, millisecondsTimeout);
return base.Wait(waitHandles, waitAll, millisecondsTimeout);
}
public record struct RestoreContext : IDisposable
{
private readonly SynchronizationContext? _oldContext;
private bool _needRestore;
internal RestoreContext(SynchronizationContext? oldContext)
{
_oldContext = oldContext;
_needRestore = true;
}
public void Dispose()
{
if (_needRestore)
{
SetSynchronizationContext(_oldContext);
_needRestore = false;
}
}
}
public static RestoreContext Ensure(DispatcherPriority priority)
{
if (Current is AvaloniaSynchronizationContext avaloniaContext
&& avaloniaContext.Priority == priority)
return default;
var oldContext = Current;
Dispatcher.UIThread.VerifyAccess();
SetSynchronizationContext(Dispatcher.UIThread.GetContextWithPriority(priority));
return new RestoreContext(oldContext);
}
}
}

53
src/Avalonia.Base/Threading/Dispatcher.Invoke.cs

@ -2,6 +2,7 @@ using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Avalonia.Threading;
@ -106,7 +107,8 @@ public partial class Dispatcher
// call the callback directly.
if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{
callback();
using (AvaloniaSynchronizationContext.Ensure(priority))
callback();
return;
}
@ -227,7 +229,8 @@ public partial class Dispatcher
// call the callback directly.
if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{
return callback();
using (AvaloniaSynchronizationContext.Ensure(priority))
return callback();
}
// Slow-Path: go through the queue.
@ -388,7 +391,7 @@ public partial class Dispatcher
return operation;
}
private void InvokeAsyncImpl(DispatcherOperation operation, CancellationToken cancellationToken)
internal void InvokeAsyncImpl(DispatcherOperation operation, CancellationToken cancellationToken)
{
bool succeeded = false;
@ -482,7 +485,7 @@ public partial class Dispatcher
// invoke.
try
{
operation.GetTask().Wait();
operation.Wait();
Debug.Assert(operation.Status == DispatcherOperationStatus.Completed ||
operation.Status == DispatcherOperationStatus.Aborted);
@ -539,6 +542,48 @@ public partial class Dispatcher
InvokeAsyncImpl(new DispatcherOperation(this, priority, action, true), CancellationToken.None);
}
/// <summary>
/// Executes the specified Func<Task> asynchronously on the
/// thread that the Dispatcher was created on
/// </summary>
/// <param name="callback">
/// A Func<Task> delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
/// callback is invoked relative to the other pending operations
/// in the Dispatcher.
/// </param>
/// <returns>
/// An task that completes after the task returned from callback finishes
/// </returns>
public Task InvokeTaskAsync(Func<Task> callback, DispatcherPriority priority = default)
{
_ = callback ?? throw new ArgumentNullException(nameof(callback));
return InvokeAsync(callback, priority).GetTask().Unwrap();
}
/// <summary>
/// Executes the specified Func<Task<TResult>> asynchronously on the
/// thread that the Dispatcher was created on
/// </summary>
/// <param name="callback">
/// A Func<Task<TResult>> delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
/// callback is invoked relative to the other pending operations
/// in the Dispatcher.
/// </param>
/// <returns>
/// An task that completes after the task returned from callback finishes
/// </returns>
public Task<TResult> InvokeTaskAsync<TResult>(Func<Task<TResult>> action, DispatcherPriority priority = default)
{
_ = action ?? throw new ArgumentNullException(nameof(action));
return InvokeAsync(action, priority).GetTask().Unwrap();
}
/// <summary>
/// Posts an action that will be invoked on the dispatcher thread.
/// </summary>

216
src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs

@ -0,0 +1,216 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Avalonia.Utilities;
namespace Avalonia.Threading;
public partial class Dispatcher
{
internal bool ExitAllFramesRequested { get; private set; }
internal bool HasShutdownStarted { get; private set; }
internal int DisabledProcessingCount { get; set; }
private bool _hasShutdownFinished;
private bool _startingShutdown;
private Stack<DispatcherFrame> _frames = new();
/// <summary>
/// Raised when the dispatcher is shutting down.
/// </summary>
public event EventHandler? ShutdownStarted;
/// <summary>
/// Raised when the dispatcher is shut down.
/// </summary>
public event EventHandler? ShutdownFinished;
/// <summary>
/// Push an execution frame.
/// </summary>
/// <param name="frame">
/// The frame for the dispatcher to process.
/// </param>
public void PushFrame(DispatcherFrame frame)
{
VerifyAccess();
if (_controlledImpl == null)
throw new PlatformNotSupportedException();
_ = frame ?? throw new ArgumentNullException(nameof(frame));
if(_hasShutdownFinished) // Dispatcher thread - no lock needed for read
throw new InvalidOperationException("Cannot perform requested operation because the Dispatcher shut down");
if (DisabledProcessingCount > 0)
throw new InvalidOperationException(
"Cannot perform this operation while dispatcher processing is suspended.");
try
{
_frames.Push(frame);
using (AvaloniaSynchronizationContext.Ensure(DispatcherPriority.Normal))
frame.Run(_controlledImpl);
}
finally
{
_frames.Pop();
if (_frames.Count == 0)
{
if (HasShutdownStarted)
ShutdownImpl();
else
ExitAllFramesRequested = false;
}
}
}
/// <summary>
/// Runs the dispatcher's main loop.
/// </summary>
/// <param name="cancellationToken">
/// A cancellation token used to exit the main loop.
/// </param>
public void MainLoop(CancellationToken cancellationToken)
{
if (_controlledImpl == null)
throw new PlatformNotSupportedException();
var frame = new DispatcherFrame();
cancellationToken.Register(() => frame.Continue = false);
PushFrame(frame);
}
/// <summary>
/// Requests that all nested frames exit.
/// </summary>
public void ExitAllFrames()
{
if (_frames.Count == 0)
return;
ExitAllFramesRequested = true;
foreach (var f in _frames)
f.MaybeExitOnDispatcherRequest();
}
/// <summary>
/// Begins the process of shutting down the dispatcher.
/// </summary>
public void BeginInvokeShutdown(DispatcherPriority priority) => Post(StartShutdownImpl, priority);
/// <summary>
/// Initiates the shutdown process of the Dispatcher synchronously.
/// </summary>
public void InvokeShutdown() => Invoke(StartShutdownImpl, DispatcherPriority.Send);
private void StartShutdownImpl()
{
if (!_startingShutdown)
{
// We only need this to prevent reentrancy if the ShutdownStarted event
// tries to shut down again.
_startingShutdown = true;
// Call the ShutdownStarted event before we actually mark ourselves
// as shutting down. This is so the handlers can actually do work
// when they get this event without throwing exceptions.
ShutdownStarted?.Invoke(this, EventArgs.Empty);
HasShutdownStarted = true;
if (_frames.Count > 0)
ExitAllFrames();
else ShutdownImpl();
}
}
private void ShutdownImpl()
{
DispatcherOperation? operation = null;
_impl.Timer -= PromoteTimers;
_impl.Signaled -= Signaled;
do
{
lock (InstanceLock)
{
if (_queue.MaxPriority != DispatcherPriority.Invalid)
{
operation = _queue.Peek();
}
else
{
operation = null;
}
}
if (operation != null)
{
operation.Abort();
}
} while (operation != null);
_impl.UpdateTimer(null);
_hasShutdownFinished = true;
ShutdownFinished?.Invoke(this, EventArgs.Empty);
}
public record struct DispatcherProcessingDisabled : IDisposable
{
private readonly SynchronizationContext? _oldContext;
private readonly bool _restoreContext;
private Dispatcher? _dispatcher;
internal DispatcherProcessingDisabled(Dispatcher dispatcher)
{
_dispatcher = dispatcher;
}
internal DispatcherProcessingDisabled(Dispatcher dispatcher, SynchronizationContext? oldContext) : this(
dispatcher)
{
_oldContext = oldContext;
_restoreContext = true;
}
public void Dispose()
{
if(_dispatcher==null)
return;
_dispatcher.DisabledProcessingCount--;
_dispatcher = null;
if (_restoreContext)
SynchronizationContext.SetSynchronizationContext(_oldContext);
}
}
/// <summary>
/// Disable the event processing of the dispatcher.
/// </summary>
/// <remarks>
/// This is an advanced method intended to eliminate the chance of
/// unrelated reentrancy. The effect of disabling processing is:
/// 1) CLR locks will not pump messages internally.
/// 2) No one is allowed to push a frame.
/// 3) No message processing is permitted.
/// </remarks>
public DispatcherProcessingDisabled DisableProcessing()
{
VerifyAccess();
// Turn off processing.
DisabledProcessingCount++;
var oldContext = SynchronizationContext.Current;
if (oldContext is AvaloniaSynchronizationContext or NonPumpingSyncContext)
return new DispatcherProcessingDisabled(this);
var helper = AvaloniaLocator.Current.GetService<NonPumpingLockHelper.IHelperImpl>();
if (helper == null)
return new DispatcherProcessingDisabled(this);
SynchronizationContext.SetSynchronizationContext(new NonPumpingSyncContext(helper, oldContext));
return new DispatcherProcessingDisabled(this, oldContext);
}
}

14
src/Avalonia.Base/Threading/Dispatcher.Queue.cs

@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Threading;
namespace Avalonia.Threading;
@ -43,10 +44,19 @@ public partial class Dispatcher
/// </summary>
public void RunJobs(DispatcherPriority? priority = null)
{
RunJobs(priority, CancellationToken.None);
}
internal void RunJobs(DispatcherPriority? priority, CancellationToken cancellationToken)
{
if (DisabledProcessingCount > 0)
throw new InvalidOperationException(
"Cannot perform this operation while dispatcher processing is suspended.");
priority ??= DispatcherPriority.MinimumActiveValue;
if (priority < DispatcherPriority.MinimumActiveValue)
priority = DispatcherPriority.MinimumActiveValue;
while (true)
while (!cancellationToken.IsCancellationRequested)
{
DispatcherOperation? job;
lock (InstanceLock)
@ -168,7 +178,7 @@ public partial class Dispatcher
}
}
private bool RequestProcessing()
internal bool RequestProcessing()
{
lock (InstanceLock)
{

51
src/Avalonia.Base/Threading/Dispatcher.cs

@ -18,11 +18,13 @@ public partial class Dispatcher : IDispatcher
{
private IDispatcherImpl _impl;
internal object InstanceLock { get; } = new();
private bool _hasShutdownFinished;
private IControlledDispatcherImpl? _controlledImpl;
private static Dispatcher? s_uiThread;
private IDispatcherImplWithPendingInput? _pendingInputImpl;
private IDispatcherImplWithExplicitBackgroundProcessing? _backgroundProcessingImpl;
private readonly IDispatcherImplWithExplicitBackgroundProcessing? _backgroundProcessingImpl;
private readonly AvaloniaSynchronizationContext?[] _priorityContexts =
new AvaloniaSynchronizationContext?[DispatcherPriority.MaxValue - DispatcherPriority.MinValue + 1];
internal Dispatcher(IDispatcherImpl impl)
{
@ -37,6 +39,7 @@ public partial class Dispatcher : IDispatcher
}
public static Dispatcher UIThread => s_uiThread ??= CreateUIThreadDispatcher();
public bool SupportsRunLoops => _controlledImpl != null;
private static Dispatcher CreateUIThreadDispatcher()
{
@ -72,50 +75,14 @@ public partial class Dispatcher : IDispatcher
[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowVerifyAccess()
=> throw new InvalidOperationException("Call from invalid thread");
ThrowVerifyAccess();
}
}
internal void Shutdown()
{
DispatcherOperation? operation = null;
_impl.Timer -= PromoteTimers;
_impl.Signaled -= Signaled;
do
{
lock(InstanceLock)
{
if(_queue.MaxPriority != DispatcherPriority.Invalid)
{
operation = _queue.Peek();
}
else
{
operation = null;
}
}
if(operation != null)
{
operation.Abort();
}
} while(operation != null);
_impl.UpdateTimer(null);
_hasShutdownFinished = true;
}
/// <summary>
/// Runs the dispatcher's main loop.
/// </summary>
/// <param name="cancellationToken">
/// A cancellation token used to exit the main loop.
/// </param>
public void MainLoop(CancellationToken cancellationToken)
internal AvaloniaSynchronizationContext GetContextWithPriority(DispatcherPriority priority)
{
if (_controlledImpl == null)
throw new PlatformNotSupportedException();
cancellationToken.Register(() => RequestProcessing());
_controlledImpl.RunLoop(cancellationToken);
DispatcherPriority.Validate(priority, nameof(priority));
var index = priority - DispatcherPriority.MinValue;
return _priorityContexts[index] ??= new(priority);
}
}

125
src/Avalonia.Base/Threading/DispatcherFrame.cs

@ -0,0 +1,125 @@
using System;
using System.Threading;
namespace Avalonia.Threading;
/// <summary>
/// Representation of Dispatcher frame.
/// </summary>
public class DispatcherFrame
{
private bool _exitWhenRequested;
private bool _continue;
private bool _isRunning;
private CancellationTokenSource? _cancellationTokenSource;
/// <summary>
/// Constructs a new instance of the DispatcherFrame class.
/// </summary>
public DispatcherFrame() : this(true)
{
}
public Dispatcher Dispatcher { get; }
/// <summary>
/// Constructs a new instance of the DispatcherFrame class.
/// </summary>
/// <param name="exitWhenRequested">
/// Indicates whether or not this frame will exit when all frames
/// are requested to exit.
/// <p/>
/// Dispatcher frames typically break down into two categories:
/// 1) Long running, general purpose frames, that exit only when
/// told to. These frames should exit when requested.
/// 2) Short running, very specific frames that exit themselves
/// when an important criteria is met. These frames may
/// consider not exiting when requested in favor of waiting
/// for their important criteria to be met. These frames
/// should have a timeout associated with them.
/// </param>
public DispatcherFrame(bool exitWhenRequested)
{
Dispatcher = Dispatcher.UIThread;
Dispatcher.VerifyAccess();
_exitWhenRequested = exitWhenRequested;
_continue = true;
}
/// <summary>
/// Indicates that this dispatcher frame should exit.
/// </summary>
public bool Continue
{
get
{
// This method is free-threaded.
// First check if this frame wants to continue.
bool shouldContinue = _continue;
if (shouldContinue)
{
// This frame wants to continue, so next check if it will
// respect the "exit requests" from the dispatcher.
if (_exitWhenRequested)
{
Dispatcher dispatcher = Dispatcher;
// This frame is willing to respect the "exit requests" of
// the dispatcher, so check them.
if (dispatcher.ExitAllFramesRequested || dispatcher.HasShutdownStarted)
{
shouldContinue = false;
}
}
}
return shouldContinue;
}
set
{
// This method is free-threaded.
lock (Dispatcher.InstanceLock)
{
_continue = value;
if (!_continue)
_cancellationTokenSource?.Cancel();
}
}
}
internal void Run(IControlledDispatcherImpl impl)
{
// Since the actual platform run loop is controlled by a Cancellation token, we are restarting
// it if frame still needs to run
while (Continue)
RunCore(impl);
}
private void RunCore(IControlledDispatcherImpl impl)
{
if (_isRunning)
throw new InvalidOperationException("This frame is already running");
_isRunning = true;
try
{
_cancellationTokenSource = new CancellationTokenSource();
// Wake up the dispatcher in case it has pending jobs
Dispatcher.RequestProcessing();
impl.RunLoop(_cancellationTokenSource.Token);
}
finally
{
_isRunning = false;
_cancellationTokenSource?.Cancel();
_cancellationTokenSource = null;
}
}
internal void MaybeExitOnDispatcherRequest()
{
if (_exitWhenRequested)
_cancellationTokenSource?.Cancel();
}
}

127
src/Avalonia.Base/Threading/DispatcherOperation.cs

@ -101,21 +101,131 @@ public class DispatcherOperation
}
}
public void Abort()
public bool Abort()
{
lock (Dispatcher.InstanceLock)
{
if (Status == DispatcherOperationStatus.Pending)
return;
if (Status != DispatcherOperationStatus.Pending)
return false;
Dispatcher.Abort(this);
return true;
}
}
public void Wait()
/// <summary>
/// Waits for this operation to complete.
/// </summary>
/// <returns>
/// The status of the operation. To obtain the return value
/// of the invoked delegate, use the the Result property.
/// </returns>
public void Wait() => Wait(TimeSpan.FromMilliseconds(-1));
/// <summary>
/// Waits for this operation to complete.
/// </summary>
/// <param name="timeout">
/// The maximum amount of time to wait.
/// </param>
public void Wait(TimeSpan timeout)
{
if (Dispatcher.CheckAccess())
throw new InvalidOperationException("Wait is only supported on background thread");
GetTask().Wait();
if ((Status == DispatcherOperationStatus.Pending || Status == DispatcherOperationStatus.Executing) &&
timeout.TotalMilliseconds != 0)
{
if (Dispatcher.CheckAccess())
{
if (Status == DispatcherOperationStatus.Executing)
{
// We are the dispatching thread, and the current operation state is
// executing, which means that the operation is in the middle of
// executing (on this thread) and is trying to wait for the execution
// to complete. Unfortunately, the thread will now deadlock, so
// we throw an exception instead.
throw new InvalidOperationException("A thread cannot wait on operations already running on the same thread.");
}
var cts = new CancellationTokenSource();
EventHandler finishedHandler = delegate
{
cts.Cancel();
};
Completed += finishedHandler;
Aborted += finishedHandler;
try
{
while (Status == DispatcherOperationStatus.Pending)
{
if (Dispatcher.SupportsRunLoops)
{
if (Priority >= DispatcherPriority.MinimumForegroundPriority)
Dispatcher.RunJobs(Priority, cts.Token);
else
Dispatcher.PushFrame(new DispatcherOperationFrame(this, timeout));
}
else
Dispatcher.RunJobs(DispatcherPriority.MinimumActiveValue, cts.Token);
}
}
finally
{
Completed -= finishedHandler;
Aborted -= finishedHandler;
}
}
}
GetTask().GetAwaiter().GetResult();
}
private class DispatcherOperationFrame : DispatcherFrame
{
// Note: we pass "exitWhenRequested=false" to the base
// DispatcherFrame construsctor because we do not want to exit
// this frame if the dispatcher is shutting down. This is
// because we may need to invoke operations during the shutdown process.
public DispatcherOperationFrame(DispatcherOperation op, TimeSpan timeout) : base(false)
{
_operation = op;
// We will exit this frame once the operation is completed or aborted.
_operation.Aborted += OnCompletedOrAborted;
_operation.Completed += OnCompletedOrAborted;
// We will exit the frame if the operation is not completed within
// the requested timeout.
if (timeout.TotalMilliseconds > 0)
{
_waitTimer = new Timer(_ => Exit(),
null,
timeout,
TimeSpan.FromMilliseconds(-1));
}
// Some other thread could have aborted the operation while we were
// setting up the handlers. We check the state again and mark the
// frame as "should not continue" if this happened.
if (_operation.Status != DispatcherOperationStatus.Pending)
{
Exit();
}
}
private void Exit()
{
Continue = false;
if (_waitTimer != null)
{
_waitTimer.Dispose();
}
_operation.Aborted -= OnCompletedOrAborted;
_operation.Completed -= OnCompletedOrAborted;
}
private void OnCompletedOrAborted(object? sender, EventArgs e) => Exit();
private DispatcherOperation _operation;
private Timer? _waitTimer;
}
public Task GetTask() => GetTaskCore();
@ -148,7 +258,8 @@ public class DispatcherOperation
try
{
InvokeCore();
using (AvaloniaSynchronizationContext.Ensure(Priority))
InvokeCore();
}
finally
{

4
src/Avalonia.Base/Threading/DispatcherPriority.cs

@ -20,7 +20,9 @@ namespace Avalonia.Threading
/// <summary>
/// The lowest foreground dispatcher priority
/// </summary>
internal static readonly DispatcherPriority Default = new(0);
public static readonly DispatcherPriority Default = new(0);
internal static readonly DispatcherPriority MinimumForegroundPriority = Default;
/// <summary>
/// The job will be processed with the same priority as input.

24
src/Windows/Avalonia.Win32/NonPumpingSyncContext.cs → src/Avalonia.Base/Threading/NonPumpingSyncContext.cs

@ -2,16 +2,17 @@ using System;
using System.Runtime.ConstrainedExecution;
using System.Threading;
using Avalonia.Utilities;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32
namespace Avalonia.Threading
{
internal class NonPumpingSyncContext : SynchronizationContext, IDisposable
{
private readonly NonPumpingLockHelper.IHelperImpl _impl;
private readonly SynchronizationContext? _inner;
private NonPumpingSyncContext(SynchronizationContext? inner)
public NonPumpingSyncContext(NonPumpingLockHelper.IHelperImpl impl, SynchronizationContext? inner)
{
_impl = impl;
_inner = inner;
SetWaitNotificationRequired();
SetSynchronizationContext(this);
@ -48,15 +49,12 @@ namespace Avalonia.Win32
#if !NET6_0_OR_GREATER
[PrePrepareMethod]
#endif
public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
return UnmanagedMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll,
millisecondsTimeout, false);
}
public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) =>
_impl.Wait(waitHandles, waitAll, millisecondsTimeout);
public void Dispose() => SetSynchronizationContext(_inner);
public static IDisposable? Use()
internal static IDisposable? Use(NonPumpingLockHelper.IHelperImpl impl)
{
var current = Current;
if (current == null)
@ -67,12 +65,8 @@ namespace Avalonia.Win32
if (current is NonPumpingSyncContext)
return null;
return new NonPumpingSyncContext(current);
}
internal class HelperImpl : NonPumpingLockHelper.IHelperImpl
{
IDisposable? NonPumpingLockHelper.IHelperImpl.Use() => NonPumpingSyncContext.Use();
return new NonPumpingSyncContext(impl, current);
}
}
}

11
src/Avalonia.Base/Utilities/NonPumpingLockHelper.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Threading;
namespace Avalonia.Utilities
{
@ -6,9 +7,15 @@ namespace Avalonia.Utilities
{
public interface IHelperImpl
{
IDisposable? Use();
int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);
}
public static IDisposable? Use() => AvaloniaLocator.Current.GetService<IHelperImpl>()?.Use();
public static IDisposable? Use()
{
var impl = AvaloniaLocator.Current.GetService<IHelperImpl>();
if (impl == null)
return null;
return NonPumpingSyncContext.Use(impl);
}
}
}

2
src/Avalonia.Base/Visual.cs

@ -50,7 +50,7 @@ namespace Avalonia
AvaloniaProperty.Register<Visual, Geometry?>(nameof(Clip));
/// <summary>
/// Defines the <see cref="IsVisibleProperty"/> property.
/// Defines the <see cref="IsVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsVisibleProperty =
AvaloniaProperty.Register<Visual, bool>(nameof(IsVisible), true);

4
src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs

@ -1,6 +1,4 @@
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls
namespace Avalonia.Controls
{
/// <summary>
/// Presents a color for user editing using a spectrum, palette and component sliders within a drop down.

2
src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.cs

@ -124,7 +124,7 @@ namespace Avalonia.Controls.Primitives
if (accentStep != 0)
{
// ColorChanged will be invoked in OnPropertyChanged if the value is different
HsvColor = AccentColorConverter.GetAccent(hsvColor, accentStep);
SetCurrentValue(HsvColorProperty, AccentColorConverter.GetAccent(hsvColor, accentStep));
}
}
}

30
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs

@ -96,8 +96,22 @@ namespace Avalonia.Controls.Primitives
// independent pixels of controls.
var scale = LayoutHelper.GetLayoutScale(this);
var pixelWidth = Convert.ToInt32(Bounds.Width * scale);
var pixelHeight = Convert.ToInt32(Bounds.Height * scale);
int pixelWidth;
int pixelHeight;
if (base._track != null)
{
pixelWidth = Convert.ToInt32(base._track.Bounds.Width * scale);
pixelHeight = Convert.ToInt32(base._track.Bounds.Height * scale);
}
else
{
// 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
pixelWidth = Convert.ToInt32(Bounds.Width * scale);
pixelHeight = Convert.ToInt32(Bounds.Height * scale);
}
if (pixelWidth != 0 && pixelHeight != 0)
{
@ -373,7 +387,7 @@ namespace Avalonia.Controls.Primitives
ignorePropertyChanged = true;
// Always keep the two color properties in sync
HsvColor = Color.ToHsv();
SetCurrentValue(HsvColorProperty, Color.ToHsv());
SetColorToSliderValues();
UpdateBackground();
@ -403,7 +417,7 @@ namespace Avalonia.Controls.Primitives
ignorePropertyChanged = true;
// Always keep the two color properties in sync
Color = HsvColor.ToRgb();
SetCurrentValue(ColorProperty, HsvColor.ToRgb());
SetColorToSliderValues();
UpdateBackground();
@ -440,13 +454,13 @@ namespace Avalonia.Controls.Primitives
if (ColorModel == ColorModel.Hsva)
{
HsvColor = hsvColor;
Color = hsvColor.ToRgb();
SetCurrentValue(HsvColorProperty, hsvColor);
SetCurrentValue(ColorProperty, hsvColor.ToRgb());
}
else
{
Color = color;
HsvColor = color.ToHsv();
SetCurrentValue(ColorProperty, color);
SetCurrentValue(HsvColorProperty, color.ToHsv());
}
UpdatePseudoClasses();

10
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs

@ -96,10 +96,10 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Defines the <see cref="ThirdComponent"/> property.
/// </summary>
public static readonly StyledProperty<ColorComponent> ThirdComponentProperty =
AvaloniaProperty.Register<ColorSpectrum, ColorComponent>(
public static readonly DirectProperty<ColorSpectrum, ColorComponent> ThirdComponentProperty =
AvaloniaProperty.RegisterDirect<ColorSpectrum, ColorComponent>(
nameof(ThirdComponent),
ColorComponent.Component3); // Value
o => o.ThirdComponent);
/// <summary>
/// Gets or sets the currently selected color in the RGB color model.
@ -239,8 +239,8 @@ namespace Avalonia.Controls.Primitives
/// </remarks>
public ColorComponent ThirdComponent
{
get => GetValue(ThirdComponentProperty);
protected set => SetValue(ThirdComponentProperty, value);
get => _thirdComponent;
private set => SetAndRaise(ThirdComponentProperty, ref _thirdComponent, value);
}
}
}

11
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs

@ -13,9 +13,9 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Reactive;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.Reactive;
namespace Avalonia.Controls.Primitives
{
@ -48,6 +48,7 @@ namespace Avalonia.Controls.Primitives
private bool _isPointerPressed = false;
private bool _shouldShowLargeSelection = false;
private List<Hsv> _hsvValues = new List<Hsv>();
private ColorComponent _thirdComponent = ColorComponent.Component3; // HsvComponent.Value
private IDisposable? _layoutRootDisposable;
private IDisposable? _selectionEllipsePanelDisposable;
@ -403,7 +404,7 @@ namespace Avalonia.Controls.Primitives
_updatingHsvColor = true;
Hsv newHsv = (new Rgb(color)).ToHsv();
HsvColor = newHsv.ToHsvColor(color.A / 255.0);
SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(color.A / 255.0));
_updatingHsvColor = false;
UpdateEllipse();
@ -534,7 +535,7 @@ namespace Avalonia.Controls.Primitives
_updatingColor = true;
Rgb newRgb = (new Hsv(hsvColor)).ToRgb();
Color = newRgb.ToColor(hsvColor.A);
SetCurrentValue(ColorProperty, newRgb.ToColor(hsvColor.A));
_updatingColor = false;
@ -608,8 +609,8 @@ namespace Avalonia.Controls.Primitives
Rgb newRgb = newHsv.ToRgb();
double alpha = HsvColor.A;
Color = newRgb.ToColor(alpha);
HsvColor = newHsv.ToHsvColor(alpha);
SetCurrentValue(ColorProperty, newRgb.ToColor(alpha));
SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(alpha));
UpdateEllipse();
UpdatePseudoClasses();

26
src/Avalonia.Controls.ColorPicker/ColorView/AlphaComponentPosition.cs

@ -0,0 +1,26 @@
namespace Avalonia.Controls
{
/// <summary>
/// Defines the position of a color's alpha component relative to all other components.
/// </summary>
public enum AlphaComponentPosition
{
/// <summary>
/// The alpha component occurs before all other components.
/// </summary>
/// <remarks>
/// For example, this may indicate the #AARRGGBB or ARGB format which
/// is the default format for XAML itself and the Color struct.
/// </remarks>
Leading,
/// <summary>
/// The alpha component occurs after all other components.
/// </summary>
/// <remarks>
/// For example, this may indicate the #RRGGBBAA or RGBA format which
/// is the default format for CSS.
/// </remarks>
Trailing,
}
}

18
src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs

@ -42,6 +42,14 @@ namespace Avalonia.Controls
nameof(ColorSpectrumShape),
ColorSpectrumShape.Box);
/// <summary>
/// Defines the <see cref="HexInputAlphaPosition"/> property.
/// </summary>
public static readonly StyledProperty<AlphaComponentPosition> HexInputAlphaPositionProperty =
AvaloniaProperty.Register<ColorView, AlphaComponentPosition>(
nameof(HexInputAlphaPosition),
AlphaComponentPosition.Trailing); // Match CSS (and default slider order) instead of XAML/WinUI
/// <summary>
/// Defines the <see cref="HsvColor"/> property.
/// </summary>
@ -260,6 +268,16 @@ namespace Avalonia.Controls
set => SetValue(ColorSpectrumShapeProperty, value);
}
/// <summary>
/// Gets or sets the position of the alpha component in the hexadecimal input box relative to
/// all other color components.
/// </summary>
public AlphaComponentPosition HexInputAlphaPosition
{
get => GetValue(HexInputAlphaPositionProperty);
set => SetValue(HexInputAlphaPositionProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.HsvColor"/>
public HsvColor HsvColor
{

35
src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs

@ -1,14 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using Avalonia.Controls.Converters;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -28,8 +24,7 @@ namespace Avalonia.Controls
private TextBox? _hexTextBox;
private TabControl? _tabControl;
private ColorToHexConverter colorToHexConverter = new ColorToHexConverter();
protected bool ignorePropertyChanged = false;
protected bool _ignorePropertyChanged = false;
/// <summary>
/// Initializes a new instance of the <see cref="ColorView"/> class.
@ -46,11 +41,11 @@ namespace Avalonia.Controls
{
if (_hexTextBox != null)
{
var convertedColor = colorToHexConverter.ConvertBack(_hexTextBox.Text, typeof(Color), null, CultureInfo.CurrentCulture);
var convertedColor = ColorToHexConverter.ParseHexString(_hexTextBox.Text ?? string.Empty, HexInputAlphaPosition);
if (convertedColor is Color color)
{
Color = color;
SetCurrentValue(ColorProperty, color);
}
// Re-apply the hex value
@ -66,7 +61,7 @@ namespace Avalonia.Controls
{
if (_hexTextBox != null)
{
_hexTextBox.Text = colorToHexConverter.Convert(Color, typeof(string), null, CultureInfo.CurrentCulture) as string;
_hexTextBox.Text = ColorToHexConverter.ToHexString(Color, HexInputAlphaPosition);
}
}
@ -167,7 +162,7 @@ namespace Avalonia.Controls
// The work-around for this is done here where SelectedIndex is forcefully
// synchronized with whatever the TabControl property value is. This is
// possible since selection validation is already done by this method.
SelectedIndex = _tabControl.SelectedIndex;
SetCurrentValue(SelectedIndexProperty, _tabControl.SelectedIndex);
}
return;
@ -200,7 +195,7 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (ignorePropertyChanged)
if (_ignorePropertyChanged)
{
base.OnPropertyChanged(change);
return;
@ -209,29 +204,29 @@ namespace Avalonia.Controls
// Always keep the two color properties in sync
if (change.Property == ColorProperty)
{
ignorePropertyChanged = true;
_ignorePropertyChanged = true;
HsvColor = Color.ToHsv();
SetCurrentValue(HsvColorProperty, Color.ToHsv());
SetColorToHexTextBox();
OnColorChanged(new ColorChangedEventArgs(
change.GetOldValue<Color>(),
change.GetNewValue<Color>()));
ignorePropertyChanged = false;
_ignorePropertyChanged = false;
}
else if (change.Property == HsvColorProperty)
{
ignorePropertyChanged = true;
_ignorePropertyChanged = true;
Color = HsvColor.ToRgb();
SetCurrentValue(ColorProperty, HsvColor.ToRgb());
SetColorToHexTextBox();
OnColorChanged(new ColorChangedEventArgs(
change.GetOldValue<HsvColor>().ToRgb(),
change.GetNewValue<HsvColor>().ToRgb()));
ignorePropertyChanged = false;
_ignorePropertyChanged = false;
}
else if (change.Property == PaletteProperty)
{
@ -241,7 +236,7 @@ namespace Avalonia.Controls
// bound properties controlling the palette grid
if (palette != null)
{
PaletteColumnCount = palette.ColorCount;
SetCurrentValue(PaletteColumnCountProperty, palette.ColorCount);
List<Color> newPaletteColors = new List<Color>();
for (int shadeIndex = 0; shadeIndex < palette.ShadeCount; shadeIndex++)
@ -252,14 +247,14 @@ namespace Avalonia.Controls
}
}
PaletteColors = newPaletteColors;
SetCurrentValue(PaletteColorsProperty, newPaletteColors);
}
}
else if (change.Property == IsAlphaEnabledProperty)
{
// Manually coerce the HsvColor value
// (Color will be coerced automatically if HsvColor changes)
HsvColor = OnCoerceHsvColor(HsvColor);
SetCurrentValue(HsvColorProperty, OnCoerceHsvColor(HsvColor));
}
else if (change.Property == IsColorComponentsVisibleProperty ||
change.Property == IsColorPaletteVisibleProperty ||

169
src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs

@ -2,6 +2,7 @@
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Controls.Converters
{
@ -10,6 +11,11 @@ namespace Avalonia.Controls.Converters
/// </summary>
public class ColorToHexConverter : IValueConverter
{
/// <summary>
/// Gets or sets the position of a color's alpha component relative to all other components.
/// </summary>
public AlphaComponentPosition AlphaPosition { get; set; } = AlphaComponentPosition.Leading;
/// <inheritdoc/>
public object? Convert(
object? value,
@ -42,16 +48,7 @@ namespace Avalonia.Controls.Converters
return AvaloniaProperty.UnsetValue;
}
string hexColor = color.ToUint32().ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant();
if (includeSymbol == false)
{
// TODO: When .net standard 2.0 is dropped, replace the below line
//hexColor = hexColor.Replace("#", string.Empty, StringComparison.Ordinal);
hexColor = hexColor.Replace("#", string.Empty);
}
return hexColor;
return ToHexString(color, AlphaPosition, includeSymbol);
}
/// <inheritdoc/>
@ -62,21 +59,159 @@ namespace Avalonia.Controls.Converters
CultureInfo culture)
{
string hexValue = value?.ToString() ?? string.Empty;
return ParseHexString(hexValue, AlphaPosition) ?? AvaloniaProperty.UnsetValue;
}
/// <summary>
/// Converts the given color to its hex color value string representation.
/// </summary>
/// <param name="color">The color to represent as a hex value string.</param>
/// <param name="alphaPosition">The output position of the alpha component.</param>
/// <param name="includeSymbol">Whether the hex symbol '#' will be added.</param>
/// <returns>The input color converted to its hex value string.</returns>
public static string ToHexString(
Color color,
AlphaComponentPosition alphaPosition,
bool includeSymbol = false)
{
uint intColor;
if (alphaPosition == AlphaComponentPosition.Trailing)
{
intColor = ((uint)color.R << 24) | ((uint)color.G << 16) | ((uint)color.B << 8) | (uint)color.A;
}
else
{
// Default is Leading alpha
intColor = ((uint)color.A << 24) | ((uint)color.R << 16) | ((uint)color.G << 8) | (uint)color.B;
}
if (Color.TryParse(hexValue, out Color color))
string hexColor = intColor.ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant();
if (includeSymbol)
{
hexColor = '#' + hexColor;
}
return hexColor;
}
/// <summary>
/// Parses a hex color value string into a new <see cref="Color"/>.
/// </summary>
/// <param name="hexColor">The hex color string to parse.</param>
/// <param name="alphaPosition">The input position of the alpha component.</param>
/// <returns>The parsed <see cref="Color"/>; otherwise, null.</returns>
public static Color? ParseHexString(
string hexColor,
AlphaComponentPosition alphaPosition)
{
hexColor = hexColor.Trim();
if (!hexColor.StartsWith("#", StringComparison.Ordinal))
{
hexColor = "#" + hexColor;
}
if (TryParseHexFormat(hexColor.AsSpan(), alphaPosition, out Color color))
{
return color;
}
else if (hexValue.StartsWith("#", StringComparison.Ordinal) == false &&
Color.TryParse("#" + hexValue, out Color color2))
return null;
}
/// <summary>
/// Parses the given span of characters representing a hex color value into a new <see cref="Color"/>.
/// </summary>
/// <remarks>
/// This is based on the Color.TryParseHexFormat() method.
/// It is copied because it needs to be extended to handle alpha position.
/// However, the alpha position enum is only available in the controls namespace with the ColorPicker control.
/// </remarks>
private static bool TryParseHexFormat(
ReadOnlySpan<char> s,
AlphaComponentPosition alphaPosition,
out Color color)
{
static bool TryParseCore(ReadOnlySpan<char> input, AlphaComponentPosition alphaPosition, ref Color color)
{
return color2;
var alphaComponent = 0u;
if (input.Length == 6)
{
if (alphaPosition == AlphaComponentPosition.Trailing)
{
alphaComponent = 0x000000FF;
}
else
{
alphaComponent = 0xFF000000;
}
}
else if (input.Length != 8)
{
return false;
}
if (!input.TryParseUInt(NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed))
{
return false;
}
if (alphaComponent != 0)
{
if (alphaPosition == AlphaComponentPosition.Trailing)
{
parsed = (parsed << 8) | alphaComponent;
}
else
{
parsed = parsed | alphaComponent;
}
}
if (alphaPosition == AlphaComponentPosition.Trailing)
{
// #RRGGBBAA
color = new Color(
a: (byte)(parsed & 0xFF),
r: (byte)((parsed >> 24) & 0xFF),
g: (byte)((parsed >> 16) & 0xFF),
b: (byte)((parsed >> 8) & 0xFF));
}
else
{
// #AARRGGBB
color = new Color(
a: (byte)((parsed >> 24) & 0xFF),
r: (byte)((parsed >> 16) & 0xFF),
g: (byte)((parsed >> 8) & 0xFF),
b: (byte)(parsed & 0xFF));
}
return true;
}
else
color = default;
ReadOnlySpan<char> input = s.Slice(1);
// Handle shorthand cases like #FFF (RGB) or #FFFF (ARGB).
if (input.Length == 3 || input.Length == 4)
{
// Invalid hex color value provided
return AvaloniaProperty.UnsetValue;
var extendedLength = 2 * input.Length;
Span<char> extended = stackalloc char[extendedLength];
for (int i = 0; i < input.Length; i++)
{
extended[2 * i + 0] = input[i];
extended[2 * i + 1] = input[i];
}
return TryParseCore(extended, alphaPosition, ref color);
}
return TryParseCore(input, alphaPosition, ref color);
}
}
}

146
src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs

@ -1,6 +1,6 @@
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Utilities;
@ -11,8 +11,11 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public static class ColorHelper
{
private static readonly Dictionary<Color, string> cachedDisplayNames = new Dictionary<Color, string>();
private static readonly object cacheMutex = new object();
private static readonly Dictionary<HsvColor, string> _cachedDisplayNames = new Dictionary<HsvColor, string>();
private static readonly Dictionary<KnownColor, string> _cachedKnownColorNames = new Dictionary<KnownColor, string>();
private static readonly object _displayNameCacheMutex = new object();
private static readonly object _knownColorCacheMutex = new object();
private static readonly KnownColor[] _knownColors = (KnownColor[])Enum.GetValues(typeof(KnownColor));
/// <summary>
/// Gets the relative (perceptual) luminance/brightness of the given color.
@ -59,7 +62,36 @@ namespace Avalonia.Controls.Primitives
/// <returns>The approximate color display name.</returns>
public static string ToDisplayName(Color color)
{
// Without rounding, there are 16,777,216 possible RGB colors (without alpha).
var hsvColor = color.ToHsv();
// Handle extremes that are outside the below algorithm
if (color.A == 0x00)
{
return GetDisplayName(KnownColor.Transparent);
}
// HSV ----------------------------------------------------------------------
//
// There are far too many possible HSV colors to cache and search through
// for performance reasons. Therefore, the HSV color is rounded.
// Rounding is tolerable in this algorithm because it is perception based.
// Hue is the most important for user perception so is rounded the least.
// Then there is a lot of loss in rounding the saturation and value components
// which are not as closely related to perceived color.
//
// Hue : Round to nearest int (0..360)
// Saturation : Round to the nearest 1/10 (0..1)
// Value : Round to the nearest 1/10 (0..1)
// Alpha : Is ignored in this algorithm
//
// Rounding results in ~36_000 values to cache in the worse case.
//
// RGB ----------------------------------------------------------------------
//
// The original algorithm worked in RGB color space.
// If this code is every adjusted to work in RGB again note the following:
//
// Without rounding, there are 16_777_216 possible RGB colors (without alpha).
// This is too many to cache and search through for performance reasons.
// It is also needlessly large as there are only ~140 known/named colors.
// Therefore, rounding of the input color's component values is done to
@ -68,42 +100,67 @@ namespace Avalonia.Controls.Primitives
// The rounding value of 5 is specially chosen.
// It is a factor of 255 and therefore evenly divisible which improves
// the quality of the calculations.
double rounding = 5;
var roundedColor = new Color(
0xFF,
Convert.ToByte(Math.Round(color.R / rounding) * rounding),
Convert.ToByte(Math.Round(color.G / rounding) * rounding),
Convert.ToByte(Math.Round(color.B / rounding) * rounding));
var roundedHsvColor = new HsvColor(
1.0,
Math.Round(hsvColor.H, 0),
Math.Round(hsvColor.S, 1),
Math.Round(hsvColor.V, 1));
// Attempt to use a previously cached display name
lock (cacheMutex)
lock (_displayNameCacheMutex)
{
if (cachedDisplayNames.TryGetValue(roundedColor, out var displayName))
if (_cachedDisplayNames.TryGetValue(roundedHsvColor, out var displayName))
{
return displayName;
}
}
// Build the KnownColor name cache if it doesn't already exist
lock (_knownColorCacheMutex)
{
if (_cachedKnownColorNames.Count == 0)
{
for (int i = 1; i < _knownColors.Length; i++) // Skip 'None' so start at 1
{
KnownColor knownColor = _knownColors[i];
// Some known colors have the same numerical value. For example:
// - Aqua = 0xff00ffff
// - Cyan = 0xff00ffff
//
// This is not possible to represent in a dictionary which requires
// unique values. Therefore, only the first value is used.
if (!_cachedKnownColorNames.ContainsKey(knownColor))
{
_cachedKnownColorNames.Add(knownColor, GetDisplayName(knownColor));
}
}
}
}
// Find the closest known color by measuring 3D Euclidean distance (ignore alpha)
// This is done in HSV color space to most closely match user-perception
var closestKnownColor = KnownColor.None;
var closestKnownColorDistance = double.PositiveInfinity;
var knownColors = (KnownColor[])Enum.GetValues(typeof(KnownColor));
for (int i = 1; i < knownColors.Length; i++) // Skip 'None'
for (int i = 1; i < _knownColors.Length; i++) // Skip 'None' so start at 1
{
KnownColor knownColor = _knownColors[i];
// Transparent is skipped since alpha is ignored making it equivalent to White
if (knownColors[i] != KnownColor.Transparent)
if (knownColor != KnownColor.Transparent)
{
Color knownColor = KnownColors.ToColor(knownColors[i]);
HsvColor knownHsvColor = KnownColors.ToColor(knownColor).ToHsv();
double distance = Math.Sqrt(
Math.Pow((double)(roundedColor.R - knownColor.R), 2.0) +
Math.Pow((double)(roundedColor.G - knownColor.G), 2.0) +
Math.Pow((double)(roundedColor.B - knownColor.B), 2.0));
Math.Pow((roundedHsvColor.H - knownHsvColor.H), 2.0) +
Math.Pow((roundedHsvColor.S - knownHsvColor.S), 2.0) +
Math.Pow((roundedHsvColor.V - knownHsvColor.V), 2.0));
if (distance < closestKnownColorDistance)
{
closestKnownColor = knownColors[i];
closestKnownColor = knownColor;
closestKnownColorDistance = distance;
}
}
@ -113,26 +170,19 @@ namespace Avalonia.Controls.Primitives
// Cache results for next time as well
if (closestKnownColor != KnownColor.None)
{
var sb = StringBuilderCache.Acquire();
string name = closestKnownColor.ToString();
string? displayName;
// Add spaces converting PascalCase to human-readable names
for (int i = 0; i < name.Length; i++)
lock (_knownColorCacheMutex)
{
if (i != 0 &&
char.IsUpper(name[i]))
if (!_cachedKnownColorNames.TryGetValue(closestKnownColor, out displayName))
{
sb.Append(' ');
displayName = GetDisplayName(closestKnownColor);
}
sb.Append(name[i]);
}
string displayName = StringBuilderCache.GetStringAndRelease(sb);
lock (cacheMutex)
lock (_displayNameCacheMutex)
{
cachedDisplayNames.Add(roundedColor, displayName);
_cachedDisplayNames.Add(roundedHsvColor, displayName);
}
return displayName;
@ -142,5 +192,35 @@ namespace Avalonia.Controls.Primitives
return string.Empty;
}
}
/// <summary>
/// Gets the human-readable display name for the given <see cref="KnownColor"/>.
/// </summary>
/// <remarks>
/// This currently uses the <see cref="KnownColor"/> enum value's C# name directly
/// which limits it to the EN language only. In the future this should be localized
/// to other cultures.
/// </remarks>
/// <param name="knownColor">The <see cref="KnownColor"/> to get the display name for.</param>
/// <returns>The human-readable display name for the given <see cref="KnownColor"/>.</returns>
private static string GetDisplayName(KnownColor knownColor)
{
var sb = StringBuilderCache.Acquire();
string name = knownColor.ToString();
// Add spaces converting PascalCase to human-readable names
for (int i = 0; i < name.Length; i++)
{
if (i != 0 &&
char.IsUpper(name[i]))
{
sb.Append(' ');
}
sb.Append(name[i]);
}
return StringBuilderCache.GetStringAndRelease(sb);
}
}
}

2
src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs

@ -4,8 +4,6 @@
// Licensed to The Avalonia Project under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Layout;

5
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@ -42,7 +42,8 @@
</Panel>
</DropDownButton.Content>
<DropDownButton.Flyout>
<Flyout FlyoutPresenterClasses="nopadding">
<Flyout FlyoutPresenterClasses="nopadding"
Placement="Top">
<!-- The following is copy-pasted from the ColorView's control template.
It MUST always be kept in sync with the ColorView (which is master).
@ -216,6 +217,7 @@
Content="RGB"
CornerRadius="4,0,0,4"
BorderThickness="1,1,0,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@ -223,6 +225,7 @@
Content="HSV"
CornerRadius="0,4,4,0"
BorderThickness="0,1,1,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"

67
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml

@ -1,13 +1,20 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Note that the Slider thumb should generally follow the overall Slider dimensions.
Therefore, there are not currently separate resources to control it. -->
<x:Double x:Key="ColorSliderSize">20</x:Double>
<x:Double x:Key="ColorSliderTrackSize">20</x:Double>
<CornerRadius x:Key="ColorSliderCornerRadius">10</CornerRadius>
<CornerRadius x:Key="ColorSliderTrackCornerRadius">10</CornerRadius>
<ControlTheme x:Key="ColorSliderThumbTheme"
TargetType="Thumb">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<!--TODO: <Setter Property="BorderBrush" Value="{DynamicResource ColorControlDefaultSelectorBrush}" />-->
<Setter Property="BorderThickness" Value="3" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
@ -25,27 +32,28 @@
<Style Selector="^:horizontal">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Height" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Height" Value="{DynamicResource ColorSliderSize}" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
Background="{TemplateBinding Background}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Track Name="PART_Track"
Height="{DynamicResource ColorSliderTrackSize}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
VerticalAlignment="Center"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
@ -82,7 +90,7 @@
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Theme="{DynamicResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"
@ -97,26 +105,27 @@
<Style Selector="^:vertical">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Width" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Width" Value="{DynamicResource ColorSliderSize}" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Border HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Border HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
Background="{TemplateBinding Background}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Track Name="PART_Track"
HorizontalAlignment="Stretch"
Width="{DynamicResource ColorSliderTrackSize}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
@ -154,7 +163,7 @@
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Theme="{DynamicResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"

3
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@ -8,7 +8,6 @@
<pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<converters:ColorToHexConverter x:Key="ColorToHexConverter" />
<converters:DoNothingForNullConverter x:Key="DoNothingForNullConverter" />
<globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
<x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
@ -465,6 +464,7 @@
Content="RGB"
CornerRadius="4,0,0,4"
BorderThickness="1,1,0,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@ -472,6 +472,7 @@
Content="HSV"
CornerRadius="0,4,4,0"
BorderThickness="0,1,1,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"

4
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml

@ -42,7 +42,7 @@
</Panel>
</DropDownButton.Content>
<DropDownButton.Flyout>
<Flyout>
<Flyout Placement="Top">
<!-- The following is copy-pasted from the ColorView's control template.
It MUST always be kept in sync with the ColorView (which is master).
@ -216,6 +216,7 @@
Content="RGB"
CornerRadius="0,0,0,0"
BorderThickness="1,1,0,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@ -223,6 +224,7 @@
Content="HSV"
CornerRadius="0,0,0,0"
BorderThickness="0,1,1,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"

67
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml

@ -1,13 +1,20 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Note that the Slider thumb should generally follow the overall Slider dimensions.
Therefore, there are not currently separate resources to control it. -->
<x:Double x:Key="ColorSliderSize">20</x:Double>
<x:Double x:Key="ColorSliderTrackSize">20</x:Double>
<CornerRadius x:Key="ColorSliderCornerRadius">10</CornerRadius>
<CornerRadius x:Key="ColorSliderTrackCornerRadius">10</CornerRadius>
<ControlTheme x:Key="ColorSliderThumbTheme"
TargetType="Thumb">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeForegroundBrush}" />
<!--TODO: <Setter Property="BorderBrush" Value="{DynamicResource ColorControlDefaultSelectorBrush}" />-->
<Setter Property="BorderThickness" Value="3" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
@ -25,27 +32,28 @@
<Style Selector="^:horizontal">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Height" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Height" Value="{DynamicResource ColorSliderSize}" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
Background="{TemplateBinding Background}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Track Name="PART_Track"
Height="{DynamicResource ColorSliderTrackSize}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
VerticalAlignment="Center"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
@ -82,7 +90,7 @@
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Theme="{DynamicResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"
@ -97,26 +105,27 @@
<Style Selector="^:vertical">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Width" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Width" Value="{DynamicResource ColorSliderSize}" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Border HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Border HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
Background="{TemplateBinding Background}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Track Name="PART_Track"
HorizontalAlignment="Stretch"
Width="{DynamicResource ColorSliderTrackSize}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
@ -154,7 +163,7 @@
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Theme="{DynamicResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"

3
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

@ -8,7 +8,6 @@
<pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<converters:ColorToHexConverter x:Key="ColorToHexConverter" />
<converters:DoNothingForNullConverter x:Key="DoNothingForNullConverter" />
<globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
<x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
@ -427,6 +426,7 @@
Content="RGB"
CornerRadius="0,0,0,0"
BorderThickness="1,1,0,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@ -434,6 +434,7 @@
Content="HSV"
CornerRadius="0,0,0,0"
BorderThickness="0,1,1,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"

2
src/Avalonia.Controls.ItemsRepeater/Layout/UniformGridLayout.cs

@ -113,7 +113,7 @@ namespace Avalonia.Layout
AvaloniaProperty.Register<UniformGridLayout, double>(nameof(MinRowSpacing));
/// <summary>
/// Defines the <see cref="MaximumRowsOrColumnsProperty"/> property.
/// Defines the <see cref="MaximumRowsOrColumns"/> property.
/// </summary>
public static readonly StyledProperty<int> MaximumRowsOrColumnsProperty =
AvaloniaProperty.Register<UniformGridLayout, int>(nameof(MaximumRowsOrColumns));

1
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -184,6 +184,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
_cts?.Cancel();
_cts = null;
_isShuttingDown = false;
Dispatcher.UIThread.InvokeShutdown();
}
return true;

104
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs

@ -52,34 +52,32 @@ namespace Avalonia.Controls
/// </summary>
/// <value>The identifier for the <see cref="IsTextCompletionEnabled" /> property.</value>
public static readonly StyledProperty<bool> IsTextCompletionEnabledProperty =
AvaloniaProperty.Register<AutoCompleteBox, bool>(nameof(IsTextCompletionEnabled));
AvaloniaProperty.Register<AutoCompleteBox, bool>(
nameof(IsTextCompletionEnabled));
/// <summary>
/// Identifies the <see cref="ItemTemplate" /> property.
/// </summary>
/// <value>The identifier for the <see cref="ItemTemplate" /> property.</value>
public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty =
AvaloniaProperty.Register<AutoCompleteBox, IDataTemplate>(nameof(ItemTemplate));
AvaloniaProperty.Register<AutoCompleteBox, IDataTemplate>(
nameof(ItemTemplate));
/// <summary>
/// Identifies the <see cref="IsDropDownOpen" /> property.
/// </summary>
/// <value>The identifier for the <see cref="IsDropDownOpen" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, bool> IsDropDownOpenProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, bool>(
nameof(IsDropDownOpen),
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
public static readonly StyledProperty<bool> IsDropDownOpenProperty =
AvaloniaProperty.Register<AutoCompleteBox, bool>(
nameof(IsDropDownOpen));
/// <summary>
/// Identifies the <see cref="SelectedItem" /> property.
/// </summary>
/// <value>The identifier the <see cref="SelectedItem" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, object?> SelectedItemProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, object?>(
public static readonly StyledProperty<object?> SelectedItemProperty =
AvaloniaProperty.Register<AutoCompleteBox, object?>(
nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
@ -115,58 +113,50 @@ namespace Avalonia.Controls
/// Identifies the <see cref="ItemFilter" /> property.
/// </summary>
/// <value>The identifier for the <see cref="ItemFilter" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<object?>?> ItemFilterProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<object?>?>(
nameof(ItemFilter),
o => o.ItemFilter,
(o, v) => o.ItemFilter = v);
public static readonly StyledProperty<AutoCompleteFilterPredicate<object?>?> ItemFilterProperty =
AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterPredicate<object?>?>(
nameof(ItemFilter));
/// <summary>
/// Identifies the <see cref="TextFilter" /> property.
/// </summary>
/// <value>The identifier for the <see cref="TextFilter" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<string?>?> TextFilterProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<string?>?>(
public static readonly StyledProperty<AutoCompleteFilterPredicate<string?>?> TextFilterProperty =
AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterPredicate<string?>?>(
nameof(TextFilter),
o => o.TextFilter,
(o, v) => o.TextFilter = v,
unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
defaultValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
/// <summary>
/// Identifies the <see cref="ItemSelector" /> property.
/// </summary>
/// <value>The identifier for the <see cref="ItemSelector" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<object>?> ItemSelectorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<object>?>(
nameof(ItemSelector),
o => o.ItemSelector,
(o, v) => o.ItemSelector = v);
public static readonly StyledProperty<AutoCompleteSelector<object>?> ItemSelectorProperty =
AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteSelector<object>?>(
nameof(ItemSelector));
/// <summary>
/// Identifies the <see cref="TextSelector" /> property.
/// </summary>
/// <value>The identifier for the <see cref="TextSelector" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<string?>?> TextSelectorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<string?>?>(
nameof(TextSelector),
o => o.TextSelector,
(o, v) => o.TextSelector = v);
public static readonly StyledProperty<AutoCompleteSelector<string?>?> TextSelectorProperty =
AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteSelector<string?>?>(
nameof(TextSelector));
/// <summary>
/// Identifies the <see cref="Items" /> property.
/// </summary>
/// <value>The identifier for the <see cref="Items" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, IEnumerable?> ItemsProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, IEnumerable?>(
nameof(Items),
o => o.Items,
(o, v) => o.Items = v);
public static readonly StyledProperty<IEnumerable?> ItemsProperty =
AvaloniaProperty.Register<AutoCompleteBox, IEnumerable?>(
nameof(Items));
public static readonly DirectProperty<AutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?> AsyncPopulatorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?>(
nameof(AsyncPopulator),
o => o.AsyncPopulator,
(o, v) => o.AsyncPopulator = v);
/// <summary>
/// Identifies the <see cref="AsyncPopulator" /> property.
/// </summary>
/// <value>The identifier for the <see cref="AsyncPopulator" /> property.</value>
public static readonly StyledProperty<Func<string?, CancellationToken, Task<IEnumerable<object>>>?> AsyncPopulatorProperty =
AvaloniaProperty.Register<AutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?>(
nameof(AsyncPopulator));
/// <summary>
/// Gets or sets the minimum number of characters required to be entered
@ -265,8 +255,8 @@ namespace Avalonia.Controls
/// </value>
public bool IsDropDownOpen
{
get => _isDropDownOpen;
set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value);
get => GetValue(IsDropDownOpenProperty);
set => SetValue(IsDropDownOpenProperty, value);
}
/// <summary>
@ -303,8 +293,8 @@ namespace Avalonia.Controls
/// </remarks>
public object? SelectedItem
{
get => _selectedItem;
set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
get => GetValue(SelectedItemProperty);
set => SetValue(SelectedItemProperty, value);
}
/// <summary>
@ -388,8 +378,8 @@ namespace Avalonia.Controls
/// </remarks>
public AutoCompleteFilterPredicate<object?>? ItemFilter
{
get => _itemFilter;
set => SetAndRaise(ItemFilterProperty, ref _itemFilter, value);
get => GetValue(ItemFilterProperty);
set => SetValue(ItemFilterProperty, value);
}
/// <summary>
@ -406,8 +396,8 @@ namespace Avalonia.Controls
/// </remarks>
public AutoCompleteFilterPredicate<string?>? TextFilter
{
get => _textFilter;
set => SetAndRaise(TextFilterProperty, ref _textFilter, value);
get => GetValue(TextFilterProperty);
set => SetValue(TextFilterProperty, value);
}
/// <summary>
@ -420,8 +410,8 @@ namespace Avalonia.Controls
/// </value>
public AutoCompleteSelector<object>? ItemSelector
{
get => _itemSelector;
set => SetAndRaise(ItemSelectorProperty, ref _itemSelector, value);
get => GetValue(ItemSelectorProperty);
set => SetValue(ItemSelectorProperty, value);
}
/// <summary>
@ -436,14 +426,14 @@ namespace Avalonia.Controls
/// </value>
public AutoCompleteSelector<string?>? TextSelector
{
get => _textSelector;
set => SetAndRaise(TextSelectorProperty, ref _textSelector, value);
get => GetValue(TextSelectorProperty);
set => SetValue(TextSelectorProperty, value);
}
public Func<string?, CancellationToken, Task<IEnumerable<object>>>? AsyncPopulator
{
get => _asyncPopulator;
set => SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value);
get => GetValue(AsyncPopulatorProperty);
set => SetValue(AsyncPopulatorProperty, value);
}
/// <summary>
@ -454,8 +444,8 @@ namespace Avalonia.Controls
/// drop-down portion of the <see cref="AutoCompleteBox" /> control.</value>
public IEnumerable? Items
{
get => _itemsEnumerable;
set => SetAndRaise(ItemsProperty, ref _itemsEnumerable, value);
get => GetValue(ItemsProperty);
set => SetValue(ItemsProperty, value);
}
}
}

65
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -94,8 +94,6 @@ namespace Avalonia.Controls
/// </summary>
private const string ElementTextBox = "PART_TextBox";
private IEnumerable? _itemsEnumerable;
/// <summary>
/// Gets or sets a local cached copy of the items data.
/// </summary>
@ -188,24 +186,15 @@ namespace Avalonia.Controls
/// </summary>
private IDisposable? _collectionChangeSubscription;
private Func<string?, CancellationToken, Task<IEnumerable<object>>>? _asyncPopulator;
private CancellationTokenSource? _populationCancellationTokenSource;
private bool _itemTemplateIsFromValueMemberBinding = true;
private bool _settingItemTemplateFromValueMemberBinding;
private object? _selectedItem;
private bool _isDropDownOpen;
private bool _isFocused = false;
private string? _searchText = string.Empty;
private AutoCompleteFilterPredicate<object?>? _itemFilter;
private AutoCompleteFilterPredicate<string?>? _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith);
private AutoCompleteSelector<object>? _itemSelector;
private AutoCompleteSelector<string?>? _textSelector;
private readonly EventHandler _populateDropDownHandler;
/// <summary>
@ -264,7 +253,7 @@ namespace Avalonia.Controls
bool isEnabled = (bool)e.NewValue!;
if (!isEnabled)
{
IsDropDownOpen = false;
SetCurrentValue(IsDropDownOpenProperty, false);
}
}
@ -388,7 +377,7 @@ namespace Avalonia.Controls
{
// Reset the old value before it was incorrectly written
_ignorePropertyChange = true;
SetValue(e.Property, e.OldValue);
SetCurrentValue(e.Property, e.OldValue);
throw new InvalidOperationException("Cannot set read-only property SearchText.");
}
@ -403,7 +392,7 @@ namespace Avalonia.Controls
AutoCompleteFilterMode mode = (AutoCompleteFilterMode)e.NewValue!;
// Sets the filter predicate for the new value
TextFilter = AutoCompleteSearch.GetFilter(mode);
SetCurrentValue(TextFilterProperty, AutoCompleteSearch.GetFilter(mode));
}
/// <summary>
@ -417,12 +406,12 @@ namespace Avalonia.Controls
// If null, revert to the "None" predicate
if (value == null)
{
FilterMode = AutoCompleteFilterMode.None;
SetCurrentValue(FilterModeProperty, AutoCompleteFilterMode.None);
}
else
{
FilterMode = AutoCompleteFilterMode.Custom;
TextFilter = null;
SetCurrentValue(FilterModeProperty, AutoCompleteFilterMode.Custom);
SetCurrentValue(TextFilterProperty, null);
}
}
@ -442,7 +431,7 @@ namespace Avalonia.Controls
}
private void OnValueMemberBindingChanged(IBinding? value)
{
if(_itemTemplateIsFromValueMemberBinding)
if (_itemTemplateIsFromValueMemberBinding)
{
var template =
new FuncDataTemplate(
@ -456,7 +445,7 @@ namespace Avalonia.Controls
});
_settingItemTemplateFromValueMemberBinding = true;
ItemTemplate = template;
SetCurrentValue(ItemTemplateProperty, template);
_settingItemTemplateFromValueMemberBinding = false;
}
}
@ -713,7 +702,7 @@ namespace Avalonia.Controls
// The drop down is not open, the Down key will toggle it open.
if (e.Key == Key.Down)
{
IsDropDownOpen = true;
SetCurrentValue(IsDropDownOpenProperty, true);
e.Handled = true;
}
}
@ -722,7 +711,7 @@ namespace Avalonia.Controls
switch (e.Key)
{
case Key.F4:
IsDropDownOpen = !IsDropDownOpen;
SetCurrentValue(IsDropDownOpenProperty, !IsDropDownOpen);
e.Handled = true;
break;
@ -827,7 +816,7 @@ namespace Avalonia.Controls
}
else
{
IsDropDownOpen = false;
SetCurrentValue(IsDropDownOpenProperty, false);
_userCalledPopulate = false;
ClearTextBoxSelection();
}
@ -1021,7 +1010,7 @@ namespace Avalonia.Controls
if (args.Cancel)
{
_ignorePropertyChange = true;
SetValue(IsDropDownOpenProperty, oldValue);
SetCurrentValue(IsDropDownOpenProperty, oldValue);
}
else
{
@ -1046,7 +1035,7 @@ namespace Avalonia.Controls
if (args.Cancel)
{
_ignorePropertyChange = true;
SetValue(IsDropDownOpenProperty, oldValue);
SetCurrentValue(IsDropDownOpenProperty, oldValue);
}
else
{
@ -1066,7 +1055,7 @@ namespace Avalonia.Controls
// Force the drop down dependency property to be false.
if (IsDropDownOpen)
{
IsDropDownOpen = false;
SetCurrentValue(IsDropDownOpenProperty, false);
}
// Fire the DropDownClosed event
@ -1088,7 +1077,7 @@ namespace Avalonia.Controls
// Update the prefix/search text.
SearchText = Text;
if(TryPopulateAsync(SearchText))
if (TryPopulateAsync(SearchText))
{
return;
}
@ -1110,7 +1099,7 @@ namespace Avalonia.Controls
_populationCancellationTokenSource?.Dispose();
_populationCancellationTokenSource = null;
if(_asyncPopulator == null)
if (AsyncPopulator == null)
{
return false;
}
@ -1127,7 +1116,7 @@ namespace Avalonia.Controls
try
{
IEnumerable<object> result = await _asyncPopulator!.Invoke(searchText, cancellationToken);
IEnumerable<object> result = await AsyncPopulator!.Invoke(searchText, cancellationToken);
var resultList = result.ToList();
if (cancellationToken.IsCancellationRequested)
@ -1139,7 +1128,7 @@ namespace Avalonia.Controls
{
if (!cancellationToken.IsCancellationRequested)
{
Items = resultList;
SetCurrentValue(ItemsProperty, resultList);
PopulateComplete();
}
});
@ -1199,7 +1188,7 @@ namespace Avalonia.Controls
private string? FormatValue(object? value, bool clearDataContext)
{
string? result = FormatValue(value);
if(clearDataContext && _valueBindingEvaluator != null)
if (clearDataContext && _valueBindingEvaluator != null)
{
_valueBindingEvaluator.ClearDataContext();
}
@ -1332,7 +1321,7 @@ namespace Avalonia.Controls
// 1. Minimum prefix length
// 2. If a delay timer is in use, use it
bool populateReady = newText.Length >= MinimumPrefixLength && MinimumPrefixLength >= 0;
if(populateReady && MinimumPrefixLength == 0 && String.IsNullOrEmpty(newText) && String.IsNullOrEmpty(SearchText))
if (populateReady && MinimumPrefixLength == 0 && String.IsNullOrEmpty(newText) && String.IsNullOrEmpty(SearchText))
{
populateReady = false;
}
@ -1361,10 +1350,12 @@ namespace Avalonia.Controls
{
_skipSelectedItemTextUpdate = true;
}
SelectedItem = null;
SetCurrentValue(SelectedItemProperty, null);
if (IsDropDownOpen)
{
IsDropDownOpen = false;
SetCurrentValue(IsDropDownOpenProperty, false);
}
}
}
@ -1600,7 +1591,7 @@ namespace Avalonia.Controls
if (isDropDownOpen != IsDropDownOpen)
{
_ignorePropertyChange = true;
IsDropDownOpen = isDropDownOpen;
SetCurrentValue(IsDropDownOpenProperty, isDropDownOpen);
}
if (IsDropDownOpen)
{
@ -1688,7 +1679,7 @@ namespace Avalonia.Controls
{
_skipSelectedItemTextUpdate = true;
}
SelectedItem = newSelectedItem;
SetCurrentValue(SelectedItemProperty, newSelectedItem);
// Restore updates for TextSelection
if (_ignoreTextSelectionChange)
@ -1784,7 +1775,7 @@ namespace Avalonia.Controls
/// <param name="e">The selection changed event data.</param>
private void OnAdapterSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
SelectedItem = _adapter!.SelectedItem;
SetCurrentValue(SelectedItemProperty, _adapter!.SelectedItem);
}
//TODO Check UpdateTextCompletion
@ -1795,7 +1786,7 @@ namespace Avalonia.Controls
/// <param name="e">The event data.</param>
private void OnAdapterSelectionComplete(object? sender, RoutedEventArgs e)
{
IsDropDownOpen = false;
SetCurrentValue(IsDropDownOpenProperty, false);
// Completion will update the selected value
//UpdateTextCompletion(false);

7
src/Avalonia.Controls/Carousel.cs

@ -1,7 +1,6 @@
using Avalonia.Animation;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
namespace Avalonia.Controls
{
@ -20,8 +19,8 @@ namespace Avalonia.Controls
/// The default value of <see cref="ItemsControl.ItemsPanelProperty"/> for
/// <see cref="Carousel"/>.
/// </summary>
private static readonly ITemplate<Panel> PanelTemplate =
new FuncTemplate<Panel>(() => new VirtualizingCarouselPanel());
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new VirtualizingCarouselPanel());
private IScrollable? _scroller;
@ -31,7 +30,7 @@ namespace Avalonia.Controls
static Carousel()
{
SelectionModeProperty.OverrideDefaultValue<Carousel>(SelectionMode.AlwaysSelected);
ItemsPanelProperty.OverrideDefaultValue<Carousel>(PanelTemplate);
ItemsPanelProperty.OverrideDefaultValue<Carousel>(DefaultPanel);
}
/// <summary>

7
src/Avalonia.Controls/ComboBox.cs

@ -1,13 +1,10 @@
using System;
using System.Diagnostics;
using System.Linq;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
@ -29,8 +26,8 @@ namespace Avalonia.Controls
/// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
private static readonly FuncTemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new VirtualizingStackPanel());
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new VirtualizingStackPanel());
/// <summary>
/// Defines the <see cref="IsDropDownOpen"/> property.

5
src/Avalonia.Controls/ContextMenu.cs

@ -4,7 +4,6 @@ using System.ComponentModel;
using Avalonia.Automation.Peers;
using System.Linq;
using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Primitives.PopupPositioning;
@ -84,8 +83,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Control?> PlacementTargetProperty =
Popup.PlacementTargetProperty.AddOwner<ContextMenu>();
private static readonly ITemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel { Orientation = Orientation.Vertical });
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new StackPanel { Orientation = Orientation.Vertical });
private Popup? _popup;
private List<Control>? _attachedControls;
private IInputElement? _previousFocus;

8
src/Avalonia.Controls/Control.cs

@ -527,5 +527,13 @@ namespace Avalonia.Controls
}
}
}
// Since we are resetting the dispatcher instance, the callback might never arrive
internal static void ResetLoadedQueueForUnitTests()
{
_loadedQueue.Clear();
_loadedProcessingQueue.Clear();
_isLoadedProcessing = false;
}
}
}

12
src/Avalonia.Controls/ItemsControl.cs

@ -2,9 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
@ -29,8 +27,8 @@ namespace Avalonia.Controls
/// <summary>
/// The default value for the <see cref="ItemsPanel"/> property.
/// </summary>
private static readonly FuncTemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel());
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new StackPanel());
/// <summary>
/// Defines the <see cref="Items"/> property.
@ -58,8 +56,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
/// </summary>
public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty =
AvaloniaProperty.Register<ItemsControl, ITemplate<Panel>>(nameof(ItemsPanel), DefaultPanel);
public static readonly StyledProperty<ITemplate<Panel?>> ItemsPanelProperty =
AvaloniaProperty.Register<ItemsControl, ITemplate<Panel?>>(nameof(ItemsPanel), DefaultPanel);
/// <summary>
/// Defines the <see cref="ItemsSource"/> property.
@ -202,7 +200,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the panel used to display the items.
/// </summary>
public ITemplate<Panel> ItemsPanel
public ITemplate<Panel?> ItemsPanel
{
get => GetValue(ItemsPanelProperty);
set => SetValue(ItemsPanelProperty, value);

7
src/Avalonia.Controls/ListBox.cs

@ -1,13 +1,10 @@
using System.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -20,8 +17,8 @@ namespace Avalonia.Controls
/// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
private static readonly FuncTemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new VirtualizingStackPanel());
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new VirtualizingStackPanel());
/// <summary>
/// Defines the <see cref="Scroll"/> property.

6
src/Avalonia.Controls/Menu.cs

@ -1,7 +1,6 @@
using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
@ -14,9 +13,8 @@ namespace Avalonia.Controls
/// </summary>
public class Menu : MenuBase, IMainMenu
{
private static readonly ITemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel { Orientation = Orientation.Horizontal });
private static readonly FuncTemplate<Panel?> DefaultPanel =
new (() => new StackPanel { Orientation = Orientation.Horizontal });
/// <summary>
/// Initializes a new instance of the <see cref="Menu"/> class.

5
src/Avalonia.Controls/MenuItem.cs

@ -4,7 +4,6 @@ using System.Linq;
using Avalonia.Reactive;
using System.Windows.Input;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
@ -107,8 +106,8 @@ namespace Avalonia.Controls
/// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
private static readonly ITemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel());
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new StackPanel());
private bool _commandCanExecute = true;
private bool _commandBindingError;

12
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -16,7 +16,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
/// </summary>
public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty =
public static readonly StyledProperty<ITemplate<Panel?>> ItemsPanelProperty =
ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenter>();
private PanelContainerGenerator? _generator;
@ -68,7 +68,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Gets or sets a template which creates the <see cref="Panel"/> used to display the items.
/// </summary>
public ITemplate<Panel> ItemsPanel
public ITemplate<Panel?> ItemsPanel
{
get => GetValue(ItemsPanelProperty);
set => SetValue(ItemsPanelProperty, value);
@ -166,6 +166,12 @@ namespace Avalonia.Controls.Presenters
}
Panel = ItemsPanel.Build();
if (Panel is null)
{
return;
}
Panel.TemplatedParent = TemplatedParent;
Panel.IsItemsHost = true;
_scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
@ -183,7 +189,7 @@ namespace Avalonia.Controls.Presenters
else
CreateSimplePanelGenerator();
if(Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
scrollSnapPointsInfo.VerticalSnapPointsChanged += (s, e) =>
{

2
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -49,7 +49,7 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register<ScrollBar, Orientation>(nameof(Orientation), Orientation.Vertical);
/// <summary>
/// Defines the <see cref="IsExpandedProperty"/> property.
/// Defines the <see cref="IsExpanded"/> property.
/// </summary>
public static readonly DirectProperty<ScrollBar, bool> IsExpandedProperty =
AvaloniaProperty.RegisterDirect<ScrollBar, bool>(

6
src/Avalonia.Controls/Primitives/TabStrip.cs

@ -1,15 +1,13 @@
using Avalonia.Controls.Generators;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
public class TabStrip : SelectingItemsControl
{
private static readonly FuncTemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new WrapPanel { Orientation = Orientation.Horizontal });
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new WrapPanel { Orientation = Orientation.Horizontal });
static TabStrip()
{

19
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -286,14 +286,17 @@ namespace Avalonia.Controls.Primitives
{
Logger.TryGet(LogEventLevel.Verbose, LogArea.Control)?.Log(this, "Creating control template");
var (child, nameScope) = template.Build(this);
ApplyTemplatedParent(child, this);
((ISetLogicalParent)child).SetParent(this);
VisualChildren.Add(child);
var e = new TemplateAppliedEventArgs(nameScope);
OnApplyTemplate(e);
RaiseEvent(e);
if (template.Build(this) is { } templateResult)
{
var (child, nameScope) = templateResult;
ApplyTemplatedParent(child, this);
((ISetLogicalParent)child).SetParent(this);
VisualChildren.Add(child);
var e = new TemplateAppliedEventArgs(nameScope);
OnApplyTemplate(e);
RaiseEvent(e);
}
}
_appliedTemplate = template;

2
src/Avalonia.Controls/ScrollViewer.cs

@ -200,7 +200,7 @@ namespace Avalonia.Controls
ScrollBarVisibility.Auto);
/// <summary>
/// Defines the <see cref="IsExpandedProperty"/> property.
/// Defines the <see cref="IsExpanded"/> property.
/// </summary>
public static readonly DirectProperty<ScrollViewer, bool> IsExpandedProperty =
ScrollBar.IsExpandedProperty.AddOwner<ScrollViewer>(o => o.IsExpanded);

10
src/Avalonia.Controls/Slider.cs

@ -80,16 +80,16 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<Slider, TickPlacement>(nameof(TickPlacement), 0d);
/// <summary>
/// Defines the <see cref="TicksProperty"/> property.
/// Defines the <see cref="Ticks"/> property.
/// </summary>
public static readonly StyledProperty<AvaloniaList<double>?> TicksProperty =
TickBar.TicksProperty.AddOwner<Slider>();
// Slider required parts
private bool _isDragging;
private Track? _track;
private Button? _decreaseButton;
private Button? _increaseButton;
protected bool _isDragging;
protected Track? _track;
protected Button? _decreaseButton;
protected Button? _increaseButton;
private IDisposable? _decreaseButtonPressDispose;
private IDisposable? _decreaseButtonReleaseDispose;
private IDisposable? _increaseButtonSubscription;

7
src/Avalonia.Controls/TabControl.cs

@ -1,8 +1,6 @@
using System.ComponentModel;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -12,7 +10,6 @@ using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using Avalonia.Automation;
using Avalonia.Controls.Metadata;
using Avalonia.Data;
namespace Avalonia.Controls
{
@ -61,8 +58,8 @@ namespace Avalonia.Controls
/// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
private static readonly FuncTemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new WrapPanel());
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new WrapPanel());
/// <summary>
/// Initializes static members of the <see cref="TabControl"/> class.

4
src/Avalonia.Controls/Templates/FuncTemplate`1.cs

@ -7,7 +7,7 @@ namespace Avalonia.Controls.Templates
/// Creates a control from a <see cref="Func{TControl}"/>.
/// </summary>
/// <typeparam name="TControl">The type of control.</typeparam>
public class FuncTemplate<TControl> : ITemplate<TControl> where TControl : Control
public class FuncTemplate<TControl> : ITemplate<TControl> where TControl : Control?
{
private readonly Func<TControl> _func;
@ -31,6 +31,6 @@ namespace Avalonia.Controls.Templates
return _func();
}
object ITemplate.Build() => Build();
object? ITemplate.Build() => Build();
}
}

4
src/Avalonia.Controls/Templates/IControlTemplate.cs

@ -1,13 +1,11 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Styling;
namespace Avalonia.Controls.Templates
{
/// <summary>
/// Interface representing a template used to build a <see cref="TemplatedControl"/>.
/// </summary>
public interface IControlTemplate : ITemplate<TemplatedControl, ControlTemplateResult>
public interface IControlTemplate : ITemplate<TemplatedControl, ControlTemplateResult?>
{
}

2
src/Avalonia.Controls/TopLevel.cs

@ -75,7 +75,7 @@ namespace Avalonia.Controls
unsetValue: WindowTransparencyLevel.None);
/// <summary>
/// Defines the <see cref="TransparencyBackgroundFallbackProperty"/> property.
/// Defines the <see cref="TransparencyBackgroundFallback"/> property.
/// </summary>
public static readonly StyledProperty<IBrush> TransparencyBackgroundFallbackProperty =
AvaloniaProperty.Register<TopLevel, IBrush>(nameof(TransparencyBackgroundFallback), Brushes.White);

4
src/Avalonia.Controls/TreeView.cs

@ -10,6 +10,7 @@ using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Layout;
using Avalonia.Threading;
using Avalonia.VisualTree;
@ -163,6 +164,9 @@ namespace Avalonia.Controls
{
item.IsExpanded = true;
if (item.Presenter?.Panel is null)
(this.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
if (item.Presenter?.Panel is { } panel)
{
foreach (var child in panel.Children)

23
src/Avalonia.Controls/TreeViewItem.cs

@ -40,8 +40,8 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<TreeViewItem, int>(
nameof(Level), o => o.Level);
private static readonly ITemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel());
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new StackPanel());
private TreeView? _treeView;
private Control? _header;
@ -90,8 +90,20 @@ namespace Avalonia.Controls
internal TreeView? TreeViewOwner => _treeView;
protected internal override Control CreateContainerForItemOverride() => new TreeViewItem();
protected internal override bool IsItemItsOwnContainerOverride(Control item) => item is TreeViewItem;
protected internal override Control CreateContainerForItemOverride()
{
return EnsureTreeView().CreateContainerForItemOverride();
}
protected internal override bool IsItemItsOwnContainerOverride(Control item)
{
return EnsureTreeView().IsItemItsOwnContainerOverride(item);
}
protected internal override void PrepareContainerForItemOverride(Control container, object? item, int index)
{
EnsureTreeView().PrepareContainerForItemOverride(container, item, index);
}
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
@ -283,6 +295,9 @@ namespace Avalonia.Controls
return logical != null ? result : @default;
}
private TreeView EnsureTreeView() => _treeView ??
throw new InvalidOperationException("The TreeViewItem is not part of a TreeView.");
private void HeaderDoubleTapped(object? sender, TappedEventArgs e)
{
OnHeaderDoubleTapped(e);

2
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -1088,7 +1088,7 @@ namespace Avalonia.Controls
++newIndex;
}
if (realizedIndex <= 0)
if (realizedIndex < 0)
{
// The insertion point was before the first element, update the first index.
_firstIndex += count;

2
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@ -118,7 +118,7 @@
</Border>
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="False"
Placement="Right"
Placement="RightEdgeAlignedTop"
HorizontalOffset="{DynamicResource MenuFlyoutSubItemPopupHorizontalOffset}"
IsLightDismissEnabled="False"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">

2
src/Avalonia.Themes.Simple/Controls/MenuItem.xaml

@ -65,7 +65,7 @@
IsLightDismissEnabled="False"
IsOpen="{TemplateBinding IsSubMenuOpen,
Mode=TwoWay}"
Placement="Right">
Placement="RightEdgeAlignedTop">
<Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="{TemplateBinding BorderThickness}">

2
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -58,7 +58,6 @@
<Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
<Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />
<Compile Include="XamlLoadException.cs" />
<Compile Include="..\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs" />
<Compile Include="XamlTypes.cs" />
</ItemGroup>
<ItemGroup>
@ -69,6 +68,7 @@
<Import Project="..\..\..\build\ApiDiff.props" />
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\TrimmingEnable.props" />
<Import Project="..\..\..\build\NullableEnable.props" />
<ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.Markup.Xaml.UnitTests, PublicKey=$(AvaloniaPublicKey)" />

5
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -1,8 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Markup.Xaml
{
/// <summary>
@ -79,7 +78,7 @@ namespace Avalonia.Markup.Xaml
var compiledLoader = assetLocator.GetAssembly(uri, baseUri)
?.GetType("CompiledAvaloniaXaml.!XamlLoader")
?.GetMethod("TryLoad", new[] { typeof(System.IServiceProvider), typeof(string) });
?.GetMethod("TryLoad", new[] { typeof(IServiceProvider), typeof(string) });
if (compiledLoader != null)
{
var compiledResult = compiledLoader.Invoke(null, new object?[] { sp, absoluteUri.ToString()});

14
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs

@ -14,18 +14,20 @@ namespace Avalonia.Markup.Xaml.Converters
[RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)]
public class AvaloniaPropertyTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var registry = AvaloniaPropertyRegistry.Instance;
var (ns, owner, propertyName) = PropertyParser.Parse(new CharacterReader(((string)value).AsSpan()));
var ownerType = TryResolveOwnerByName(context, ns, owner);
var targetType = context.GetFirstParent<ControlTemplate>()?.TargetType ??
context.GetFirstParent<Style>()?.Selector?.TargetType ??
var targetType = context?.GetFirstParent<ControlTemplate>()?.TargetType ??
context?.GetFirstParent<Style>()?.Selector?.TargetType ??
typeof(Control);
var effectiveOwner = ownerType ?? targetType;
var property = registry.FindRegistered(effectiveOwner, propertyName);
@ -51,11 +53,11 @@ namespace Avalonia.Markup.Xaml.Converters
}
[RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)]
private static Type TryResolveOwnerByName(ITypeDescriptorContext context, string ns, string owner)
private static Type? TryResolveOwnerByName(ITypeDescriptorContext? context, string? ns, string? owner)
{
if (owner != null)
{
var result = context.ResolveType(ns, owner);
var result = context?.ResolveType(ns, owner);
if (result == null)
{

6
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs

@ -6,12 +6,14 @@ namespace Avalonia.Markup.Xaml.Converters
{
public class AvaloniaUriTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
/// <inheritdoc />
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var s = value as string;
if (s == null)

11
src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs

@ -1,20 +1,21 @@
using System;
using System.ComponentModel;
using System.Globalization;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
namespace Avalonia.Markup.Xaml.Converters
{
using System.ComponentModel;
public class BitmapTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var s = (string)value;
var uri = s.StartsWith("/")
@ -25,7 +26,7 @@ namespace Avalonia.Markup.Xaml.Converters
return new Bitmap(uri.LocalPath);
var assets = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
return new Bitmap(assets.Open(uri, context.GetContextBaseUri()));
return new Bitmap(assets.Open(uri, context?.GetContextBaseUri()));
}
}
}

8
src/Markup/Avalonia.Markup.Xaml/Converters/ColorToBrushConverter.cs

@ -23,7 +23,7 @@ namespace Avalonia.Markup.Xaml.Converters
/// If <paramref name="value"/> is a <see cref="Color"/> and <paramref name="targetType"/>
/// is <see cref="IBrush"/> then converts the color to a solid color brush.
/// </returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return Convert(value, targetType);
}
@ -40,7 +40,7 @@ namespace Avalonia.Markup.Xaml.Converters
/// If <paramref name="value"/> is an <see cref="ISolidColorBrush"/> and <paramref name="targetType"/>
/// is <see cref="Color"/> then converts the solid color brush to a color.
/// </returns>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return ConvertBack(value, targetType);
}
@ -55,7 +55,7 @@ namespace Avalonia.Markup.Xaml.Converters
/// If <paramref name="value"/> is a <see cref="Color"/> and <paramref name="targetType"/>
/// is <see cref="IBrush"/> then converts the color to a solid color brush.
/// </returns>
public static object Convert(object value, Type targetType)
public static object? Convert(object? value, Type? targetType)
{
if (targetType == typeof(IBrush) && value is Color c)
{
@ -75,7 +75,7 @@ namespace Avalonia.Markup.Xaml.Converters
/// If <paramref name="value"/> is an <see cref="ISolidColorBrush"/> and <paramref name="targetType"/>
/// is <see cref="Color"/> then converts the solid color brush to a color.
/// </returns>
public static object ConvertBack(object value, Type targetType)
public static object? ConvertBack(object? value, Type? targetType)
{
if (targetType == typeof(Color) && value is ISolidColorBrush brush)
{

10
src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs

@ -1,24 +1,24 @@
using System;
using System.ComponentModel;
using System.Globalization;
using Avalonia.Media;
namespace Avalonia.Markup.Xaml.Converters
{
public class FontFamilyTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var s = (string)value;
return FontFamily.Parse(s, context.GetContextBaseUri());
return FontFamily.Parse(s, context?.GetContextBaseUri());
}
}
}

13
src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs

@ -2,20 +2,21 @@
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using System;
using System.ComponentModel;
using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters
{
using System.ComponentModel;
public class IconTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var path = value as string;
if (path != null)
@ -32,7 +33,7 @@ namespace Avalonia.Markup.Xaml.Converters
throw new NotSupportedException();
}
private static WindowIcon CreateIconFromPath(ITypeDescriptorContext context, string s)
private static WindowIcon CreateIconFromPath(ITypeDescriptorContext? context, string s)
{
var uri = s.StartsWith("/")
? new Uri(s, UriKind.Relative)
@ -41,7 +42,7 @@ namespace Avalonia.Markup.Xaml.Converters
if(uri.IsAbsoluteUri && uri.IsFile)
return new WindowIcon(uri.LocalPath);
var assets = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
return new WindowIcon(assets.Open(uri, context.GetContextBaseUri()));
return new WindowIcon(assets.Open(uri, context?.GetContextBaseUri()));
}
}
}

9
src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs

@ -1,20 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using Avalonia.Utilities;
namespace Avalonia.Markup.Xaml.Converters
{
using System.ComponentModel;
using Avalonia.Utilities;
public class PointsListTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var points = new List<Point>();

13
src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs

@ -1,18 +1,13 @@
using System;
using System.ComponentModel;
using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters
{
using System.ComponentModel;
public class TimeSpanTypeConverter : System.ComponentModel.TimeSpanConverter
public class TimeSpanTypeConverter : TimeSpanConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
/// <inheritdoc />
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var valueStr = (string)value;
if (!valueStr.Contains(':'))

33
src/Markup/Avalonia.Markup.Xaml/Extensions.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Controls;
@ -11,38 +10,38 @@ namespace Avalonia.Markup.Xaml
{
internal static class Extensions
{
public static T GetService<T>(this IServiceProvider sp) => (T)sp?.GetService(typeof(T));
public static Uri GetContextBaseUri(this IServiceProvider ctx) => ctx.GetService<IUriContext>().BaseUri;
public static T? GetService<T>(this IServiceProvider sp) => (T?)sp.GetService(typeof(T));
public static T GetRequiredService<T>(this IServiceProvider sp)
=> sp.GetService<T>() ?? throw new InvalidOperationException($"Service {typeof(T)} hasn't been registered");
public static T GetFirstParent<T>(this IServiceProvider ctx) where T : class
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>().FirstOrDefault();
public static Uri? GetContextBaseUri(this IServiceProvider ctx) => ctx.GetService<IUriContext>()?.BaseUri;
public static T GetLastParent<T>(this IServiceProvider ctx) where T : class
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>().LastOrDefault();
public static T? GetFirstParent<T>(this IServiceProvider ctx) where T : class
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents.OfType<T>().FirstOrDefault();
public static T? GetLastParent<T>(this IServiceProvider ctx) where T : class
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents.OfType<T>().LastOrDefault();
public static IEnumerable<T> GetParents<T>(this IServiceProvider sp)
{
return sp.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>();
}
=> sp.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents.OfType<T>() ?? Enumerable.Empty<T>();
public static bool IsInControlTemplate(this IServiceProvider sp) => sp.GetService<IAvaloniaXamlIlControlTemplateProvider>() != null;
[RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)]
public static Type ResolveType(this IServiceProvider ctx, string namespacePrefix, string type)
public static Type ResolveType(this IServiceProvider ctx, string? namespacePrefix, string type)
{
var tr = ctx.GetService<IXamlTypeResolver>();
var tr = ctx.GetRequiredService<IXamlTypeResolver>();
string name = string.IsNullOrEmpty(namespacePrefix) ? type : $"{namespacePrefix}:{type}";
return tr?.Resolve(name);
return tr.Resolve(name);
}
public static object GetDefaultAnchor(this IServiceProvider provider)
public static object? GetDefaultAnchor(this IServiceProvider provider)
{
// If the target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest Control in
// the context.
object anchor = provider.GetFirstParent<Control>();
object? anchor = provider.GetFirstParent<Control>();
if (anchor is null)
{

12
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs

@ -35,7 +35,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
};
}
private protected override ExpressionObserver CreateExpressionObserver(AvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation)
private protected override ExpressionObserver CreateExpressionObserver(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor, bool enableDataValidation)
{
if (Source != null)
{
@ -61,8 +61,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
}
else
{
var styledElement = target as StyledElement
?? anchor as StyledElement
?? throw new ArgumentException($"Cannot find a valid {nameof(StyledElement)} to use as the binding source.");
return CreateSourceObserver(
(target as StyledElement) ?? (anchor as StyledElement),
styledElement,
Path.BuildExpression(enableDataValidation));
}
}
@ -70,8 +74,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
[ConstructorArgument("path")]
public CompiledBindingPath Path { get; set; }
public object Source { get; set; }
public object? Source { get; set; }
public Type DataType { get; set; }
public Type? DataType { get; set; }
}
}

10
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ArrayElementPlugin.cs

@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
class ArrayElementPlugin : IPropertyAccessorPlugin
internal class ArrayElementPlugin : IPropertyAccessorPlugin
{
private readonly int[] _indices;
private readonly Type _elementType;
@ -25,7 +23,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
public IPropertyAccessor? Start(WeakReference<object?> reference, string propertyName)
{
if (reference.TryGetTarget(out var target) && target is Array arr)
{
@ -48,9 +46,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public override Type PropertyType { get; }
public override object Value => _reference.TryGetTarget(out var arr) ? arr.GetValue(_indices) : null;
public override object? Value => _reference.TryGetTarget(out var arr) ? arr.GetValue(_indices) : null;
public override bool SetValue(object value, BindingPriority priority)
public override bool SetValue(object? value, BindingPriority priority)
{
if (_reference.TryGetTarget(out var arr))
{

36
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs

@ -2,8 +2,6 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
@ -13,11 +11,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
internal class CommandAccessorPlugin : IPropertyAccessorPlugin
{
private readonly Action<object, object> _execute;
private readonly Func<object, object, bool> _canExecute;
private readonly Action<object, object?> _execute;
private readonly Func<object, object?, bool>? _canExecute;
private readonly ISet<string> _dependsOnProperties;
public CommandAccessorPlugin(Action<object, object> execute, Func<object, object, bool> canExecute, ISet<string> dependsOnProperties)
public CommandAccessorPlugin(Action<object, object?> execute, Func<object, object?, bool>? canExecute, ISet<string> dependsOnProperties)
{
_execute = execute;
_canExecute = canExecute;
@ -31,18 +29,18 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
public IPropertyAccessor Start(WeakReference<object?> reference, string propertyName)
{
return new CommandAccessor(reference, _execute, _canExecute, _dependsOnProperties);
}
private sealed class CommandAccessor : PropertyAccessorBase
{
private readonly WeakReference<object> _reference;
private Command _command;
private readonly WeakReference<object?> _reference;
private readonly Command _command;
private readonly ISet<string> _dependsOnProperties;
public CommandAccessor(WeakReference<object> reference, Action<object, object> execute, Func<object, object, bool> canExecute, ISet<string> dependsOnProperties)
public CommandAccessor(WeakReference<object?> reference, Action<object, object?> execute, Func<object, object?, bool>? canExecute, ISet<string> dependsOnProperties)
{
_reference = reference ?? throw new ArgumentNullException(nameof(reference));
_dependsOnProperties = dependsOnProperties;
@ -50,7 +48,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
public override object Value => _reference.TryGetTarget(out var _) ? _command : null;
public override object? Value => _reference.TryGetTarget(out _) ? _command : null;
private void RaiseCanExecuteChanged()
{
@ -59,13 +57,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
private sealed class Command : ICommand
{
private readonly WeakReference<object> _target;
private readonly Action<object, object> _execute;
private readonly Func<object, object, bool> _canExecute;
private readonly WeakReference<object?> _target;
private readonly Action<object, object?> _execute;
private readonly Func<object, object?, bool>? _canExecute;
public event EventHandler CanExecuteChanged;
public event EventHandler? CanExecuteChanged;
public Command(WeakReference<object> target, Action<object, object> execute, Func<object, object, bool> canExecute)
public Command(WeakReference<object?> target, Action<object, object?> execute, Func<object, object?, bool>? canExecute)
{
_target = target;
_execute = execute;
@ -78,7 +76,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
, Threading.DispatcherPriority.Input);
}
public bool CanExecute(object parameter)
public bool CanExecute(object? parameter)
{
if (_target.TryGetTarget(out var target))
{
@ -91,7 +89,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return false;
}
public void Execute(object parameter)
public void Execute(object? parameter)
{
if (_target.TryGetTarget(out var target))
{
@ -102,12 +100,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public override Type PropertyType => typeof(ICommand);
public override bool SetValue(object value, BindingPriority priority)
public override bool SetValue(object? value, BindingPriority priority)
{
return false;
}
void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
private void OnNotifyPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (string.IsNullOrEmpty(e.PropertyName) || _dependsOnProperties.Contains(e.PropertyName))
{

54
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs

@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public CompiledBindingPath()
=> _elements = Array.Empty<ICompiledBindingPathElement>();
internal CompiledBindingPath(ICompiledBindingPathElement[] elements, object rawSource)
internal CompiledBindingPath(ICompiledBindingPathElement[] elements, object? rawSource)
{
_elements = elements;
RawSource = rawSource;
@ -26,14 +26,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.CompiledBindingSafeSupressWarningMessage)]
internal ExpressionNode BuildExpression(bool enableValidation)
{
ExpressionNode pathRoot = null;
ExpressionNode path = null;
ExpressionNode? pathRoot = null;
ExpressionNode? path = null;
foreach (var element in _elements)
{
ExpressionNode node = null;
ExpressionNode? node;
switch (element)
{
case NotExpressionPathElement _:
case NotExpressionPathElement:
node = new LogicalNotNode();
break;
case PropertyElement prop:
@ -54,7 +54,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
case AncestorPathElement ancestor:
node = new FindAncestorNode(ancestor.AncestorType, ancestor.Level);
break;
case SelfPathElement _:
case SelfPathElement:
node = new SelfNode();
break;
case ElementNameElement name:
@ -70,7 +70,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
throw new InvalidOperationException($"Unknown binding path element type {element.GetType().FullName}");
}
path = pathRoot is null ? (pathRoot = node) : path.Next = node;
path = pathRoot is null ? (pathRoot = node) : path!.Next = node;
}
return pathRoot ?? new EmptyExpressionNode();
@ -81,16 +81,17 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal SourceMode SourceMode => Array.Exists(_elements, e => e is IControlSourceBindingPathElement)
? SourceMode.Control : SourceMode.Data;
internal object RawSource { get; }
internal object? RawSource { get; }
/// <inheritdoc />
public override string ToString()
=> string.Concat((IEnumerable<ICompiledBindingPathElement>) _elements);
}
public class CompiledBindingPathBuilder
{
private object _rawSource;
private List<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>();
private object? _rawSource;
private readonly List<ICompiledBindingPathElement> _elements = new();
public CompiledBindingPathBuilder Not()
{
@ -98,7 +99,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this;
}
public CompiledBindingPathBuilder Property(IPropertyInfo info, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
public CompiledBindingPathBuilder Property(IPropertyInfo info, Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> accessorFactory)
{
_elements.Add(new PropertyElement(info, accessorFactory, _elements.Count == 0));
return this;
@ -110,7 +111,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this;
}
public CompiledBindingPathBuilder Command(string methodName, Action<object, object> executeHelper, Func<object, object, bool> canExecuteHelper, string[] dependsOnProperties)
public CompiledBindingPathBuilder Command(string methodName, Action<object, object?> executeHelper, Func<object, object?, bool>? canExecuteHelper, string[]? dependsOnProperties)
{
_elements.Add(new MethodAsCommandElement(methodName, executeHelper, canExecuteHelper, dependsOnProperties ?? Array.Empty<string>()));
return this;
@ -163,7 +164,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this;
}
public CompiledBindingPathBuilder SetRawSource(object rawSource)
public CompiledBindingPathBuilder SetRawSource(object? rawSource)
{
_rawSource = rawSource;
return this;
@ -187,7 +188,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
private readonly bool _isFirstElement;
public PropertyElement(IPropertyInfo property, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory, bool isFirstElement)
public PropertyElement(IPropertyInfo property, Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> accessorFactory, bool isFirstElement)
{
Property = property;
AccessorFactory = accessorFactory;
@ -196,7 +197,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public IPropertyInfo Property { get; }
public Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
public Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
public override string ToString()
=> _isFirstElement ? Property.Name : $".{Property.Name}";
@ -206,7 +207,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
public MethodAsDelegateElement(RuntimeMethodHandle method, RuntimeTypeHandle delegateType)
{
Method = (MethodInfo)MethodBase.GetMethodFromHandle(method);
Method = MethodBase.GetMethodFromHandle(method) as MethodInfo
?? throw new ArgumentException("Invalid method handle", nameof(method));
DelegateType = Type.GetTypeFromHandle(delegateType);
}
@ -217,7 +219,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class MethodAsCommandElement : ICompiledBindingPathElement
{
public MethodAsCommandElement(string methodName, Action<object, object> executeHelper, Func<object, object, bool> canExecuteHelper, string[] dependsOnElements)
public MethodAsCommandElement(string methodName, Action<object, object?> executeHelper, Func<object, object?, bool>? canExecuteHelper, string[] dependsOnElements)
{
MethodName = methodName;
ExecuteMethod = executeHelper;
@ -226,8 +228,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
public string MethodName { get; }
public Action<object, object> ExecuteMethod { get; }
public Func<object, object, bool> CanExecuteMethod { get; }
public Action<object, object?> ExecuteMethod { get; }
public Func<object, object?, bool>? CanExecuteMethod { get; }
public HashSet<string> DependsOnProperties { get; }
}
@ -240,7 +242,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
Type Type { get; }
Func<object, object> Cast { get; }
Func<object?, object?> Cast { get; }
}
internal class TaskStreamPathElement<T> : IStronglyTypedStreamElement
@ -267,13 +269,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class AncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
{
public AncestorPathElement(Type ancestorType, int level)
public AncestorPathElement(Type? ancestorType, int level)
{
AncestorType = ancestorType;
Level = level;
}
public Type AncestorType { get; }
public Type? AncestorType { get; }
public int Level { get; }
public override string ToString()
@ -282,13 +284,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class VisualAncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
{
public VisualAncestorPathElement(Type ancestorType, int level)
public VisualAncestorPathElement(Type? ancestorType, int level)
{
AncestorType = ancestorType;
Level = level;
}
public Type AncestorType { get; }
public Type? AncestorType { get; }
public int Level { get; }
}
@ -323,7 +325,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class TypeCastPathElement<T> : ITypeCastElement
{
private static object TryCast(object obj)
private static object? TryCast(object? obj)
{
if (obj is T result)
return result;
@ -332,7 +334,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public Type Type => typeof(T);
public Func<object, object> Cast => TryCast;
public Func<object?, object?> Cast => TryCast;
public override string ToString()
=> $"({Type.FullName})";

12
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs

@ -5,13 +5,13 @@ using Avalonia.Reactive;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
class FindVisualAncestorNode : ExpressionNode
internal class FindVisualAncestorNode : ExpressionNode
{
private readonly int _level;
private readonly Type _ancestorType;
private IDisposable _subscription;
private readonly Type? _ancestorType;
private IDisposable? _subscription;
public FindVisualAncestorNode(Type ancestorType, int level)
public FindVisualAncestorNode(Type? ancestorType, int level)
{
_level = level;
_ancestorType = ancestorType;
@ -32,9 +32,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
}
protected override void StartListeningCore(WeakReference<object> reference)
protected override void StartListeningCore(WeakReference<object?> reference)
{
if (reference.TryGetTarget(out object target) && target is Visual visual)
if (reference.TryGetTarget(out object? target) && target is Visual visual)
{
_subscription = VisualLocator.Track(visual, _level, _ancestorType).Subscribe(ValueChanged);
}

6
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs

@ -1,19 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
internal class MethodAccessorPlugin : IPropertyAccessorPlugin
{
private MethodInfo _method;
private readonly MethodInfo _method;
private readonly Type _delegateType;
public MethodAccessorPlugin(MethodInfo method, Type delegateType)

14
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs

@ -1,33 +1,31 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Avalonia.Data.Core.Plugins;
using Avalonia.Reactive;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
class ObservableStreamPlugin<T> : IStreamPlugin
internal class ObservableStreamPlugin<T> : IStreamPlugin
{
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
public bool Match(WeakReference<object> reference)
public bool Match(WeakReference<object?> reference)
{
return reference.TryGetTarget(out var target) && target is IObservable<T>;
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
public IObservable<object> Start(WeakReference<object> reference)
public IObservable<object?> Start(WeakReference<object?> reference)
{
if (!(reference.TryGetTarget(out var target) && target is IObservable<T> obs))
{
return Observable.Empty<object>();
return Observable.Empty<object?>();
}
else if (target is IObservable<object> obj)
else if (target is IObservable<object?> obj)
{
return obj;
}
return obs.Select(x => (object)x);
return obs.Select(x => (object?)x);
}
}
}

52
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs

@ -11,29 +11,29 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
public static class PropertyInfoAccessorFactory
{
public static IPropertyAccessor CreateInpcPropertyAccessor(WeakReference<object> target, IPropertyInfo property)
public static IPropertyAccessor CreateInpcPropertyAccessor(WeakReference<object?> target, IPropertyInfo property)
=> new InpcPropertyAccessor(target, property);
public static IPropertyAccessor CreateAvaloniaPropertyAccessor(WeakReference<object> target, IPropertyInfo property)
=> new AvaloniaPropertyAccessor(new WeakReference<AvaloniaObject>((AvaloniaObject)(target.TryGetTarget(out var o) ? o : null)), (AvaloniaProperty)property);
public static IPropertyAccessor CreateAvaloniaPropertyAccessor(WeakReference<object?> target, IPropertyInfo property)
=> new AvaloniaPropertyAccessor(new WeakReference<AvaloniaObject?>((AvaloniaObject?)(target.TryGetTarget(out var o) ? o : null)), (AvaloniaProperty)property);
public static IPropertyAccessor CreateIndexerPropertyAccessor(WeakReference<object> target, IPropertyInfo property, int argument)
public static IPropertyAccessor CreateIndexerPropertyAccessor(WeakReference<object?> target, IPropertyInfo property, int argument)
=> new IndexerAccessor(target, property, argument);
}
internal class AvaloniaPropertyAccessor : PropertyAccessorBase
{
private readonly WeakReference<AvaloniaObject> _reference;
private readonly WeakReference<AvaloniaObject?> _reference;
private readonly AvaloniaProperty _property;
private IDisposable _subscription;
private IDisposable? _subscription;
public AvaloniaPropertyAccessor(WeakReference<AvaloniaObject> reference, AvaloniaProperty property)
public AvaloniaPropertyAccessor(WeakReference<AvaloniaObject?> reference, AvaloniaProperty property)
{
_reference = reference ?? throw new ArgumentNullException(nameof(reference));;
_property = property ?? throw new ArgumentNullException(nameof(property));;
_reference = reference ?? throw new ArgumentNullException(nameof(reference));
_property = property ?? throw new ArgumentNullException(nameof(property));
}
public AvaloniaObject Instance
public AvaloniaObject? Instance
{
get
{
@ -43,13 +43,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
public override Type PropertyType => _property.PropertyType;
public override object Value => Instance?.GetValue(_property);
public override object? Value => Instance?.GetValue(_property);
public override bool SetValue(object value, BindingPriority priority)
public override bool SetValue(object? value, BindingPriority priority)
{
if (!_property.IsReadOnly)
if (!_property.IsReadOnly && Instance is { } instance)
{
Instance.SetValue(_property, value, priority);
instance.SetValue(_property, value, priority);
return true;
}
@ -70,10 +70,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class InpcPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber<PropertyChangedEventArgs>
{
protected readonly WeakReference<object> _reference;
protected readonly WeakReference<object?> _reference;
private readonly IPropertyInfo _property;
public InpcPropertyAccessor(WeakReference<object> reference, IPropertyInfo property)
public InpcPropertyAccessor(WeakReference<object?> reference, IPropertyInfo property)
{
_reference = reference ?? throw new ArgumentNullException(nameof(reference));
_property = property ?? throw new ArgumentNullException(nameof(property));
@ -81,7 +81,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public override Type PropertyType => _property.PropertyType;
public override object Value
public override object? Value
{
get
{
@ -89,7 +89,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
}
public override bool SetValue(object value, BindingPriority priority)
public override bool SetValue(object? value, BindingPriority priority)
{
if (_property.CanSet && _reference.TryGetTarget(out var o))
{
@ -103,7 +103,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return false;
}
public void OnEvent(object sender, WeakEvent ev, PropertyChangedEventArgs e)
public void OnEvent(object? sender, WeakEvent ev, PropertyChangedEventArgs e)
{
if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
{
@ -144,9 +144,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class IndexerAccessor : InpcPropertyAccessor, IWeakEventSubscriber<NotifyCollectionChangedEventArgs>
{
private int _index;
private readonly int _index;
public IndexerAccessor(WeakReference<object> target, IPropertyInfo basePropertyInfo, int argument)
public IndexerAccessor(WeakReference<object?> target, IPropertyInfo basePropertyInfo, int argument)
:base(target, basePropertyInfo)
{
_index = argument;
@ -167,7 +167,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
WeakEvents.CollectionChanged.Unsubscribe(incc, this);
}
public void OnEvent(object sender, WeakEvent ev, NotifyCollectionChangedEventArgs args)
public void OnEvent(object? sender, WeakEvent ev, NotifyCollectionChangedEventArgs args)
{
if (ShouldNotifyListeners(args))
{
@ -175,7 +175,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
}
bool ShouldNotifyListeners(NotifyCollectionChangedEventArgs e)
private bool ShouldNotifyListeners(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
@ -185,12 +185,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return _index >= e.OldStartingIndex;
case NotifyCollectionChangedAction.Replace:
return _index >= e.NewStartingIndex &&
_index < e.NewStartingIndex + e.NewItems.Count;
_index < e.NewStartingIndex + e.NewItems!.Count;
case NotifyCollectionChangedAction.Move:
return (_index >= e.NewStartingIndex &&
_index < e.NewStartingIndex + e.NewItems.Count) ||
_index < e.NewStartingIndex + e.NewItems!.Count) ||
(_index >= e.OldStartingIndex &&
_index < e.OldStartingIndex + e.OldItems.Count);
_index < e.OldStartingIndex + e.OldItems!.Count);
case NotifyCollectionChangedAction.Reset:
return true;
}

11
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs

@ -1,20 +1,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
class PropertyInfoAccessorPlugin : IPropertyAccessorPlugin
internal class PropertyInfoAccessorPlugin : IPropertyAccessorPlugin
{
private readonly IPropertyInfo _propertyInfo;
private readonly Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> _accessorFactory;
private readonly Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> _accessorFactory;
public PropertyInfoAccessorPlugin(IPropertyInfo propertyInfo, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
public PropertyInfoAccessorPlugin(IPropertyInfo propertyInfo, Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> accessorFactory)
{
_propertyInfo = propertyInfo;
_accessorFactory = accessorFactory;
@ -27,7 +24,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
public IPropertyAccessor Start(WeakReference<object?> reference, string propertyName)
{
Debug.Assert(_propertyInfo.Name == propertyName);
return _accessorFactory(reference, _propertyInfo);

6
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs

@ -5,14 +5,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
internal class StrongTypeCastNode : TypeCastNode
{
private Func<object, object> _cast;
private readonly Func<object?, object?> _cast;
public StrongTypeCastNode(Type type, Func<object, object> cast) : base(type)
public StrongTypeCastNode(Type type, Func<object?, object?> cast) : base(type)
{
_cast = cast;
}
protected override object Cast(object value)
protected override object? Cast(object? value)
=> _cast(value);
}
}

18
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs

@ -7,20 +7,20 @@ using Avalonia.Reactive;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
class TaskStreamPlugin<T> : IStreamPlugin
internal class TaskStreamPlugin<T> : IStreamPlugin
{
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
public bool Match(WeakReference<object> reference)
public bool Match(WeakReference<object?> reference)
{
return reference.TryGetTarget(out var target) && target is Task<T>;
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
public IObservable<object> Start(WeakReference<object> reference)
public IObservable<object?> Start(WeakReference<object?> reference)
{
if(!(reference.TryGetTarget(out var target) && target is Task<T> task))
{
return Observable.Empty<object>();
return Observable.Empty<object?>();
}
switch (task.Status)
@ -29,9 +29,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
case TaskStatus.Faulted:
return HandleCompleted(task);
default:
var subject = new LightweightSubject<object>();
var subject = new LightweightSubject<object?>();
task.ContinueWith(
x => HandleCompleted(task).Subscribe(subject),
_ => HandleCompleted(task).Subscribe(subject),
TaskScheduler.FromCurrentSynchronizationContext())
.ConfigureAwait(false);
return subject;
@ -39,14 +39,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
private static IObservable<object> HandleCompleted(Task<T> task)
private static IObservable<object?> HandleCompleted(Task<T> task)
{
switch (task.Status)
{
case TaskStatus.RanToCompletion:
return Observable.Return((object)task.Result);
return Observable.Return((object?)task.Result);
case TaskStatus.Faulted:
return Observable.Return(new BindingNotification(task.Exception, BindingErrorType.Error));
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error));
default:
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task.");
}

4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -4,8 +4,6 @@ using Avalonia.Data;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Media;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class DynamicResourceExtension : IBinding
@ -31,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
if (!(provideTarget.TargetObject is StyledElement))
if (provideTarget?.TargetObject is not StyledElement)
{
_anchor = serviceProvider.GetFirstParent<StyledElement>() ??
serviceProvider.GetFirstParent<IResourceProvider>() ??

3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/On.cs

@ -1,5 +1,4 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Generic;
using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.MarkupExtensions;

6
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs

@ -1,6 +1,4 @@
#nullable enable
using System;
using Avalonia.Metadata;
using Avalonia.Platform;
@ -20,7 +18,7 @@ public sealed class OnFormFactorExtension : OnFormFactorExtensionBase<object, On
public static bool ShouldProvideOption(IServiceProvider serviceProvider, FormFactorType option)
{
return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().FormFactor == option;
return serviceProvider.GetService<IRuntimePlatform>()?.GetRuntimeInfo().FormFactor == option;
}
}
@ -38,7 +36,7 @@ public sealed class OnFormFactorExtension<TReturn> : OnFormFactorExtensionBase<T
public static bool ShouldProvideOption(IServiceProvider serviceProvider, FormFactorType option)
{
return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().FormFactor == option;
return serviceProvider.GetService<IRuntimePlatform>()?.GetRuntimeInfo().FormFactor == option;
}
}

4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs

@ -1,6 +1,4 @@
#nullable enable
using System;
using Avalonia.Compatibility;
using Avalonia.Compatibility;
using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.MarkupExtensions;

27
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs

@ -2,7 +2,6 @@ using Avalonia.Data;
using System;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Markup.Xaml.MarkupExtensions
@ -21,11 +20,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public Binding ProvideValue(IServiceProvider serviceProvider)
{
var descriptorContext = (ITypeDescriptorContext)serviceProvider;
return new Binding
{
TypeResolver = descriptorContext.ResolveType,
TypeResolver = serviceProvider.ResolveType,
Converter = Converter,
ConverterParameter = ConverterParameter,
ElementName = ElementName,
@ -36,33 +33,33 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Source = Source,
StringFormat = StringFormat,
RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(descriptorContext.GetDefaultAnchor()),
DefaultAnchor = new WeakReference(serviceProvider.GetDefaultAnchor()),
TargetNullValue = TargetNullValue,
NameScope = new WeakReference<INameScope>(serviceProvider.GetService<INameScope>())
NameScope = new WeakReference<INameScope?>(serviceProvider.GetService<INameScope>())
};
}
public IValueConverter Converter { get; set; }
public IValueConverter? Converter { get; set; }
public object ConverterParameter { get; set; }
public object? ConverterParameter { get; set; }
public string ElementName { get; set; }
public string? ElementName { get; set; }
public object FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
public BindingMode Mode { get; set; }
[ConstructorArgument("path")]
public string Path { get; set; }
public string Path { get; set; } = "";
public BindingPriority Priority { get; set; } = BindingPriority.LocalValue;
public object Source { get; set; }
public object? Source { get; set; }
public string StringFormat { get; set; }
public string? StringFormat { get; set; }
public RelativeSource RelativeSource { get; set; }
public RelativeSource? RelativeSource { get; set; }
public object TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue;
public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue;
}
}

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs

@ -28,7 +28,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
[ConstructorArgument("mode")]
public RelativeSourceMode Mode { get; set; } = RelativeSourceMode.FindAncestor;
public Type AncestorType { get; set; }
public Type? AncestorType { get; set; }
public TreeType Tree { get; set; }

11
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs

@ -2,8 +2,6 @@
using Avalonia.Controls;
using Avalonia.Data.Core;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class ResolveByNameExtension
@ -18,6 +16,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public object? ProvideValue(IServiceProvider serviceProvider)
{
var nameScope = serviceProvider.GetService<INameScope>();
if (nameScope is null)
return null;
var value = nameScope.FindAsync(Name);
@ -25,10 +26,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
return value.GetResult();
var provideValueTarget = serviceProvider.GetService<IProvideValueTarget>();
var target = provideValueTarget.TargetObject;
if (provideValueTarget.TargetProperty is IPropertyInfo property)
if (provideValueTarget?.TargetProperty is IPropertyInfo property)
{
var target = provideValueTarget.TargetObject;
value.OnCompleted(() => property.Set(target, value.GetResult()));
}
return AvaloniaProperty.UnsetValue;
}

34
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Markup.Data;
@ -8,8 +7,6 @@ using Avalonia.Markup.Xaml.Converters;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Styling;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class StaticResourceExtension
@ -25,7 +22,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public object? ResourceKey { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
public object? ProvideValue(IServiceProvider serviceProvider)
{
if (ResourceKey is not { } resourceKey)
{
@ -34,32 +31,37 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var stack = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>();
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
var themeVariant = (provideTarget.TargetObject as IThemeVariantHost)?.ActualThemeVariant;
var targetObject = provideTarget?.TargetObject;
var targetProperty = provideTarget?.TargetProperty;
var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant;
var targetType = provideTarget.TargetProperty switch
var targetType = targetProperty switch
{
AvaloniaProperty ap => ap.PropertyType,
PropertyInfo pi => pi.PropertyType,
_ => null,
_ => null
};
if (provideTarget.TargetObject is Setter { Property: not null } setter)
if (targetObject is Setter { Property: { } setterProperty })
{
targetType = setter.Property?.PropertyType;
targetType = setterProperty.PropertyType;
}
// Look upwards though the ambient context for IResourceNodes
// which might be able to give us the resource.
foreach (var parent in stack.Parents)
if (stack is not null)
{
if (parent is IResourceNode node && node.TryGetResource(resourceKey, themeVariant, out var value))
foreach (var parent in stack.Parents)
{
return ColorToBrushConverter.Convert(value, targetType);
if (parent is IResourceNode node && node.TryGetResource(resourceKey, themeVariant, out var value))
{
return ColorToBrushConverter.Convert(value, targetType);
}
}
}
if (provideTarget.TargetObject is Control target &&
provideTarget.TargetProperty is PropertyInfo property)
if (targetObject is Control target &&
targetProperty is PropertyInfo property)
{
// This is stored locally to avoid allocating closure in the outer scope.
var localTargetType = targetType;
@ -72,7 +74,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
throw new KeyNotFoundException($"Static resource '{resourceKey}' not found.");
}
private object GetValue(StyledElement control, Type? targetType)
private object? GetValue(StyledElement control, Type? targetType)
{
return ColorToBrushConverter.Convert(control.FindResource(ResourceKey!), targetType);
}

8
src/Markup/Avalonia.Markup.Xaml/Parsers/PropertyParser.cs

@ -5,7 +5,7 @@ namespace Avalonia.Markup.Xaml.Parsers
{
internal class PropertyParser
{
public static (string ns, string owner, string name) Parse(CharacterReader r)
public static (string? ns, string? owner, string name) Parse(CharacterReader r)
{
if (r.End)
{
@ -14,9 +14,9 @@ namespace Avalonia.Markup.Xaml.Parsers
var openParens = r.TakeIf('(');
bool closeParens = false;
string ns = null;
string owner = null;
string name = null;
string? ns = null;
string? owner = null;
string? name = null;
do
{

2
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs

@ -2,8 +2,6 @@ using System.Reflection;
namespace Avalonia.Markup.Xaml;
#nullable enable
public class RuntimeXamlLoaderConfiguration
{
/// <summary>

3
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs

@ -1,5 +1,4 @@
#nullable enable
using System;
using System;
using System.IO;
using System.Text;

3
src/Markup/Avalonia.Markup.Xaml/Styling/MergeResourceInclude.cs

@ -1,8 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Markup.Xaml.Styling;

2
src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs

@ -3,8 +3,6 @@ using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Styling;
#nullable enable
namespace Avalonia.Markup.Xaml.Styling
{
/// <summary>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save