Browse Source

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

pull/10826/head
affederaffe 4 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 Grid.Column="2"
Grid.Row="0" Grid.Row="0"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> 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" <ColorSpectrum x:Name="ColorSpectrum1"
Grid.Row="0" Grid.Row="0"
Color="Red" Color="Red"

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

@ -1,7 +1,6 @@
using System; using System;
using Android.Content; using Android.Content;
using Android.Runtime; using Android.Runtime;
using Android.Text;
using Android.Views; using Android.Views;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Android.Platform.SkiaPlatform;
@ -99,6 +98,9 @@ namespace Avalonia.Android
{ {
_host.InitEditorInfo((topLevel, outAttrs) => _host.InitEditorInfo((topLevel, outAttrs) =>
{ {
if (_client == null)
return null;
_inputConnection = new AvaloniaInputConnection(topLevel, this); _inputConnection = new AvaloniaInputConnection(topLevel, this);
outAttrs.InputType = options.ContentType switch 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)); AvaloniaProperty.Register<Layoutable, VerticalAlignment>(nameof(VerticalAlignment));
/// <summary> /// <summary>
/// Defines the <see cref="UseLayoutRoundingProperty"/> property. /// Defines the <see cref="UseLayoutRounding"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> UseLayoutRoundingProperty = public static readonly StyledProperty<bool> UseLayoutRoundingProperty =
AvaloniaProperty.Register<Layoutable, bool>(nameof(UseLayoutRounding), defaultValue: true, inherits: true); 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) if (input.Length == 3 || input.Length == 4)
{ {
var extendedLength = 2 * input.Length; var extendedLength = 2 * input.Length;
#if !BUILDTASK #if !BUILDTASK
Span<char> extended = stackalloc char[extendedLength]; Span<char> extended = stackalloc char[extendedLength];
#else #else

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

@ -131,7 +131,7 @@ namespace Avalonia.Media
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <list type="bullet"> /// <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> /// <item>1 is the full color.</item>
/// </list> /// </list>
/// </remarks> /// </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 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, public static void Transcode(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst,
PixelFormat format) PixelFormat format)
@ -242,6 +280,10 @@ static unsafe class PixelFormatReader
Transcode<Gray8PixelReader>(dst, src, size, strideSrc, strideDst); Transcode<Gray8PixelReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Gray16) else if (format == PixelFormats.Gray16)
Transcode<Gray16PixelReader>(dst, src, size, strideSrc, strideDst); 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) else if (format == PixelFormats.Gray32Float)
Transcode<Gray32FloatPixelReader>(dst, src, size, strideSrc, strideDst); Transcode<Gray32FloatPixelReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Rgba64) else if (format == PixelFormats.Rgba64)
@ -258,7 +300,9 @@ static unsafe class PixelFormatReader
|| format == PixelFormats.Gray8 || format == PixelFormats.Gray8
|| format == PixelFormats.Gray16 || format == PixelFormats.Gray16
|| format == PixelFormats.Gray32Float || 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 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; i = lastRunIndex;
//Possible overlap at runs of different direction
if (directionalWidth == 0) 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; int coveredLength;
TextBounds? textBounds = null; TextBounds? textBounds;
switch (currentDirection) switch (currentDirection)
{ {
@ -831,14 +836,18 @@ namespace Avalonia.Media.TextFormatting
i = firstRunIndex; i = firstRunIndex;
//Possible overlap at runs of different direction
if (directionalWidth == 0) 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; TextBounds? textBounds = null;
int coveredLength;
switch (currentDirection) switch (currentDirection)
{ {

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

@ -13,7 +13,9 @@ namespace Avalonia.Platform
Gray8, Gray8,
Gray16, Gray16,
Gray32Float, Gray32Float,
Rgba64 Rgba64,
Rgb24,
Bgr24
} }
public record struct PixelFormat public record struct PixelFormat
@ -35,6 +37,8 @@ namespace Avalonia.Platform
else if (FormatEnum == PixelFormatEnum.Rgb565 else if (FormatEnum == PixelFormatEnum.Rgb565
|| FormatEnum == PixelFormatEnum.Gray16) || FormatEnum == PixelFormatEnum.Gray16)
return 16; return 16;
else if (FormatEnum is PixelFormatEnum.Bgr24 or PixelFormatEnum.Rgb24)
return 24;
else if (FormatEnum == PixelFormatEnum.Rgba64) else if (FormatEnum == PixelFormatEnum.Rgba64)
return 64; return 64;
@ -70,5 +74,7 @@ namespace Avalonia.Platform
public static PixelFormat Gray8 { get; } = new PixelFormat(PixelFormatEnum.Gray8); public static PixelFormat Gray8 { get; } = new PixelFormat(PixelFormatEnum.Gray8);
public static PixelFormat Gray16 { get; } = new PixelFormat(PixelFormatEnum.Gray16); public static PixelFormat Gray16 { get; } = new PixelFormat(PixelFormatEnum.Gray16);
public static PixelFormat Gray32Float { get; } = new PixelFormat(PixelFormatEnum.Gray32Float); 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 abstract T CreateItem();
protected virtual void ClearItem(T item)
{
}
protected virtual void DestroyItem(T item) protected virtual void DestroyItem(T item)
{ {
@ -94,6 +99,7 @@ internal abstract class BatchStreamPoolBase<T> : IDisposable
public void Return(T item) public void Return(T item)
{ {
ClearItem(item);
lock (_pool) lock (_pool)
{ {
_usage--; _usage--;
@ -138,7 +144,7 @@ internal sealed class BatchStreamObjectPool<T> : BatchStreamPoolBase<T[]> where
return new T[ArraySize]; return new T[ArraySize];
} }
protected override void DestroyItem(T[] item) protected override void ClearItem(T[] item)
{ {
Array.Clear(item, 0, item.Length); Array.Clear(item, 0, item.Length);
} }

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

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

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

@ -1,6 +1,7 @@
using System; using System;
using System.Runtime.ConstrainedExecution; using System.Runtime.ConstrainedExecution;
using System.Threading; using System.Threading;
using Avalonia.Utilities;
namespace Avalonia.Threading namespace Avalonia.Threading
{ {
@ -9,6 +10,28 @@ namespace Avalonia.Threading
/// </summary> /// </summary>
public class AvaloniaSynchronizationContext : SynchronizationContext 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> /// <summary>
/// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer. /// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer.
/// </summary> /// </summary>
@ -24,13 +47,13 @@ namespace Avalonia.Threading
return; return;
} }
SetSynchronizationContext(new AvaloniaSynchronizationContext()); SetSynchronizationContext(Dispatcher.UIThread.GetContextWithPriority(DispatcherPriority.Normal));
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Post(SendOrPostCallback d, object? state) public override void Post(SendOrPostCallback d, object? state)
{ {
Dispatcher.UIThread.Post(d, state, DispatcherPriority.Background); Dispatcher.UIThread.Post(d, state, Priority);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -41,7 +64,50 @@ namespace Avalonia.Threading
else else
Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).GetAwaiter().GetResult(); 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.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Avalonia.Threading; namespace Avalonia.Threading;
@ -106,7 +107,8 @@ public partial class Dispatcher
// call the callback directly. // call the callback directly.
if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess()) if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{ {
callback(); using (AvaloniaSynchronizationContext.Ensure(priority))
callback();
return; return;
} }
@ -227,7 +229,8 @@ public partial class Dispatcher
// call the callback directly. // call the callback directly.
if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess()) if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{ {
return callback(); using (AvaloniaSynchronizationContext.Ensure(priority))
return callback();
} }
// Slow-Path: go through the queue. // Slow-Path: go through the queue.
@ -388,7 +391,7 @@ public partial class Dispatcher
return operation; return operation;
} }
private void InvokeAsyncImpl(DispatcherOperation operation, CancellationToken cancellationToken) internal void InvokeAsyncImpl(DispatcherOperation operation, CancellationToken cancellationToken)
{ {
bool succeeded = false; bool succeeded = false;
@ -482,7 +485,7 @@ public partial class Dispatcher
// invoke. // invoke.
try try
{ {
operation.GetTask().Wait(); operation.Wait();
Debug.Assert(operation.Status == DispatcherOperationStatus.Completed || Debug.Assert(operation.Status == DispatcherOperationStatus.Completed ||
operation.Status == DispatcherOperationStatus.Aborted); operation.Status == DispatcherOperationStatus.Aborted);
@ -539,6 +542,48 @@ public partial class Dispatcher
InvokeAsyncImpl(new DispatcherOperation(this, priority, action, true), CancellationToken.None); 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> /// <summary>
/// Posts an action that will be invoked on the dispatcher thread. /// Posts an action that will be invoked on the dispatcher thread.
/// </summary> /// </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;
using System.Diagnostics; using System.Diagnostics;
using System.Threading;
namespace Avalonia.Threading; namespace Avalonia.Threading;
@ -43,10 +44,19 @@ public partial class Dispatcher
/// </summary> /// </summary>
public void RunJobs(DispatcherPriority? priority = null) 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; priority ??= DispatcherPriority.MinimumActiveValue;
if (priority < DispatcherPriority.MinimumActiveValue) if (priority < DispatcherPriority.MinimumActiveValue)
priority = DispatcherPriority.MinimumActiveValue; priority = DispatcherPriority.MinimumActiveValue;
while (true) while (!cancellationToken.IsCancellationRequested)
{ {
DispatcherOperation? job; DispatcherOperation? job;
lock (InstanceLock) lock (InstanceLock)
@ -168,7 +178,7 @@ public partial class Dispatcher
} }
} }
private bool RequestProcessing() internal bool RequestProcessing()
{ {
lock (InstanceLock) lock (InstanceLock)
{ {

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

@ -18,11 +18,13 @@ public partial class Dispatcher : IDispatcher
{ {
private IDispatcherImpl _impl; private IDispatcherImpl _impl;
internal object InstanceLock { get; } = new(); internal object InstanceLock { get; } = new();
private bool _hasShutdownFinished;
private IControlledDispatcherImpl? _controlledImpl; private IControlledDispatcherImpl? _controlledImpl;
private static Dispatcher? s_uiThread; private static Dispatcher? s_uiThread;
private IDispatcherImplWithPendingInput? _pendingInputImpl; 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) internal Dispatcher(IDispatcherImpl impl)
{ {
@ -37,6 +39,7 @@ public partial class Dispatcher : IDispatcher
} }
public static Dispatcher UIThread => s_uiThread ??= CreateUIThreadDispatcher(); public static Dispatcher UIThread => s_uiThread ??= CreateUIThreadDispatcher();
public bool SupportsRunLoops => _controlledImpl != null;
private static Dispatcher CreateUIThreadDispatcher() private static Dispatcher CreateUIThreadDispatcher()
{ {
@ -72,50 +75,14 @@ public partial class Dispatcher : IDispatcher
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowVerifyAccess() static void ThrowVerifyAccess()
=> throw new InvalidOperationException("Call from invalid thread"); => throw new InvalidOperationException("Call from invalid thread");
ThrowVerifyAccess(); ThrowVerifyAccess();
} }
} }
internal void Shutdown() internal AvaloniaSynchronizationContext GetContextWithPriority(DispatcherPriority priority)
{
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)
{ {
if (_controlledImpl == null) DispatcherPriority.Validate(priority, nameof(priority));
throw new PlatformNotSupportedException(); var index = priority - DispatcherPriority.MinValue;
cancellationToken.Register(() => RequestProcessing()); return _priorityContexts[index] ??= new(priority);
_controlledImpl.RunLoop(cancellationToken);
} }
} }

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) lock (Dispatcher.InstanceLock)
{ {
if (Status == DispatcherOperationStatus.Pending) if (Status != DispatcherOperationStatus.Pending)
return; return false;
Dispatcher.Abort(this); 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()) if ((Status == DispatcherOperationStatus.Pending || Status == DispatcherOperationStatus.Executing) &&
throw new InvalidOperationException("Wait is only supported on background thread"); timeout.TotalMilliseconds != 0)
GetTask().Wait(); {
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(); public Task GetTask() => GetTaskCore();
@ -148,7 +258,8 @@ public class DispatcherOperation
try try
{ {
InvokeCore(); using (AvaloniaSynchronizationContext.Ensure(Priority))
InvokeCore();
} }
finally finally
{ {

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

@ -20,7 +20,9 @@ namespace Avalonia.Threading
/// <summary> /// <summary>
/// The lowest foreground dispatcher priority /// The lowest foreground dispatcher priority
/// </summary> /// </summary>
internal static readonly DispatcherPriority Default = new(0); public static readonly DispatcherPriority Default = new(0);
internal static readonly DispatcherPriority MinimumForegroundPriority = Default;
/// <summary> /// <summary>
/// The job will be processed with the same priority as input. /// 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.Runtime.ConstrainedExecution;
using System.Threading; using System.Threading;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32 namespace Avalonia.Threading
{ {
internal class NonPumpingSyncContext : SynchronizationContext, IDisposable internal class NonPumpingSyncContext : SynchronizationContext, IDisposable
{ {
private readonly NonPumpingLockHelper.IHelperImpl _impl;
private readonly SynchronizationContext? _inner; private readonly SynchronizationContext? _inner;
private NonPumpingSyncContext(SynchronizationContext? inner) public NonPumpingSyncContext(NonPumpingLockHelper.IHelperImpl impl, SynchronizationContext? inner)
{ {
_impl = impl;
_inner = inner; _inner = inner;
SetWaitNotificationRequired(); SetWaitNotificationRequired();
SetSynchronizationContext(this); SetSynchronizationContext(this);
@ -48,15 +49,12 @@ namespace Avalonia.Win32
#if !NET6_0_OR_GREATER #if !NET6_0_OR_GREATER
[PrePrepareMethod] [PrePrepareMethod]
#endif #endif
public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) =>
{ _impl.Wait(waitHandles, waitAll, millisecondsTimeout);
return UnmanagedMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll,
millisecondsTimeout, false);
}
public void Dispose() => SetSynchronizationContext(_inner); public void Dispose() => SetSynchronizationContext(_inner);
public static IDisposable? Use() internal static IDisposable? Use(NonPumpingLockHelper.IHelperImpl impl)
{ {
var current = Current; var current = Current;
if (current == null) if (current == null)
@ -67,12 +65,8 @@ namespace Avalonia.Win32
if (current is NonPumpingSyncContext) if (current is NonPumpingSyncContext)
return null; return null;
return new NonPumpingSyncContext(current); return new NonPumpingSyncContext(impl, current);
}
internal class HelperImpl : NonPumpingLockHelper.IHelperImpl
{
IDisposable? NonPumpingLockHelper.IHelperImpl.Use() => NonPumpingSyncContext.Use();
} }
} }
} }

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

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Threading;
namespace Avalonia.Utilities namespace Avalonia.Utilities
{ {
@ -6,9 +7,15 @@ namespace Avalonia.Utilities
{ {
public interface IHelperImpl 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)); AvaloniaProperty.Register<Visual, Geometry?>(nameof(Clip));
/// <summary> /// <summary>
/// Defines the <see cref="IsVisibleProperty"/> property. /// Defines the <see cref="IsVisible"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> IsVisibleProperty = public static readonly StyledProperty<bool> IsVisibleProperty =
AvaloniaProperty.Register<Visual, bool>(nameof(IsVisible), true); 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> /// <summary>
/// Presents a color for user editing using a spectrum, palette and component sliders within a drop down. /// 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) if (accentStep != 0)
{ {
// ColorChanged will be invoked in OnPropertyChanged if the value is different // 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. // independent pixels of controls.
var scale = LayoutHelper.GetLayoutScale(this); var scale = LayoutHelper.GetLayoutScale(this);
var pixelWidth = Convert.ToInt32(Bounds.Width * scale); int pixelWidth;
var pixelHeight = Convert.ToInt32(Bounds.Height * scale); 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) if (pixelWidth != 0 && pixelHeight != 0)
{ {
@ -373,7 +387,7 @@ namespace Avalonia.Controls.Primitives
ignorePropertyChanged = true; ignorePropertyChanged = true;
// Always keep the two color properties in sync // Always keep the two color properties in sync
HsvColor = Color.ToHsv(); SetCurrentValue(HsvColorProperty, Color.ToHsv());
SetColorToSliderValues(); SetColorToSliderValues();
UpdateBackground(); UpdateBackground();
@ -403,7 +417,7 @@ namespace Avalonia.Controls.Primitives
ignorePropertyChanged = true; ignorePropertyChanged = true;
// Always keep the two color properties in sync // Always keep the two color properties in sync
Color = HsvColor.ToRgb(); SetCurrentValue(ColorProperty, HsvColor.ToRgb());
SetColorToSliderValues(); SetColorToSliderValues();
UpdateBackground(); UpdateBackground();
@ -440,13 +454,13 @@ namespace Avalonia.Controls.Primitives
if (ColorModel == ColorModel.Hsva) if (ColorModel == ColorModel.Hsva)
{ {
HsvColor = hsvColor; SetCurrentValue(HsvColorProperty, hsvColor);
Color = hsvColor.ToRgb(); SetCurrentValue(ColorProperty, hsvColor.ToRgb());
} }
else else
{ {
Color = color; SetCurrentValue(ColorProperty, color);
HsvColor = color.ToHsv(); SetCurrentValue(HsvColorProperty, color.ToHsv());
} }
UpdatePseudoClasses(); UpdatePseudoClasses();

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

@ -96,10 +96,10 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="ThirdComponent"/> property. /// Defines the <see cref="ThirdComponent"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<ColorComponent> ThirdComponentProperty = public static readonly DirectProperty<ColorSpectrum, ColorComponent> ThirdComponentProperty =
AvaloniaProperty.Register<ColorSpectrum, ColorComponent>( AvaloniaProperty.RegisterDirect<ColorSpectrum, ColorComponent>(
nameof(ThirdComponent), nameof(ThirdComponent),
ColorComponent.Component3); // Value o => o.ThirdComponent);
/// <summary> /// <summary>
/// Gets or sets the currently selected color in the RGB color model. /// Gets or sets the currently selected color in the RGB color model.
@ -239,8 +239,8 @@ namespace Avalonia.Controls.Primitives
/// </remarks> /// </remarks>
public ColorComponent ThirdComponent public ColorComponent ThirdComponent
{ {
get => GetValue(ThirdComponentProperty); get => _thirdComponent;
protected set => SetValue(ThirdComponentProperty, value); 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.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Reactive;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Reactive;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -48,6 +48,7 @@ namespace Avalonia.Controls.Primitives
private bool _isPointerPressed = false; private bool _isPointerPressed = false;
private bool _shouldShowLargeSelection = false; private bool _shouldShowLargeSelection = false;
private List<Hsv> _hsvValues = new List<Hsv>(); private List<Hsv> _hsvValues = new List<Hsv>();
private ColorComponent _thirdComponent = ColorComponent.Component3; // HsvComponent.Value
private IDisposable? _layoutRootDisposable; private IDisposable? _layoutRootDisposable;
private IDisposable? _selectionEllipsePanelDisposable; private IDisposable? _selectionEllipsePanelDisposable;
@ -403,7 +404,7 @@ namespace Avalonia.Controls.Primitives
_updatingHsvColor = true; _updatingHsvColor = true;
Hsv newHsv = (new Rgb(color)).ToHsv(); Hsv newHsv = (new Rgb(color)).ToHsv();
HsvColor = newHsv.ToHsvColor(color.A / 255.0); SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(color.A / 255.0));
_updatingHsvColor = false; _updatingHsvColor = false;
UpdateEllipse(); UpdateEllipse();
@ -534,7 +535,7 @@ namespace Avalonia.Controls.Primitives
_updatingColor = true; _updatingColor = true;
Rgb newRgb = (new Hsv(hsvColor)).ToRgb(); Rgb newRgb = (new Hsv(hsvColor)).ToRgb();
Color = newRgb.ToColor(hsvColor.A); SetCurrentValue(ColorProperty, newRgb.ToColor(hsvColor.A));
_updatingColor = false; _updatingColor = false;
@ -608,8 +609,8 @@ namespace Avalonia.Controls.Primitives
Rgb newRgb = newHsv.ToRgb(); Rgb newRgb = newHsv.ToRgb();
double alpha = HsvColor.A; double alpha = HsvColor.A;
Color = newRgb.ToColor(alpha); SetCurrentValue(ColorProperty, newRgb.ToColor(alpha));
HsvColor = newHsv.ToHsvColor(alpha); SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(alpha));
UpdateEllipse(); UpdateEllipse();
UpdatePseudoClasses(); 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), nameof(ColorSpectrumShape),
ColorSpectrumShape.Box); 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> /// <summary>
/// Defines the <see cref="HsvColor"/> property. /// Defines the <see cref="HsvColor"/> property.
/// </summary> /// </summary>
@ -260,6 +268,16 @@ namespace Avalonia.Controls
set => SetValue(ColorSpectrumShapeProperty, value); 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"/> /// <inheritdoc cref="ColorSpectrum.HsvColor"/>
public HsvColor HsvColor public HsvColor HsvColor
{ {

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

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

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

@ -2,6 +2,7 @@
using System.Globalization; using System.Globalization;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Controls.Converters namespace Avalonia.Controls.Converters
{ {
@ -10,6 +11,11 @@ namespace Avalonia.Controls.Converters
/// </summary> /// </summary>
public class ColorToHexConverter : IValueConverter 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/> /// <inheritdoc/>
public object? Convert( public object? Convert(
object? value, object? value,
@ -42,16 +48,7 @@ namespace Avalonia.Controls.Converters
return AvaloniaProperty.UnsetValue; return AvaloniaProperty.UnsetValue;
} }
string hexColor = color.ToUint32().ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant(); return ToHexString(color, AlphaPosition, includeSymbol);
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;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -62,21 +59,159 @@ namespace Avalonia.Controls.Converters
CultureInfo culture) CultureInfo culture)
{ {
string hexValue = value?.ToString() ?? string.Empty; 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; 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 var extendedLength = 2 * input.Length;
return AvaloniaProperty.UnsetValue; 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;
using System.Globalization;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -11,8 +11,11 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public static class ColorHelper public static class ColorHelper
{ {
private static readonly Dictionary<Color, string> cachedDisplayNames = new Dictionary<Color, string>(); private static readonly Dictionary<HsvColor, string> _cachedDisplayNames = new Dictionary<HsvColor, string>();
private static readonly object cacheMutex = new object(); 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> /// <summary>
/// Gets the relative (perceptual) luminance/brightness of the given color. /// 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> /// <returns>The approximate color display name.</returns>
public static string ToDisplayName(Color color) 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. // 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. // 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 // 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. // The rounding value of 5 is specially chosen.
// It is a factor of 255 and therefore evenly divisible which improves // It is a factor of 255 and therefore evenly divisible which improves
// the quality of the calculations. // the quality of the calculations.
double rounding = 5; var roundedHsvColor = new HsvColor(
var roundedColor = new Color( 1.0,
0xFF, Math.Round(hsvColor.H, 0),
Convert.ToByte(Math.Round(color.R / rounding) * rounding), Math.Round(hsvColor.S, 1),
Convert.ToByte(Math.Round(color.G / rounding) * rounding), Math.Round(hsvColor.V, 1));
Convert.ToByte(Math.Round(color.B / rounding) * rounding));
// Attempt to use a previously cached display name // 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; 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) // 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 closestKnownColor = KnownColor.None;
var closestKnownColorDistance = double.PositiveInfinity; 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 // 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( double distance = Math.Sqrt(
Math.Pow((double)(roundedColor.R - knownColor.R), 2.0) + Math.Pow((roundedHsvColor.H - knownHsvColor.H), 2.0) +
Math.Pow((double)(roundedColor.G - knownColor.G), 2.0) + Math.Pow((roundedHsvColor.S - knownHsvColor.S), 2.0) +
Math.Pow((double)(roundedColor.B - knownColor.B), 2.0)); Math.Pow((roundedHsvColor.V - knownHsvColor.V), 2.0));
if (distance < closestKnownColorDistance) if (distance < closestKnownColorDistance)
{ {
closestKnownColor = knownColors[i]; closestKnownColor = knownColor;
closestKnownColorDistance = distance; closestKnownColorDistance = distance;
} }
} }
@ -113,26 +170,19 @@ namespace Avalonia.Controls.Primitives
// Cache results for next time as well // Cache results for next time as well
if (closestKnownColor != KnownColor.None) if (closestKnownColor != KnownColor.None)
{ {
var sb = StringBuilderCache.Acquire(); string? displayName;
string name = closestKnownColor.ToString();
// Add spaces converting PascalCase to human-readable names lock (_knownColorCacheMutex)
for (int i = 0; i < name.Length; i++)
{ {
if (i != 0 && if (!_cachedKnownColorNames.TryGetValue(closestKnownColor, out displayName))
char.IsUpper(name[i]))
{ {
sb.Append(' '); displayName = GetDisplayName(closestKnownColor);
} }
sb.Append(name[i]);
} }
string displayName = StringBuilderCache.GetStringAndRelease(sb); lock (_displayNameCacheMutex)
lock (cacheMutex)
{ {
cachedDisplayNames.Add(roundedColor, displayName); _cachedDisplayNames.Add(roundedHsvColor, displayName);
} }
return displayName; return displayName;
@ -142,5 +192,35 @@ namespace Avalonia.Controls.Primitives
return string.Empty; 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. // Licensed to The Avalonia Project under the MIT License.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Layout; using Avalonia.Layout;

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

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

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

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

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

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

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

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

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

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

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

@ -8,7 +8,6 @@
<pc:ContrastBrushConverter x:Key="ContrastBrushConverter" /> <pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" /> <converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<converters:ColorToHexConverter x:Key="ColorToHexConverter" />
<converters:DoNothingForNullConverter x:Key="DoNothingForNullConverter" /> <converters:DoNothingForNullConverter x:Key="DoNothingForNullConverter" />
<globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" /> <globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
<x:Double x:Key="ColorViewTabStripHeight">48</x:Double> <x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
@ -427,6 +426,7 @@
Content="RGB" Content="RGB"
CornerRadius="0,0,0,0" CornerRadius="0,0,0,0"
BorderThickness="1,1,0,1" 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}" /> IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton" <RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}" Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@ -434,6 +434,7 @@
Content="HSV" Content="HSV"
CornerRadius="0,0,0,0" CornerRadius="0,0,0,0"
BorderThickness="0,1,1,1" 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}" /> IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid> </Grid>
<Grid x:Name="HexInputGrid" <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)); AvaloniaProperty.Register<UniformGridLayout, double>(nameof(MinRowSpacing));
/// <summary> /// <summary>
/// Defines the <see cref="MaximumRowsOrColumnsProperty"/> property. /// Defines the <see cref="MaximumRowsOrColumns"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<int> MaximumRowsOrColumnsProperty = public static readonly StyledProperty<int> MaximumRowsOrColumnsProperty =
AvaloniaProperty.Register<UniformGridLayout, int>(nameof(MaximumRowsOrColumns)); AvaloniaProperty.Register<UniformGridLayout, int>(nameof(MaximumRowsOrColumns));

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

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

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

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

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

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

7
src/Avalonia.Controls/Carousel.cs

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

7
src/Avalonia.Controls/ComboBox.cs

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

5
src/Avalonia.Controls/ContextMenu.cs

@ -4,7 +4,6 @@ using System.ComponentModel;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using System.Linq; using System.Linq;
using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Primitives.PopupPositioning;
@ -84,8 +83,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Control?> PlacementTargetProperty = public static readonly StyledProperty<Control?> PlacementTargetProperty =
Popup.PlacementTargetProperty.AddOwner<ContextMenu>(); Popup.PlacementTargetProperty.AddOwner<ContextMenu>();
private static readonly ITemplate<Panel> DefaultPanel = private static readonly FuncTemplate<Panel?> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel { Orientation = Orientation.Vertical }); new(() => new StackPanel { Orientation = Orientation.Vertical });
private Popup? _popup; private Popup? _popup;
private List<Control>? _attachedControls; private List<Control>? _attachedControls;
private IInputElement? _previousFocus; 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
@ -29,8 +27,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// The default value for the <see cref="ItemsPanel"/> property. /// The default value for the <see cref="ItemsPanel"/> property.
/// </summary> /// </summary>
private static readonly FuncTemplate<Panel> DefaultPanel = private static readonly FuncTemplate<Panel?> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel()); new(() => new StackPanel());
/// <summary> /// <summary>
/// Defines the <see cref="Items"/> property. /// Defines the <see cref="Items"/> property.
@ -58,8 +56,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="ItemsPanel"/> property. /// Defines the <see cref="ItemsPanel"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty = public static readonly StyledProperty<ITemplate<Panel?>> ItemsPanelProperty =
AvaloniaProperty.Register<ItemsControl, ITemplate<Panel>>(nameof(ItemsPanel), DefaultPanel); AvaloniaProperty.Register<ItemsControl, ITemplate<Panel?>>(nameof(ItemsPanel), DefaultPanel);
/// <summary> /// <summary>
/// Defines the <see cref="ItemsSource"/> property. /// Defines the <see cref="ItemsSource"/> property.
@ -202,7 +200,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the panel used to display the items. /// Gets or sets the panel used to display the items.
/// </summary> /// </summary>
public ITemplate<Panel> ItemsPanel public ITemplate<Panel?> ItemsPanel
{ {
get => GetValue(ItemsPanelProperty); get => GetValue(ItemsPanelProperty);
set => SetValue(ItemsPanelProperty, value); set => SetValue(ItemsPanelProperty, value);

7
src/Avalonia.Controls/ListBox.cs

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

6
src/Avalonia.Controls/Menu.cs

@ -1,7 +1,6 @@
using Avalonia.Automation; using Avalonia.Automation;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -14,9 +13,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class Menu : MenuBase, IMainMenu public class Menu : MenuBase, IMainMenu
{ {
private static readonly ITemplate<Panel> DefaultPanel = private static readonly FuncTemplate<Panel?> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel { Orientation = Orientation.Horizontal }); new (() => new StackPanel { Orientation = Orientation.Horizontal });
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Menu"/> class. /// 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 Avalonia.Reactive;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
@ -107,8 +106,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property. /// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary> /// </summary>
private static readonly ITemplate<Panel> DefaultPanel = private static readonly FuncTemplate<Panel?> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel()); new(() => new StackPanel());
private bool _commandCanExecute = true; private bool _commandCanExecute = true;
private bool _commandBindingError; private bool _commandBindingError;

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

@ -16,7 +16,7 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Defines the <see cref="ItemsPanel"/> property. /// Defines the <see cref="ItemsPanel"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty = public static readonly StyledProperty<ITemplate<Panel?>> ItemsPanelProperty =
ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenter>(); ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenter>();
private PanelContainerGenerator? _generator; private PanelContainerGenerator? _generator;
@ -68,7 +68,7 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Gets or sets a template which creates the <see cref="Panel"/> used to display the items. /// Gets or sets a template which creates the <see cref="Panel"/> used to display the items.
/// </summary> /// </summary>
public ITemplate<Panel> ItemsPanel public ITemplate<Panel?> ItemsPanel
{ {
get => GetValue(ItemsPanelProperty); get => GetValue(ItemsPanelProperty);
set => SetValue(ItemsPanelProperty, value); set => SetValue(ItemsPanelProperty, value);
@ -166,6 +166,12 @@ namespace Avalonia.Controls.Presenters
} }
Panel = ItemsPanel.Build(); Panel = ItemsPanel.Build();
if (Panel is null)
{
return;
}
Panel.TemplatedParent = TemplatedParent; Panel.TemplatedParent = TemplatedParent;
Panel.IsItemsHost = true; Panel.IsItemsHost = true;
_scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo; _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
@ -183,7 +189,7 @@ namespace Avalonia.Controls.Presenters
else else
CreateSimplePanelGenerator(); CreateSimplePanelGenerator();
if(Panel is IScrollSnapPointsInfo scrollSnapPointsInfo) if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{ {
scrollSnapPointsInfo.VerticalSnapPointsChanged += (s, e) => 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); AvaloniaProperty.Register<ScrollBar, Orientation>(nameof(Orientation), Orientation.Vertical);
/// <summary> /// <summary>
/// Defines the <see cref="IsExpandedProperty"/> property. /// Defines the <see cref="IsExpanded"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<ScrollBar, bool> IsExpandedProperty = public static readonly DirectProperty<ScrollBar, bool> IsExpandedProperty =
AvaloniaProperty.RegisterDirect<ScrollBar, bool>( 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.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
public class TabStrip : SelectingItemsControl public class TabStrip : SelectingItemsControl
{ {
private static readonly FuncTemplate<Panel> DefaultPanel = private static readonly FuncTemplate<Panel?> DefaultPanel =
new FuncTemplate<Panel>(() => new WrapPanel { Orientation = Orientation.Horizontal }); new(() => new WrapPanel { Orientation = Orientation.Horizontal });
static TabStrip() 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"); Logger.TryGet(LogEventLevel.Verbose, LogArea.Control)?.Log(this, "Creating control template");
var (child, nameScope) = template.Build(this); if (template.Build(this) is { } templateResult)
ApplyTemplatedParent(child, this); {
((ISetLogicalParent)child).SetParent(this); var (child, nameScope) = templateResult;
VisualChildren.Add(child); ApplyTemplatedParent(child, this);
((ISetLogicalParent)child).SetParent(this);
var e = new TemplateAppliedEventArgs(nameScope); VisualChildren.Add(child);
OnApplyTemplate(e);
RaiseEvent(e); var e = new TemplateAppliedEventArgs(nameScope);
OnApplyTemplate(e);
RaiseEvent(e);
}
} }
_appliedTemplate = template; _appliedTemplate = template;

2
src/Avalonia.Controls/ScrollViewer.cs

@ -200,7 +200,7 @@ namespace Avalonia.Controls
ScrollBarVisibility.Auto); ScrollBarVisibility.Auto);
/// <summary> /// <summary>
/// Defines the <see cref="IsExpandedProperty"/> property. /// Defines the <see cref="IsExpanded"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<ScrollViewer, bool> IsExpandedProperty = public static readonly DirectProperty<ScrollViewer, bool> IsExpandedProperty =
ScrollBar.IsExpandedProperty.AddOwner<ScrollViewer>(o => o.IsExpanded); 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); AvaloniaProperty.Register<Slider, TickPlacement>(nameof(TickPlacement), 0d);
/// <summary> /// <summary>
/// Defines the <see cref="TicksProperty"/> property. /// Defines the <see cref="Ticks"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<AvaloniaList<double>?> TicksProperty = public static readonly StyledProperty<AvaloniaList<double>?> TicksProperty =
TickBar.TicksProperty.AddOwner<Slider>(); TickBar.TicksProperty.AddOwner<Slider>();
// Slider required parts // Slider required parts
private bool _isDragging; protected bool _isDragging;
private Track? _track; protected Track? _track;
private Button? _decreaseButton; protected Button? _decreaseButton;
private Button? _increaseButton; protected Button? _increaseButton;
private IDisposable? _decreaseButtonPressDispose; private IDisposable? _decreaseButtonPressDispose;
private IDisposable? _decreaseButtonReleaseDispose; private IDisposable? _decreaseButtonReleaseDispose;
private IDisposable? _increaseButtonSubscription; private IDisposable? _increaseButtonSubscription;

7
src/Avalonia.Controls/TabControl.cs

@ -1,8 +1,6 @@
using System.ComponentModel;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
@ -12,7 +10,6 @@ using Avalonia.LogicalTree;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Automation; using Avalonia.Automation;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Data;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -61,8 +58,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property. /// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary> /// </summary>
private static readonly FuncTemplate<Panel> DefaultPanel = private static readonly FuncTemplate<Panel?> DefaultPanel =
new FuncTemplate<Panel>(() => new WrapPanel()); new(() => new WrapPanel());
/// <summary> /// <summary>
/// Initializes static members of the <see cref="TabControl"/> class. /// 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}"/>. /// Creates a control from a <see cref="Func{TControl}"/>.
/// </summary> /// </summary>
/// <typeparam name="TControl">The type of control.</typeparam> /// <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; private readonly Func<TControl> _func;
@ -31,6 +31,6 @@ namespace Avalonia.Controls.Templates
return _func(); 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.Controls.Primitives;
using Avalonia.Styling;
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
/// <summary> /// <summary>
/// Interface representing a template used to build a <see cref="TemplatedControl"/>. /// Interface representing a template used to build a <see cref="TemplatedControl"/>.
/// </summary> /// </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); unsetValue: WindowTransparencyLevel.None);
/// <summary> /// <summary>
/// Defines the <see cref="TransparencyBackgroundFallbackProperty"/> property. /// Defines the <see cref="TransparencyBackgroundFallback"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IBrush> TransparencyBackgroundFallbackProperty = public static readonly StyledProperty<IBrush> TransparencyBackgroundFallbackProperty =
AvaloniaProperty.Register<TopLevel, IBrush>(nameof(TransparencyBackgroundFallback), Brushes.White); 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.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Layout;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -163,6 +164,9 @@ namespace Avalonia.Controls
{ {
item.IsExpanded = true; item.IsExpanded = true;
if (item.Presenter?.Panel is null)
(this.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
if (item.Presenter?.Panel is { } panel) if (item.Presenter?.Panel is { } panel)
{ {
foreach (var child in panel.Children) foreach (var child in panel.Children)

23
src/Avalonia.Controls/TreeViewItem.cs

@ -40,8 +40,8 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<TreeViewItem, int>( AvaloniaProperty.RegisterDirect<TreeViewItem, int>(
nameof(Level), o => o.Level); nameof(Level), o => o.Level);
private static readonly ITemplate<Panel> DefaultPanel = private static readonly FuncTemplate<Panel?> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel()); new(() => new StackPanel());
private TreeView? _treeView; private TreeView? _treeView;
private Control? _header; private Control? _header;
@ -90,8 +90,20 @@ namespace Avalonia.Controls
internal TreeView? TreeViewOwner => _treeView; internal TreeView? TreeViewOwner => _treeView;
protected internal override Control CreateContainerForItemOverride() => new TreeViewItem(); protected internal override Control CreateContainerForItemOverride()
protected internal override bool IsItemItsOwnContainerOverride(Control item) => item is TreeViewItem; {
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/> /// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
@ -283,6 +295,9 @@ namespace Avalonia.Controls
return logical != null ? result : @default; 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) private void HeaderDoubleTapped(object? sender, TappedEventArgs e)
{ {
OnHeaderDoubleTapped(e); OnHeaderDoubleTapped(e);

2
src/Avalonia.Controls/VirtualizingStackPanel.cs

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

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

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

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

@ -65,7 +65,7 @@
IsLightDismissEnabled="False" IsLightDismissEnabled="False"
IsOpen="{TemplateBinding IsSubMenuOpen, IsOpen="{TemplateBinding IsSubMenuOpen,
Mode=TwoWay}" Mode=TwoWay}"
Placement="Right"> Placement="RightEdgeAlignedTop">
<Border Background="{DynamicResource ThemeBackgroundBrush}" <Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> 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\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
<Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" /> <Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />
<Compile Include="XamlLoadException.cs" /> <Compile Include="XamlLoadException.cs" />
<Compile Include="..\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs" />
<Compile Include="XamlTypes.cs" /> <Compile Include="XamlTypes.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -69,6 +68,7 @@
<Import Project="..\..\..\build\ApiDiff.props" /> <Import Project="..\..\..\build\ApiDiff.props" />
<Import Project="..\..\..\build\DevAnalyzers.props" /> <Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\TrimmingEnable.props" /> <Import Project="..\..\..\build\TrimmingEnable.props" />
<Import Project="..\..\..\build\NullableEnable.props" />
<ItemGroup Label="InternalsVisibleTo"> <ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.Markup.Xaml.UnitTests, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Markup.Xaml.UnitTests, PublicKey=$(AvaloniaPublicKey)" />

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

@ -1,8 +1,7 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO;
using Avalonia.Platform; using Avalonia.Platform;
#nullable enable
namespace Avalonia.Markup.Xaml namespace Avalonia.Markup.Xaml
{ {
/// <summary> /// <summary>
@ -79,7 +78,7 @@ namespace Avalonia.Markup.Xaml
var compiledLoader = assetLocator.GetAssembly(uri, baseUri) var compiledLoader = assetLocator.GetAssembly(uri, baseUri)
?.GetType("CompiledAvaloniaXaml.!XamlLoader") ?.GetType("CompiledAvaloniaXaml.!XamlLoader")
?.GetMethod("TryLoad", new[] { typeof(System.IServiceProvider), typeof(string) }); ?.GetMethod("TryLoad", new[] { typeof(IServiceProvider), typeof(string) });
if (compiledLoader != null) if (compiledLoader != null)
{ {
var compiledResult = compiledLoader.Invoke(null, new object?[] { sp, absoluteUri.ToString()}); 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)] [RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)]
public class AvaloniaPropertyTypeConverter : TypeConverter 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); 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 registry = AvaloniaPropertyRegistry.Instance;
var (ns, owner, propertyName) = PropertyParser.Parse(new CharacterReader(((string)value).AsSpan())); var (ns, owner, propertyName) = PropertyParser.Parse(new CharacterReader(((string)value).AsSpan()));
var ownerType = TryResolveOwnerByName(context, ns, owner); var ownerType = TryResolveOwnerByName(context, ns, owner);
var targetType = context.GetFirstParent<ControlTemplate>()?.TargetType ?? var targetType = context?.GetFirstParent<ControlTemplate>()?.TargetType ??
context.GetFirstParent<Style>()?.Selector?.TargetType ?? context?.GetFirstParent<Style>()?.Selector?.TargetType ??
typeof(Control); typeof(Control);
var effectiveOwner = ownerType ?? targetType; var effectiveOwner = ownerType ?? targetType;
var property = registry.FindRegistered(effectiveOwner, propertyName); var property = registry.FindRegistered(effectiveOwner, propertyName);
@ -51,11 +53,11 @@ namespace Avalonia.Markup.Xaml.Converters
} }
[RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)] [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) if (owner != null)
{ {
var result = context.ResolveType(ns, owner); var result = context?.ResolveType(ns, owner);
if (result == null) 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 class AvaloniaUriTypeConverter : TypeConverter
{ {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) /// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{ {
return sourceType == typeof(string); 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; var s = value as string;
if (s == null) if (s == null)

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

@ -1,20 +1,21 @@
using System; using System;
using System.ComponentModel;
using System.Globalization; using System.Globalization;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
namespace Avalonia.Markup.Xaml.Converters namespace Avalonia.Markup.Xaml.Converters
{ {
using System.ComponentModel;
public class BitmapTypeConverter : TypeConverter 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); 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 s = (string)value;
var uri = s.StartsWith("/") var uri = s.StartsWith("/")
@ -25,7 +26,7 @@ namespace Avalonia.Markup.Xaml.Converters
return new Bitmap(uri.LocalPath); return new Bitmap(uri.LocalPath);
var assets = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>(); 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"/> /// 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. /// is <see cref="IBrush"/> then converts the color to a solid color brush.
/// </returns> /// </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); 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"/> /// 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. /// is <see cref="Color"/> then converts the solid color brush to a color.
/// </returns> /// </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); 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"/> /// 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. /// is <see cref="IBrush"/> then converts the color to a solid color brush.
/// </returns> /// </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) 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"/> /// 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. /// is <see cref="Color"/> then converts the solid color brush to a color.
/// </returns> /// </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) 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;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization; using System.Globalization;
using Avalonia.Media; using Avalonia.Media;
namespace Avalonia.Markup.Xaml.Converters namespace Avalonia.Markup.Xaml.Converters
{ {
public class FontFamilyTypeConverter : TypeConverter 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); 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 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.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using System; using System;
using System.ComponentModel;
using System.Globalization; using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters namespace Avalonia.Markup.Xaml.Converters
{ {
using System.ComponentModel;
public class IconTypeConverter : TypeConverter 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); 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; var path = value as string;
if (path != null) if (path != null)
@ -32,7 +33,7 @@ namespace Avalonia.Markup.Xaml.Converters
throw new NotSupportedException(); throw new NotSupportedException();
} }
private static WindowIcon CreateIconFromPath(ITypeDescriptorContext context, string s) private static WindowIcon CreateIconFromPath(ITypeDescriptorContext? context, string s)
{ {
var uri = s.StartsWith("/") var uri = s.StartsWith("/")
? new Uri(s, UriKind.Relative) ? new Uri(s, UriKind.Relative)
@ -41,7 +42,7 @@ namespace Avalonia.Markup.Xaml.Converters
if(uri.IsAbsoluteUri && uri.IsFile) if(uri.IsAbsoluteUri && uri.IsFile)
return new WindowIcon(uri.LocalPath); return new WindowIcon(uri.LocalPath);
var assets = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>(); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization; using System.Globalization;
using Avalonia.Utilities;
namespace Avalonia.Markup.Xaml.Converters namespace Avalonia.Markup.Xaml.Converters
{ {
using System.ComponentModel;
using Avalonia.Utilities;
public class PointsListTypeConverter : TypeConverter public class PointsListTypeConverter : TypeConverter
{ {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{ {
return sourceType == typeof(string); 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>(); var points = new List<Point>();

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

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

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

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
@ -11,38 +10,38 @@ namespace Avalonia.Markup.Xaml
{ {
internal static class Extensions internal static class Extensions
{ {
public static T GetService<T>(this IServiceProvider sp) => (T)sp?.GetService(typeof(T)); public static T? GetService<T>(this IServiceProvider sp) => (T?)sp.GetService(typeof(T));
public static T GetRequiredService<T>(this IServiceProvider sp)
public static Uri GetContextBaseUri(this IServiceProvider ctx) => ctx.GetService<IUriContext>().BaseUri; => sp.GetService<T>() ?? throw new InvalidOperationException($"Service {typeof(T)} hasn't been registered");
public static T GetFirstParent<T>(this IServiceProvider ctx) where T : class public static Uri? GetContextBaseUri(this IServiceProvider ctx) => ctx.GetService<IUriContext>()?.BaseUri;
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>().FirstOrDefault();
public static T GetLastParent<T>(this IServiceProvider ctx) where T : class public static T? GetFirstParent<T>(this IServiceProvider ctx) where T : class
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>().LastOrDefault(); => 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) public static IEnumerable<T> GetParents<T>(this IServiceProvider sp)
{ => sp.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents.OfType<T>() ?? Enumerable.Empty<T>();
return sp.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>();
}
public static bool IsInControlTemplate(this IServiceProvider sp) => sp.GetService<IAvaloniaXamlIlControlTemplateProvider>() != null; public static bool IsInControlTemplate(this IServiceProvider sp) => sp.GetService<IAvaloniaXamlIlControlTemplateProvider>() != null;
[RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)] [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}"; 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 // 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 // up named controls and style resources. First look for the closest Control in
// the context. // the context.
object anchor = provider.GetFirstParent<Control>(); object? anchor = provider.GetFirstParent<Control>();
if (anchor is null) 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) if (Source != null)
{ {
@ -61,8 +61,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
} }
else 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( return CreateSourceObserver(
(target as StyledElement) ?? (anchor as StyledElement), styledElement,
Path.BuildExpression(enableDataValidation)); Path.BuildExpression(enableDataValidation));
} }
} }
@ -70,8 +74,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
[ConstructorArgument("path")] [ConstructorArgument("path")]
public CompiledBindingPath Path { get; set; } 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;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
class ArrayElementPlugin : IPropertyAccessorPlugin internal class ArrayElementPlugin : IPropertyAccessorPlugin
{ {
private readonly int[] _indices; private readonly int[] _indices;
private readonly Type _elementType; private readonly Type _elementType;
@ -25,7 +23,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
} }
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] [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) 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 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)) 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.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
@ -13,11 +11,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
internal class CommandAccessorPlugin : IPropertyAccessorPlugin internal class CommandAccessorPlugin : IPropertyAccessorPlugin
{ {
private readonly Action<object, object> _execute; private readonly Action<object, object?> _execute;
private readonly Func<object, object, bool> _canExecute; private readonly Func<object, object?, bool>? _canExecute;
private readonly ISet<string> _dependsOnProperties; 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; _execute = execute;
_canExecute = canExecute; _canExecute = canExecute;
@ -31,18 +29,18 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
} }
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] [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); return new CommandAccessor(reference, _execute, _canExecute, _dependsOnProperties);
} }
private sealed class CommandAccessor : PropertyAccessorBase private sealed class CommandAccessor : PropertyAccessorBase
{ {
private readonly WeakReference<object> _reference; private readonly WeakReference<object?> _reference;
private Command _command; private readonly Command _command;
private readonly ISet<string> _dependsOnProperties; 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)); _reference = reference ?? throw new ArgumentNullException(nameof(reference));
_dependsOnProperties = dependsOnProperties; _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() private void RaiseCanExecuteChanged()
{ {
@ -59,13 +57,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
private sealed class Command : ICommand private sealed class Command : ICommand
{ {
private readonly WeakReference<object> _target; private readonly WeakReference<object?> _target;
private readonly Action<object, object> _execute; private readonly Action<object, object?> _execute;
private readonly Func<object, object, bool> _canExecute; 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; _target = target;
_execute = execute; _execute = execute;
@ -78,7 +76,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
, Threading.DispatcherPriority.Input); , Threading.DispatcherPriority.Input);
} }
public bool CanExecute(object parameter) public bool CanExecute(object? parameter)
{ {
if (_target.TryGetTarget(out var target)) if (_target.TryGetTarget(out var target))
{ {
@ -91,7 +89,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return false; return false;
} }
public void Execute(object parameter) public void Execute(object? parameter)
{ {
if (_target.TryGetTarget(out var target)) if (_target.TryGetTarget(out var target))
{ {
@ -102,12 +100,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public override Type PropertyType => typeof(ICommand); public override Type PropertyType => typeof(ICommand);
public override bool SetValue(object value, BindingPriority priority) public override bool SetValue(object? value, BindingPriority priority)
{ {
return false; return false;
} }
void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e) private void OnNotifyPropertyChanged(object? sender, PropertyChangedEventArgs e)
{ {
if (string.IsNullOrEmpty(e.PropertyName) || _dependsOnProperties.Contains(e.PropertyName)) 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() public CompiledBindingPath()
=> _elements = Array.Empty<ICompiledBindingPathElement>(); => _elements = Array.Empty<ICompiledBindingPathElement>();
internal CompiledBindingPath(ICompiledBindingPathElement[] elements, object rawSource) internal CompiledBindingPath(ICompiledBindingPathElement[] elements, object? rawSource)
{ {
_elements = elements; _elements = elements;
RawSource = rawSource; RawSource = rawSource;
@ -26,14 +26,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.CompiledBindingSafeSupressWarningMessage)] [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.CompiledBindingSafeSupressWarningMessage)]
internal ExpressionNode BuildExpression(bool enableValidation) internal ExpressionNode BuildExpression(bool enableValidation)
{ {
ExpressionNode pathRoot = null; ExpressionNode? pathRoot = null;
ExpressionNode path = null; ExpressionNode? path = null;
foreach (var element in _elements) foreach (var element in _elements)
{ {
ExpressionNode node = null; ExpressionNode? node;
switch (element) switch (element)
{ {
case NotExpressionPathElement _: case NotExpressionPathElement:
node = new LogicalNotNode(); node = new LogicalNotNode();
break; break;
case PropertyElement prop: case PropertyElement prop:
@ -54,7 +54,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
case AncestorPathElement ancestor: case AncestorPathElement ancestor:
node = new FindAncestorNode(ancestor.AncestorType, ancestor.Level); node = new FindAncestorNode(ancestor.AncestorType, ancestor.Level);
break; break;
case SelfPathElement _: case SelfPathElement:
node = new SelfNode(); node = new SelfNode();
break; break;
case ElementNameElement name: case ElementNameElement name:
@ -70,7 +70,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
throw new InvalidOperationException($"Unknown binding path element type {element.GetType().FullName}"); 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(); return pathRoot ?? new EmptyExpressionNode();
@ -81,16 +81,17 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal SourceMode SourceMode => Array.Exists(_elements, e => e is IControlSourceBindingPathElement) internal SourceMode SourceMode => Array.Exists(_elements, e => e is IControlSourceBindingPathElement)
? SourceMode.Control : SourceMode.Data; ? SourceMode.Control : SourceMode.Data;
internal object RawSource { get; } internal object? RawSource { get; }
/// <inheritdoc />
public override string ToString() public override string ToString()
=> string.Concat((IEnumerable<ICompiledBindingPathElement>) _elements); => string.Concat((IEnumerable<ICompiledBindingPathElement>) _elements);
} }
public class CompiledBindingPathBuilder public class CompiledBindingPathBuilder
{ {
private object _rawSource; private object? _rawSource;
private List<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>(); private readonly List<ICompiledBindingPathElement> _elements = new();
public CompiledBindingPathBuilder Not() public CompiledBindingPathBuilder Not()
{ {
@ -98,7 +99,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this; 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)); _elements.Add(new PropertyElement(info, accessorFactory, _elements.Count == 0));
return this; return this;
@ -110,7 +111,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this; 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>())); _elements.Add(new MethodAsCommandElement(methodName, executeHelper, canExecuteHelper, dependsOnProperties ?? Array.Empty<string>()));
return this; return this;
@ -163,7 +164,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this; return this;
} }
public CompiledBindingPathBuilder SetRawSource(object rawSource) public CompiledBindingPathBuilder SetRawSource(object? rawSource)
{ {
_rawSource = rawSource; _rawSource = rawSource;
return this; return this;
@ -187,7 +188,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
private readonly bool _isFirstElement; 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; Property = property;
AccessorFactory = accessorFactory; AccessorFactory = accessorFactory;
@ -196,7 +197,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public IPropertyInfo Property { get; } public IPropertyInfo Property { get; }
public Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; } public Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
public override string ToString() public override string ToString()
=> _isFirstElement ? Property.Name : $".{Property.Name}"; => _isFirstElement ? Property.Name : $".{Property.Name}";
@ -206,7 +207,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
public MethodAsDelegateElement(RuntimeMethodHandle method, RuntimeTypeHandle delegateType) 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); DelegateType = Type.GetTypeFromHandle(delegateType);
} }
@ -217,7 +219,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class MethodAsCommandElement : ICompiledBindingPathElement 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; MethodName = methodName;
ExecuteMethod = executeHelper; ExecuteMethod = executeHelper;
@ -226,8 +228,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
} }
public string MethodName { get; } public string MethodName { get; }
public Action<object, object> ExecuteMethod { get; } public Action<object, object?> ExecuteMethod { get; }
public Func<object, object, bool> CanExecuteMethod { get; } public Func<object, object?, bool>? CanExecuteMethod { get; }
public HashSet<string> DependsOnProperties { get; } public HashSet<string> DependsOnProperties { get; }
} }
@ -240,7 +242,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
Type Type { get; } Type Type { get; }
Func<object, object> Cast { get; } Func<object?, object?> Cast { get; }
} }
internal class TaskStreamPathElement<T> : IStronglyTypedStreamElement internal class TaskStreamPathElement<T> : IStronglyTypedStreamElement
@ -267,13 +269,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class AncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement internal class AncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
{ {
public AncestorPathElement(Type ancestorType, int level) public AncestorPathElement(Type? ancestorType, int level)
{ {
AncestorType = ancestorType; AncestorType = ancestorType;
Level = level; Level = level;
} }
public Type AncestorType { get; } public Type? AncestorType { get; }
public int Level { get; } public int Level { get; }
public override string ToString() public override string ToString()
@ -282,13 +284,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class VisualAncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement internal class VisualAncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
{ {
public VisualAncestorPathElement(Type ancestorType, int level) public VisualAncestorPathElement(Type? ancestorType, int level)
{ {
AncestorType = ancestorType; AncestorType = ancestorType;
Level = level; Level = level;
} }
public Type AncestorType { get; } public Type? AncestorType { get; }
public int Level { get; } public int Level { get; }
} }
@ -323,7 +325,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class TypeCastPathElement<T> : ITypeCastElement internal class TypeCastPathElement<T> : ITypeCastElement
{ {
private static object TryCast(object obj) private static object? TryCast(object? obj)
{ {
if (obj is T result) if (obj is T result)
return result; return result;
@ -332,7 +334,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public Type Type => typeof(T); public Type Type => typeof(T);
public Func<object, object> Cast => TryCast; public Func<object?, object?> Cast => TryCast;
public override string ToString() public override string ToString()
=> $"({Type.FullName})"; => $"({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 namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
class FindVisualAncestorNode : ExpressionNode internal class FindVisualAncestorNode : ExpressionNode
{ {
private readonly int _level; private readonly int _level;
private readonly Type _ancestorType; private readonly Type? _ancestorType;
private IDisposable _subscription; private IDisposable? _subscription;
public FindVisualAncestorNode(Type ancestorType, int level) public FindVisualAncestorNode(Type? ancestorType, int level)
{ {
_level = level; _level = level;
_ancestorType = ancestorType; _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); _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;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using System.Text;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
internal class MethodAccessorPlugin : IPropertyAccessorPlugin internal class MethodAccessorPlugin : IPropertyAccessorPlugin
{ {
private MethodInfo _method; private readonly MethodInfo _method;
private readonly Type _delegateType; private readonly Type _delegateType;
public MethodAccessorPlugin(MethodInfo method, 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;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
using Avalonia.Reactive; using Avalonia.Reactive;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
class ObservableStreamPlugin<T> : IStreamPlugin internal class ObservableStreamPlugin<T> : IStreamPlugin
{ {
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] [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>; return reference.TryGetTarget(out var target) && target is IObservable<T>;
} }
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] [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)) 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 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 class PropertyInfoAccessorFactory
{ {
public static IPropertyAccessor CreateInpcPropertyAccessor(WeakReference<object> target, IPropertyInfo property) public static IPropertyAccessor CreateInpcPropertyAccessor(WeakReference<object?> target, IPropertyInfo property)
=> new InpcPropertyAccessor(target, property); => new InpcPropertyAccessor(target, property);
public static IPropertyAccessor CreateAvaloniaPropertyAccessor(WeakReference<object> target, IPropertyInfo 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); => 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); => new IndexerAccessor(target, property, argument);
} }
internal class AvaloniaPropertyAccessor : PropertyAccessorBase internal class AvaloniaPropertyAccessor : PropertyAccessorBase
{ {
private readonly WeakReference<AvaloniaObject> _reference; private readonly WeakReference<AvaloniaObject?> _reference;
private readonly AvaloniaProperty _property; 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));; _reference = reference ?? throw new ArgumentNullException(nameof(reference));
_property = property ?? throw new ArgumentNullException(nameof(property));; _property = property ?? throw new ArgumentNullException(nameof(property));
} }
public AvaloniaObject Instance public AvaloniaObject? Instance
{ {
get get
{ {
@ -43,13 +43,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
} }
public override Type PropertyType => _property.PropertyType; 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; return true;
} }
@ -70,10 +70,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class InpcPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber<PropertyChangedEventArgs> internal class InpcPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber<PropertyChangedEventArgs>
{ {
protected readonly WeakReference<object> _reference; protected readonly WeakReference<object?> _reference;
private readonly IPropertyInfo _property; 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)); _reference = reference ?? throw new ArgumentNullException(nameof(reference));
_property = property ?? throw new ArgumentNullException(nameof(property)); _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 Type PropertyType => _property.PropertyType;
public override object Value public override object? Value
{ {
get 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)) if (_property.CanSet && _reference.TryGetTarget(out var o))
{ {
@ -103,7 +103,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return false; 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)) 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> 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) :base(target, basePropertyInfo)
{ {
_index = argument; _index = argument;
@ -167,7 +167,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
WeakEvents.CollectionChanged.Unsubscribe(incc, this); 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)) 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) switch (e.Action)
{ {
@ -185,12 +185,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return _index >= e.OldStartingIndex; return _index >= e.OldStartingIndex;
case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Replace:
return _index >= e.NewStartingIndex && return _index >= e.NewStartingIndex &&
_index < e.NewStartingIndex + e.NewItems.Count; _index < e.NewStartingIndex + e.NewItems!.Count;
case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Move:
return (_index >= e.NewStartingIndex && return (_index >= e.NewStartingIndex &&
_index < e.NewStartingIndex + e.NewItems.Count) || _index < e.NewStartingIndex + e.NewItems!.Count) ||
(_index >= e.OldStartingIndex && (_index >= e.OldStartingIndex &&
_index < e.OldStartingIndex + e.OldItems.Count); _index < e.OldStartingIndex + e.OldItems!.Count);
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
return true; return true;
} }

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

@ -1,20 +1,17 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text;
using Avalonia.Data;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
class PropertyInfoAccessorPlugin : IPropertyAccessorPlugin internal class PropertyInfoAccessorPlugin : IPropertyAccessorPlugin
{ {
private readonly IPropertyInfo _propertyInfo; 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; _propertyInfo = propertyInfo;
_accessorFactory = accessorFactory; _accessorFactory = accessorFactory;
@ -27,7 +24,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
} }
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName) public IPropertyAccessor Start(WeakReference<object?> reference, string propertyName)
{ {
Debug.Assert(_propertyInfo.Name == propertyName); Debug.Assert(_propertyInfo.Name == propertyName);
return _accessorFactory(reference, _propertyInfo); 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 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; _cast = cast;
} }
protected override object Cast(object value) protected override object? Cast(object? value)
=> _cast(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 namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{ {
class TaskStreamPlugin<T> : IStreamPlugin internal class TaskStreamPlugin<T> : IStreamPlugin
{ {
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] [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>; return reference.TryGetTarget(out var target) && target is Task<T>;
} }
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] [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)) if(!(reference.TryGetTarget(out var target) && target is Task<T> task))
{ {
return Observable.Empty<object>(); return Observable.Empty<object?>();
} }
switch (task.Status) switch (task.Status)
@ -29,9 +29,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
case TaskStatus.Faulted: case TaskStatus.Faulted:
return HandleCompleted(task); return HandleCompleted(task);
default: default:
var subject = new LightweightSubject<object>(); var subject = new LightweightSubject<object?>();
task.ContinueWith( task.ContinueWith(
x => HandleCompleted(task).Subscribe(subject), _ => HandleCompleted(task).Subscribe(subject),
TaskScheduler.FromCurrentSynchronizationContext()) TaskScheduler.FromCurrentSynchronizationContext())
.ConfigureAwait(false); .ConfigureAwait(false);
return subject; 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) switch (task.Status)
{ {
case TaskStatus.RanToCompletion: case TaskStatus.RanToCompletion:
return Observable.Return((object)task.Result); return Observable.Return((object?)task.Result);
case TaskStatus.Faulted: case TaskStatus.Faulted:
return Observable.Return(new BindingNotification(task.Exception, BindingErrorType.Error)); return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error));
default: default:
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task."); 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.Markup.Xaml.Converters;
using Avalonia.Media; using Avalonia.Media;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
public class DynamicResourceExtension : IBinding public class DynamicResourceExtension : IBinding
@ -31,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var provideTarget = serviceProvider.GetService<IProvideValueTarget>(); var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
if (!(provideTarget.TargetObject is StyledElement)) if (provideTarget?.TargetObject is not StyledElement)
{ {
_anchor = serviceProvider.GetFirstParent<StyledElement>() ?? _anchor = serviceProvider.GetFirstParent<StyledElement>() ??
serviceProvider.GetFirstParent<IResourceProvider>() ?? 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; using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.MarkupExtensions; namespace Avalonia.Markup.Xaml.MarkupExtensions;

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

@ -1,6 +1,4 @@
#nullable enable
using System; using System;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
@ -20,7 +18,7 @@ public sealed class OnFormFactorExtension : OnFormFactorExtensionBase<object, On
public static bool ShouldProvideOption(IServiceProvider serviceProvider, FormFactorType option) 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) 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 Avalonia.Compatibility;
using System;
using Avalonia.Compatibility;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.MarkupExtensions; namespace Avalonia.Markup.Xaml.MarkupExtensions;

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

@ -2,7 +2,6 @@ using Avalonia.Data;
using System; using System;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
@ -21,11 +20,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public Binding ProvideValue(IServiceProvider serviceProvider) public Binding ProvideValue(IServiceProvider serviceProvider)
{ {
var descriptorContext = (ITypeDescriptorContext)serviceProvider;
return new Binding return new Binding
{ {
TypeResolver = descriptorContext.ResolveType, TypeResolver = serviceProvider.ResolveType,
Converter = Converter, Converter = Converter,
ConverterParameter = ConverterParameter, ConverterParameter = ConverterParameter,
ElementName = ElementName, ElementName = ElementName,
@ -36,33 +33,33 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Source = Source, Source = Source,
StringFormat = StringFormat, StringFormat = StringFormat,
RelativeSource = RelativeSource, RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(descriptorContext.GetDefaultAnchor()), DefaultAnchor = new WeakReference(serviceProvider.GetDefaultAnchor()),
TargetNullValue = TargetNullValue, 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; } public BindingMode Mode { get; set; }
[ConstructorArgument("path")] [ConstructorArgument("path")]
public string Path { get; set; } public string Path { get; set; } = "";
public BindingPriority Priority { get; set; } = BindingPriority.LocalValue; 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")] [ConstructorArgument("mode")]
public RelativeSourceMode Mode { get; set; } = RelativeSourceMode.FindAncestor; public RelativeSourceMode Mode { get; set; } = RelativeSourceMode.FindAncestor;
public Type AncestorType { get; set; } public Type? AncestorType { get; set; }
public TreeType Tree { 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.Controls;
using Avalonia.Data.Core; using Avalonia.Data.Core;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
public class ResolveByNameExtension public class ResolveByNameExtension
@ -18,6 +16,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public object? ProvideValue(IServiceProvider serviceProvider) public object? ProvideValue(IServiceProvider serviceProvider)
{ {
var nameScope = serviceProvider.GetService<INameScope>(); var nameScope = serviceProvider.GetService<INameScope>();
if (nameScope is null)
return null;
var value = nameScope.FindAsync(Name); var value = nameScope.FindAsync(Name);
@ -25,10 +26,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
return value.GetResult(); return value.GetResult();
var provideValueTarget = serviceProvider.GetService<IProvideValueTarget>(); 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())); value.OnCompleted(() => property.Set(target, value.GetResult()));
}
return AvaloniaProperty.UnsetValue; return AvaloniaProperty.UnsetValue;
} }

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

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Data; using Avalonia.Markup.Data;
@ -8,8 +7,6 @@ using Avalonia.Markup.Xaml.Converters;
using Avalonia.Markup.Xaml.XamlIl.Runtime; using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Styling; using Avalonia.Styling;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
public class StaticResourceExtension public class StaticResourceExtension
@ -25,7 +22,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public object? ResourceKey { get; set; } public object? ResourceKey { get; set; }
public object ProvideValue(IServiceProvider serviceProvider) public object? ProvideValue(IServiceProvider serviceProvider)
{ {
if (ResourceKey is not { } resourceKey) if (ResourceKey is not { } resourceKey)
{ {
@ -34,32 +31,37 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var stack = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>(); var stack = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>();
var provideTarget = serviceProvider.GetService<IProvideValueTarget>(); 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, AvaloniaProperty ap => ap.PropertyType,
PropertyInfo pi => pi.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 // Look upwards though the ambient context for IResourceNodes
// which might be able to give us the resource. // 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 && if (targetObject is Control target &&
provideTarget.TargetProperty is PropertyInfo property) targetProperty is PropertyInfo property)
{ {
// This is stored locally to avoid allocating closure in the outer scope. // This is stored locally to avoid allocating closure in the outer scope.
var localTargetType = targetType; var localTargetType = targetType;
@ -72,7 +74,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
throw new KeyNotFoundException($"Static resource '{resourceKey}' not found."); 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); 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 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) if (r.End)
{ {
@ -14,9 +14,9 @@ namespace Avalonia.Markup.Xaml.Parsers
var openParens = r.TakeIf('('); var openParens = r.TakeIf('(');
bool closeParens = false; bool closeParens = false;
string ns = null; string? ns = null;
string owner = null; string? owner = null;
string name = null; string? name = null;
do do
{ {

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

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

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

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

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

@ -1,8 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Markup.Xaml.Styling; 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.Controls;
using Avalonia.Styling; using Avalonia.Styling;
#nullable enable
namespace Avalonia.Markup.Xaml.Styling namespace Avalonia.Markup.Xaml.Styling
{ {
/// <summary> /// <summary>

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

Loading…
Cancel
Save