Browse Source

Merge branch 'master' into fixes/headlessGlyphTypeface

pull/10279/head
Benedikt Stebner 4 years ago
committed by GitHub
parent
commit
5235431c7a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/ImageSharp.props
  2. 2
      build/Moq.props
  3. 15
      build/XUnit.props
  4. 11
      src/Avalonia.Base/Animation/KeySpline.cs
  5. 38
      src/Avalonia.Base/AvaloniaObject.cs
  6. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  7. 3
      src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs
  8. 18
      src/Avalonia.Base/Input/DragEventArgs.cs
  9. 2
      src/Avalonia.Base/Input/KeyGesture.cs
  10. 44
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  11. 48
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  12. 6
      src/Avalonia.Base/Input/Platform/IClipboard.cs
  13. 6
      src/Avalonia.Base/LogicalTree/LogicalExtensions.cs
  14. 11
      src/Avalonia.Base/Media/Color.cs
  15. 2
      src/Avalonia.Base/Media/DrawingContext.cs
  16. 26
      src/Avalonia.Base/Media/DrawingGroup.cs
  17. 6
      src/Avalonia.Base/Media/DrawingImage.cs
  18. 4
      src/Avalonia.Base/Media/FontFamily.cs
  19. 5
      src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs
  20. 6
      src/Avalonia.Base/Media/FormattedText.cs
  21. 6
      src/Avalonia.Base/Media/GeometryDrawing.cs
  22. 12
      src/Avalonia.Base/Media/GlyphRunDrawing.cs
  23. 2
      src/Avalonia.Base/Media/HslColor.cs
  24. 2
      src/Avalonia.Base/Media/HsvColor.cs
  25. 3
      src/Avalonia.Base/Media/IVisualBrush.cs
  26. 24
      src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs
  27. 7
      src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
  28. 14
      src/Avalonia.Base/Media/TextDecoration.cs
  29. 7
      src/Avalonia.Base/Media/VisualBrush.cs
  30. 4
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  31. 33
      src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
  32. 2
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
  33. 2
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
  34. 2
      src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
  35. 82
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  36. 3
      src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
  37. 4
      src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
  38. 2
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  39. 14
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  40. 2
      src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
  41. 1
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
  42. 4
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  43. 2
      src/Avalonia.Base/Rendering/IRenderRoot.cs
  44. 3
      src/Avalonia.Base/Rendering/IRenderer.cs
  45. 6
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  46. 7
      src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
  47. 11
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  48. 68
      src/Avalonia.Base/Utilities/WeakEvent.cs
  49. 45
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  50. 34
      src/Avalonia.Base/Visual.cs
  51. 19
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  52. 49
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  53. 2
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  54. 2
      src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs
  55. 93
      src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs
  56. 15
      src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
  57. 14
      src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs
  58. 28
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  59. 5
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  60. 4
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  61. 282
      src/Avalonia.Controls/Automation/AutomationProperties.cs
  62. 62
      src/Avalonia.Controls/Automation/Peers/ProgressBarAutomationPeer.cs
  63. 4
      src/Avalonia.Controls/Button.cs
  64. 18
      src/Avalonia.Controls/Calendar/Calendar.cs
  65. 14
      src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
  66. 112
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  67. 33
      src/Avalonia.Controls/Calendar/DateTimeHelper.cs
  68. 20
      src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
  69. 40
      src/Avalonia.Controls/Chrome/TitleBar.cs
  70. 4
      src/Avalonia.Controls/Control.cs
  71. 18
      src/Avalonia.Controls/Controls.cs
  72. 1
      src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
  73. 90
      src/Avalonia.Controls/DefinitionBase.cs
  74. 9
      src/Avalonia.Controls/DockPanel.cs
  75. 9
      src/Avalonia.Controls/Documents/Inline.cs
  76. 18
      src/Avalonia.Controls/Documents/InlineUIContainer.cs
  77. 14
      src/Avalonia.Controls/Documents/Span.cs
  78. 230
      src/Avalonia.Controls/Grid.cs
  79. 7
      src/Avalonia.Controls/Image.cs
  80. 41
      src/Avalonia.Controls/ItemsControl.cs
  81. 20
      src/Avalonia.Controls/LayoutTransformControl.cs
  82. 1
      src/Avalonia.Controls/ListBox.cs
  83. 12
      src/Avalonia.Controls/MaskedTextBox.cs
  84. 22
      src/Avalonia.Controls/NativeControlHost.cs
  85. 20
      src/Avalonia.Controls/NativeMenu.Export.cs
  86. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  87. 12
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  88. 36
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  89. 14
      src/Avalonia.Controls/Primitives/Popup.cs
  90. 4
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  91. 312
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  92. 7
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  93. 8
      src/Avalonia.Controls/Primitives/TextSearch.cs
  94. 2
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  95. 19
      src/Avalonia.Controls/Primitives/Track.cs
  96. 16
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  97. 9
      src/Avalonia.Controls/ProgressBar.cs
  98. 7
      src/Avalonia.Controls/RelativePanel.cs
  99. 44
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  100. 13
      src/Avalonia.Controls/Slider.cs

2
build/ImageSharp.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.1" /> <PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/Moq.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="Moq" Version="4.14.1" /> <PackageReference Include="Moq" Version="4.18.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>

15
build/XUnit.props

@ -1,13 +1,12 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" /> <PackageReference Include="xunit.assert" Version="2.4.2" />
<PackageReference Include="xunit.assert" Version="2.4.1" /> <PackageReference Include="xunit.core" Version="2.4.2" />
<PackageReference Include="xunit.core" Version="2.4.1" /> <PackageReference Include="xunit.extensibility.core" Version="2.4.2" />
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" /> <PackageReference Include="xunit.extensibility.execution" Version="2.4.2" />
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" /> <PackageReference Include="xunit.runner.console" Version="2.4.2" />
<PackageReference Include="xunit.runner.console" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" /> <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
</ItemGroup> </ItemGroup>

11
src/Avalonia.Base/Animation/KeySpline.cs

@ -79,15 +79,12 @@ namespace Avalonia.Animation
/// <param name="culture">culture of the string</param> /// <param name="culture">culture of the string</param>
/// <exception cref="FormatException">Thrown if the string does not have 4 values</exception> /// <exception cref="FormatException">Thrown if the string does not have 4 values</exception>
/// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns> /// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns>
public static KeySpline Parse(string value, CultureInfo culture) public static KeySpline Parse(string value, CultureInfo? culture)
{ {
if (culture is null) culture ??= CultureInfo.InvariantCulture;
culture = CultureInfo.InvariantCulture;
using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".")) using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".");
{ return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
}
} }
/// <summary> /// <summary>

38
src/Avalonia.Base/AvaloniaObject.cs

@ -152,7 +152,7 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess(); VerifyAccess();
_values?.ClearLocalValue(property); _values.ClearLocalValue(property);
} }
/// <summary> /// <summary>
@ -242,7 +242,14 @@ namespace Avalonia
return registered.InvokeGetter(this); return registered.InvokeGetter(this);
} }
/// <inheritdoc/> /// <summary>
/// Gets an <see cref="AvaloniaProperty"/> base value.
/// </summary>
/// <param name="property">The property.</param>
/// <remarks>
/// Gets the value of the property excluding animated values, otherwise <see cref="Optional{T}.Empty"/>.
/// Note that this method does not return property values that come from inherited or default values.
/// </remarks>
public Optional<T> GetBaseValue<T>(StyledProperty<T> property) public Optional<T> GetBaseValue<T>(StyledProperty<T> property)
{ {
_ = property ?? throw new ArgumentNullException(nameof(property)); _ = property ?? throw new ArgumentNullException(nameof(property));
@ -261,7 +268,7 @@ namespace Avalonia
VerifyAccess(); VerifyAccess();
return _values?.IsAnimating(property) ?? false; return _values.IsAnimating(property);
} }
/// <summary> /// <summary>
@ -279,7 +286,7 @@ namespace Avalonia
VerifyAccess(); VerifyAccess();
return _values?.IsSet(property) ?? false; return _values.IsSet(property);
} }
/// <summary> /// <summary>
@ -515,14 +522,12 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property); public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
/// <inheritdoc/>
internal void AddInheritanceChild(AvaloniaObject child) internal void AddInheritanceChild(AvaloniaObject child)
{ {
_inheritanceChildren ??= new List<AvaloniaObject>(); _inheritanceChildren ??= new List<AvaloniaObject>();
_inheritanceChildren.Add(child); _inheritanceChildren.Add(child);
} }
/// <inheritdoc/>
internal void RemoveInheritanceChild(AvaloniaObject child) internal void RemoveInheritanceChild(AvaloniaObject child)
{ {
_inheritanceChildren?.Remove(child); _inheritanceChildren?.Remove(child);
@ -541,24 +546,11 @@ namespace Avalonia
return new AvaloniaPropertyValue( return new AvaloniaPropertyValue(
property, property,
GetValue(property), GetValue(property),
BindingPriority.Unset, BindingPriority.LocalValue,
"Local Value"); null);
}
else if (_values != null)
{
var result = _values.GetDiagnostic(property);
if (result != null)
{
return result;
}
} }
return new AvaloniaPropertyValue( return _values.GetDiagnostic(property);
property,
GetValue(property),
BindingPriority.Unset,
"Unset");
} }
internal ValueStore GetValueStore() => _values; internal ValueStore GetValueStore() => _values;

2
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -30,7 +30,7 @@ namespace Avalonia.Data.Converters
{ {
if (value == null) if (value == null)
{ {
return targetType.IsValueType ? AvaloniaProperty.UnsetValue : null; return null;
} }
if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)

3
src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs

@ -1,6 +1,3 @@
using System;
using Avalonia.Data;
namespace Avalonia.Diagnostics namespace Avalonia.Diagnostics
{ {
/// <summary> /// <summary>

18
src/Avalonia.Base/Input/DragEventArgs.cs

@ -1,36 +1,28 @@
using System; using System;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
{ {
public class DragEventArgs : RoutedEventArgs public class DragEventArgs : RoutedEventArgs
{ {
private Interactive _target; private readonly Interactive _target;
private Point _targetLocation; private readonly Point _targetLocation;
public DragDropEffects DragEffects { get; set; } public DragDropEffects DragEffects { get; set; }
public IDataObject Data { get; private set; } public IDataObject Data { get; }
public KeyModifiers KeyModifiers { get; private set; } public KeyModifiers KeyModifiers { get; }
public Point GetPosition(Visual relativeTo) public Point GetPosition(Visual relativeTo)
{ {
var point = new Point(0, 0);
if (relativeTo == null) if (relativeTo == null)
{ {
throw new ArgumentNullException(nameof(relativeTo)); throw new ArgumentNullException(nameof(relativeTo));
} }
if (_target != null) return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0);
{
point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point;
}
return point;
} }
[Unstable] [Unstable]

2
src/Avalonia.Base/Input/KeyGesture.cs

@ -136,7 +136,7 @@ namespace Avalonia.Input
return StringBuilderCache.GetStringAndRelease(s); return StringBuilderCache.GetStringAndRelease(s);
} }
public bool Matches(KeyEventArgs keyEvent) => public bool Matches(KeyEventArgs? keyEvent) =>
keyEvent != null && keyEvent != null &&
keyEvent.KeyModifiers == KeyModifiers && keyEvent.KeyModifiers == KeyModifiers &&
ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key); ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key);

44
src/Avalonia.Base/Input/KeyboardNavigationHandler.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Input.Navigation; using Avalonia.Input.Navigation;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -51,7 +50,7 @@ namespace Avalonia.Input
// If there's a custom keyboard navigation handler as an ancestor, use that. // If there's a custom keyboard navigation handler as an ancestor, use that.
var custom = (element as Visual)?.FindAncestorOfType<ICustomKeyboardNavigation>(true); var custom = (element as Visual)?.FindAncestorOfType<ICustomKeyboardNavigation>(true);
if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce)) if (custom is not null && HandlePreCustomNavigation(custom, element, direction, out var ce))
return ce; return ce;
var result = direction switch var result = direction switch
@ -117,32 +116,27 @@ namespace Avalonia.Input
NavigationDirection direction, NavigationDirection direction,
[NotNullWhen(true)] out IInputElement? result) [NotNullWhen(true)] out IInputElement? result)
{ {
if (customHandler != null) var (handled, next) = customHandler.GetNext(element, direction);
if (handled)
{ {
var (handled, next) = customHandler.GetNext(element, direction); if (next is not null)
{
result = next;
return true;
}
if (handled) var r = direction switch
{ {
if (next != null) NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler),
{ NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler),
result = next; _ => null
return true; };
}
else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) if (r is not null)
{ {
var r = direction switch result = r;
{ return true;
NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler),
NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler),
_ => throw new NotSupportedException(),
};
if (r is object)
{
result = r;
return true;
}
}
} }
} }

48
src/Avalonia.Base/Input/Navigation/TabNavigation.cs

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Input.Navigation namespace Avalonia.Input.Navigation
@ -54,8 +52,7 @@ namespace Avalonia.Input.Navigation
// Avoid the endless loop here for Cycle groups // Avoid the endless loop here for Cycle groups
if (loopStartElement == nextTabElement) if (loopStartElement == nextTabElement)
break; break;
if (loopStartElement == null) loopStartElement ??= nextTabElement;
loopStartElement = nextTabElement;
var firstTabElementInside = GetNextTab(null, nextTabElement, true); var firstTabElementInside = GetNextTab(null, nextTabElement, true);
if (firstTabElementInside != null) if (firstTabElementInside != null)
@ -80,12 +77,9 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e) public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e)
{ {
if (e is IInputElement container) if (e is IInputElement container && GetLastInTree(container) is { } last)
{ {
var last = GetLastInTree(container); return GetNextTab(last, false);
if (last is object)
return GetNextTab(last, false);
} }
return null; return null;
@ -93,11 +87,8 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly) public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly)
{ {
if (e is null && container is null) container ??=
throw new InvalidOperationException("Either 'e' or 'container' must be non-null."); GetGroupParent(e ?? throw new InvalidOperationException("Either 'e' or 'container' must be non-null."));
if (container is null)
container = GetGroupParent(e!);
KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container); KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container);
@ -163,8 +154,7 @@ namespace Avalonia.Input.Navigation
// Avoid the endless loop here // Avoid the endless loop here
if (loopStartElement == nextTabElement) if (loopStartElement == nextTabElement)
break; break;
if (loopStartElement == null) loopStartElement ??= nextTabElement;
loopStartElement = nextTabElement;
// At this point nextTabElement is TabGroup // At this point nextTabElement is TabGroup
var lastTabElementInside = GetPrevTab(null, nextTabElement, true); var lastTabElementInside = GetPrevTab(null, nextTabElement, true);
@ -189,22 +179,18 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e) public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e)
{ {
if (e is IInputElement container) if (e is IInputElement container && GetFirstChild(container) is { } first)
{ {
var first = GetFirstChild(container); return GetPrevTab(first, null, false);
if (first is object)
return GetPrevTab(first, null, false);
} }
return null; return null;
} }
private static IInputElement? FocusedElement(IInputElement e) private static IInputElement? FocusedElement(IInputElement? e)
{ {
var iie = e;
// Focus delegation is enabled only if keyboard focus is outside the container // Focus delegation is enabled only if keyboard focus is outside the container
if (iie != null && !iie.IsKeyboardFocusWithin) if (e != null && !e.IsKeyboardFocusWithin)
{ {
var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e); var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e);
if (focusedElement != null) if (focusedElement != null)
@ -229,13 +215,11 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetFirstChild(IInputElement e) private static IInputElement? GetFirstChild(IInputElement e)
{ {
// If the element has a FocusedElement it should be its first child // If the element has a FocusedElement it should be its first child
if (FocusedElement(e) is IInputElement focusedElement) if (FocusedElement(e) is { } focusedElement)
return focusedElement; return focusedElement;
// Return the first visible element. // Return the first visible element.
var uiElement = e as InputElement; if (e is not InputElement uiElement || IsVisibleAndEnabled(uiElement))
if (uiElement is null || IsVisibleAndEnabled(uiElement))
{ {
if (e is Visual elementAsVisual) if (e is Visual elementAsVisual)
{ {
@ -265,7 +249,7 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetLastChild(IInputElement e) private static IInputElement? GetLastChild(IInputElement e)
{ {
// If the element has a FocusedElement it should be its last child // If the element has a FocusedElement it should be its last child
if (FocusedElement(e) is IInputElement focusedElement) if (FocusedElement(e) is { } focusedElement)
return focusedElement; return focusedElement;
// Return the last visible element. // Return the last visible element.
@ -273,9 +257,7 @@ namespace Avalonia.Input.Navigation
if (uiElement == null || IsVisibleAndEnabled(uiElement)) if (uiElement == null || IsVisibleAndEnabled(uiElement))
{ {
var elementAsVisual = e as Visual; if (e is Visual elementAsVisual)
if (elementAsVisual != null)
{ {
var children = elementAsVisual.VisualChildren; var children = elementAsVisual.VisualChildren;
var count = children.Count; var count = children.Count;
@ -322,7 +304,7 @@ namespace Avalonia.Input.Navigation
return firstTabElement; return firstTabElement;
} }
private static IInputElement? GetLastInTree(IInputElement container) private static IInputElement GetLastInTree(IInputElement container)
{ {
IInputElement? result; IInputElement? result;
IInputElement? c = container; IInputElement? c = container;

6
src/Avalonia.Base/Input/Platform/IClipboard.cs

@ -6,9 +6,9 @@ namespace Avalonia.Input.Platform
[NotClientImplementable] [NotClientImplementable]
public interface IClipboard public interface IClipboard
{ {
Task<string> GetTextAsync(); Task<string?> GetTextAsync();
Task SetTextAsync(string text); Task SetTextAsync(string? text);
Task ClearAsync(); Task ClearAsync();
@ -16,6 +16,6 @@ namespace Avalonia.Input.Platform
Task<string[]> GetFormatsAsync(); Task<string[]> GetFormatsAsync();
Task<object> GetDataAsync(string format); Task<object?> GetDataAsync(string format);
} }
} }

6
src/Avalonia.Base/LogicalTree/LogicalExtensions.cs

@ -48,7 +48,7 @@ namespace Avalonia.LogicalTree
/// <param name="logical">The logical.</param> /// <param name="logical">The logical.</param>
/// <param name="includeSelf">If given logical should be included in search.</param> /// <param name="includeSelf">If given logical should be included in search.</param>
/// <returns>First ancestor of given type.</returns> /// <returns>First ancestor of given type.</returns>
public static T? FindLogicalAncestorOfType<T>(this ILogical logical, bool includeSelf = false) where T : class public static T? FindLogicalAncestorOfType<T>(this ILogical? logical, bool includeSelf = false) where T : class
{ {
if (logical is null) if (logical is null)
{ {
@ -120,7 +120,7 @@ namespace Avalonia.LogicalTree
/// <param name="logical">The logical.</param> /// <param name="logical">The logical.</param>
/// <param name="includeSelf">If given logical should be included in search.</param> /// <param name="includeSelf">If given logical should be included in search.</param>
/// <returns>First descendant of given type.</returns> /// <returns>First descendant of given type.</returns>
public static T? FindLogicalDescendantOfType<T>(this ILogical logical, bool includeSelf = false) where T : class public static T? FindLogicalDescendantOfType<T>(this ILogical? logical, bool includeSelf = false) where T : class
{ {
if (logical is null) if (logical is null)
{ {
@ -185,7 +185,7 @@ namespace Avalonia.LogicalTree
/// True if <paramref name="logical"/> is an ancestor of <paramref name="target"/>; /// True if <paramref name="logical"/> is an ancestor of <paramref name="target"/>;
/// otherwise false. /// otherwise false.
/// </returns> /// </returns>
public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target) public static bool IsLogicalAncestorOf(this ILogical? logical, ILogical? target)
{ {
var current = target?.LogicalParent; var current = target?.LogicalParent;

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

@ -147,16 +147,11 @@ namespace Avalonia.Media
/// <param name="s">The color string.</param> /// <param name="s">The color string.</param>
/// <param name="color">The parsed color</param> /// <param name="color">The parsed color</param>
/// <returns>The status of the operation.</returns> /// <returns>The status of the operation.</returns>
public static bool TryParse(string s, out Color color) public static bool TryParse(string? s, out Color color)
{ {
color = default; color = default;
if (s is null) if (string.IsNullOrEmpty(s))
{
return false;
}
if (s.Length == 0)
{ {
return false; return false;
} }
@ -336,7 +331,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Parses the given string representing a CSS color value into a new <see cref="Color"/>. /// Parses the given string representing a CSS color value into a new <see cref="Color"/>.
/// </summary> /// </summary>
private static bool TryParseCssFormat(string s, out Color color) private static bool TryParseCssFormat(string? s, out Color color)
{ {
bool prefixMatched = false; bool prefixMatched = false;

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

@ -240,7 +240,7 @@ namespace Avalonia.Media
/// </summary> /// </summary>
/// <param name="foreground">The foreground brush.</param> /// <param name="foreground">The foreground brush.</param>
/// <param name="glyphRun">The glyph run.</param> /// <param name="glyphRun">The glyph run.</param>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) public void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun)
{ {
_ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun)); _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun));

26
src/Avalonia.Base/Media/DrawingGroup.cs

@ -13,14 +13,14 @@ namespace Avalonia.Media
public static readonly StyledProperty<double> OpacityProperty = public static readonly StyledProperty<double> OpacityProperty =
AvaloniaProperty.Register<DrawingGroup, double>(nameof(Opacity), 1); AvaloniaProperty.Register<DrawingGroup, double>(nameof(Opacity), 1);
public static readonly StyledProperty<Transform> TransformProperty = public static readonly StyledProperty<Transform?> TransformProperty =
AvaloniaProperty.Register<DrawingGroup, Transform>(nameof(Transform)); AvaloniaProperty.Register<DrawingGroup, Transform?>(nameof(Transform));
public static readonly StyledProperty<Geometry> ClipGeometryProperty = public static readonly StyledProperty<Geometry?> ClipGeometryProperty =
AvaloniaProperty.Register<DrawingGroup, Geometry>(nameof(ClipGeometry)); AvaloniaProperty.Register<DrawingGroup, Geometry?>(nameof(ClipGeometry));
public static readonly StyledProperty<IBrush> OpacityMaskProperty = public static readonly StyledProperty<IBrush?> OpacityMaskProperty =
AvaloniaProperty.Register<DrawingGroup, IBrush>(nameof(OpacityMask)); AvaloniaProperty.Register<DrawingGroup, IBrush?>(nameof(OpacityMask));
public static readonly DirectProperty<DrawingGroup, DrawingCollection> ChildrenProperty = public static readonly DirectProperty<DrawingGroup, DrawingCollection> ChildrenProperty =
AvaloniaProperty.RegisterDirect<DrawingGroup, DrawingCollection>( AvaloniaProperty.RegisterDirect<DrawingGroup, DrawingCollection>(
@ -36,19 +36,19 @@ namespace Avalonia.Media
set => SetValue(OpacityProperty, value); set => SetValue(OpacityProperty, value);
} }
public Transform Transform public Transform? Transform
{ {
get => GetValue(TransformProperty); get => GetValue(TransformProperty);
set => SetValue(TransformProperty, value); set => SetValue(TransformProperty, value);
} }
public Geometry ClipGeometry public Geometry? ClipGeometry
{ {
get => GetValue(ClipGeometryProperty); get => GetValue(ClipGeometryProperty);
set => SetValue(ClipGeometryProperty, value); set => SetValue(ClipGeometryProperty, value);
} }
public IBrush OpacityMask public IBrush? OpacityMask
{ {
get => GetValue(OpacityMaskProperty); get => GetValue(OpacityMaskProperty);
set => SetValue(OpacityMaskProperty, value); set => SetValue(OpacityMaskProperty, value);
@ -159,7 +159,7 @@ namespace Avalonia.Media
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry) public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
{ {
if (((brush == null) && (pen == null)) || (geometry == null)) if ((brush == null) && (pen == null))
{ {
return; return;
} }
@ -167,9 +167,9 @@ namespace Avalonia.Media
AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry)); AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
} }
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun) public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
{ {
if (foreground == null || glyphRun == null) if (foreground == null)
{ {
return; return;
} }
@ -184,7 +184,7 @@ namespace Avalonia.Media
AddDrawing(glyphRunDrawing); AddDrawing(glyphRunDrawing);
} }
public void DrawLine(IPen pen, Point p1, Point p2) public void DrawLine(IPen? pen, Point p1, Point p2)
{ {
if (pen == null) if (pen == null)
{ {

6
src/Avalonia.Base/Media/DrawingImage.cs

@ -20,8 +20,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Defines the <see cref="Drawing"/> property. /// Defines the <see cref="Drawing"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Drawing> DrawingProperty = public static readonly StyledProperty<Drawing?> DrawingProperty =
AvaloniaProperty.Register<DrawingImage, Drawing>(nameof(Drawing)); AvaloniaProperty.Register<DrawingImage, Drawing?>(nameof(Drawing));
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler? Invalidated; public event EventHandler? Invalidated;
@ -30,7 +30,7 @@ namespace Avalonia.Media
/// Gets or sets the drawing content. /// Gets or sets the drawing content.
/// </summary> /// </summary>
[Content] [Content]
public Drawing Drawing public Drawing? Drawing
{ {
get => GetValue(DrawingProperty); get => GetValue(DrawingProperty);
set => SetValue(DrawingProperty, value); set => SetValue(DrawingProperty, value);

4
src/Avalonia.Base/Media/FontFamily.cs

@ -119,7 +119,7 @@ namespace Avalonia.Media
case 2: case 2:
{ {
var source = segments[0].StartsWith("/") var source = segments[0].StartsWith("/", StringComparison.Ordinal)
? new Uri(segments[0], UriKind.Relative) ? new Uri(segments[0], UriKind.Relative)
: new Uri(segments[0], UriKind.RelativeOrAbsolute); : new Uri(segments[0], UriKind.RelativeOrAbsolute);
@ -188,7 +188,7 @@ namespace Avalonia.Media
{ {
unchecked unchecked
{ {
return ((FamilyNames != null ? FamilyNames.GetHashCode() : 0) * 397) ^ (Key != null ? Key.GetHashCode() : 0); return (FamilyNames.GetHashCode() * 397) ^ (Key is not null ? Key.GetHashCode() : 0);
} }
} }

5
src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs

@ -41,10 +41,7 @@ namespace Avalonia.Media.Fonts
{ {
var hash = (int)2166136261; var hash = (int)2166136261;
if (Source != null) hash = (hash * 16777619) ^ Source.GetHashCode();
{
hash = (hash * 16777619) ^ Source.GetHashCode();
}
if (BaseUri != null) if (BaseUri != null)
{ {

6
src/Avalonia.Base/Media/FormattedText.cs

@ -1354,7 +1354,7 @@ namespace Avalonia.Media
{ {
var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0); var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0);
if (highlightBounds != null) if (highlightBounds.Count > 0)
{ {
foreach (var bound in highlightBounds) foreach (var bound in highlightBounds)
{ {
@ -1365,7 +1365,7 @@ namespace Avalonia.Media
// Convert logical units (which extend leftward from the right edge // Convert logical units (which extend leftward from the right edge
// of the paragraph) to physical units. // of the paragraph) to physical units.
// //
// Note that since rect is in logical units, rect.Right corresponds to // Note that since rect is in logical units, rect.Right corresponds to
// the visual *left* edge of the rectangle in the RTL case. Specifically, // the visual *left* edge of the rectangle in the RTL case. Specifically,
// is the distance leftward from the right edge of the formatting rectangle // is the distance leftward from the right edge of the formatting rectangle
// whose width is the paragraph width passed to FormatLine. // whose width is the paragraph width passed to FormatLine.
@ -1384,7 +1384,7 @@ namespace Avalonia.Media
else else
{ {
accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union); accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union);
} }
} }
} }
} }

6
src/Avalonia.Base/Media/GeometryDrawing.cs

@ -15,8 +15,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Defines the <see cref="Geometry"/> property. /// Defines the <see cref="Geometry"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Geometry> GeometryProperty = public static readonly StyledProperty<Geometry?> GeometryProperty =
AvaloniaProperty.Register<GeometryDrawing, Geometry>(nameof(Geometry)); AvaloniaProperty.Register<GeometryDrawing, Geometry?>(nameof(Geometry));
/// <summary> /// <summary>
/// Defines the <see cref="Brush"/> property. /// Defines the <see cref="Brush"/> property.
@ -34,7 +34,7 @@ namespace Avalonia.Media
/// Gets or sets the <see cref="Avalonia.Media.Geometry"/> that describes the shape of this <see cref="GeometryDrawing"/>. /// Gets or sets the <see cref="Avalonia.Media.Geometry"/> that describes the shape of this <see cref="GeometryDrawing"/>.
/// </summary> /// </summary>
[Content] [Content]
public Geometry Geometry public Geometry? Geometry
{ {
get => GetValue(GeometryProperty); get => GetValue(GeometryProperty);
set => SetValue(GeometryProperty, value); set => SetValue(GeometryProperty, value);

12
src/Avalonia.Base/Media/GlyphRunDrawing.cs

@ -2,19 +2,19 @@
{ {
public class GlyphRunDrawing : Drawing public class GlyphRunDrawing : Drawing
{ {
public static readonly StyledProperty<IBrush> ForegroundProperty = public static readonly StyledProperty<IBrush?> ForegroundProperty =
AvaloniaProperty.Register<GlyphRunDrawing, IBrush>(nameof(Foreground)); AvaloniaProperty.Register<GlyphRunDrawing, IBrush?>(nameof(Foreground));
public static readonly StyledProperty<GlyphRun> GlyphRunProperty = public static readonly StyledProperty<GlyphRun?> GlyphRunProperty =
AvaloniaProperty.Register<GlyphRunDrawing, GlyphRun>(nameof(GlyphRun)); AvaloniaProperty.Register<GlyphRunDrawing, GlyphRun?>(nameof(GlyphRun));
public IBrush Foreground public IBrush? Foreground
{ {
get => GetValue(ForegroundProperty); get => GetValue(ForegroundProperty);
set => SetValue(ForegroundProperty, value); set => SetValue(ForegroundProperty, value);
} }
public GlyphRun GlyphRun public GlyphRun? GlyphRun
{ {
get => GetValue(GlyphRunProperty); get => GetValue(GlyphRunProperty);
set => SetValue(GlyphRunProperty, value); set => SetValue(GlyphRunProperty, value);

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

@ -254,7 +254,7 @@ namespace Avalonia.Media
/// <param name="s">The HSL color string to parse.</param> /// <param name="s">The HSL color string to parse.</param>
/// <param name="hslColor">The parsed <see cref="HslColor"/>.</param> /// <param name="hslColor">The parsed <see cref="HslColor"/>.</param>
/// <returns>True if parsing was successful; otherwise, false.</returns> /// <returns>True if parsing was successful; otherwise, false.</returns>
public static bool TryParse(string s, out HslColor hslColor) public static bool TryParse(string? s, out HslColor hslColor)
{ {
bool prefixMatched = false; bool prefixMatched = false;

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

@ -254,7 +254,7 @@ namespace Avalonia.Media
/// <param name="s">The HSV color string to parse.</param> /// <param name="s">The HSV color string to parse.</param>
/// <param name="hsvColor">The parsed <see cref="HsvColor"/>.</param> /// <param name="hsvColor">The parsed <see cref="HsvColor"/>.</param>
/// <returns>True if parsing was successful; otherwise, false.</returns> /// <returns>True if parsing was successful; otherwise, false.</returns>
public static bool TryParse(string s, out HsvColor hsvColor) public static bool TryParse(string? s, out HsvColor hsvColor)
{ {
bool prefixMatched = false; bool prefixMatched = false;

3
src/Avalonia.Base/Media/IVisualBrush.cs

@ -1,5 +1,4 @@
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -12,6 +11,6 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets the visual to draw. /// Gets the visual to draw.
/// </summary> /// </summary>
Visual Visual { get; } Visual? Visual { get; }
} }
} }

24
src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs

@ -39,17 +39,8 @@ namespace Avalonia.Media.Immutable
{ {
return true; return true;
} }
else if (other is null)
{
return false;
}
if (Offset != other.Offset) return other is not null && Offset == other.Offset && SequenceEqual(_dashes, other.Dashes);
{
return false;
}
return SequenceEqual(Dashes, other.Dashes);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -58,30 +49,27 @@ namespace Avalonia.Media.Immutable
var hashCode = 717868523; var hashCode = 717868523;
hashCode = hashCode * -1521134295 + Offset.GetHashCode(); hashCode = hashCode * -1521134295 + Offset.GetHashCode();
if (_dashes != null) foreach (var i in _dashes)
{ {
foreach (var i in _dashes) hashCode = hashCode * -1521134295 + i.GetHashCode();
{
hashCode = hashCode * -1521134295 + i.GetHashCode();
}
} }
return hashCode; return hashCode;
} }
private static bool SequenceEqual(IReadOnlyList<double> left, IReadOnlyList<double>? right) private static bool SequenceEqual(double[] left, IReadOnlyList<double>? right)
{ {
if (ReferenceEquals(left, right)) if (ReferenceEquals(left, right))
{ {
return true; return true;
} }
if (left == null || right == null || left.Count != right.Count) if (right is null || left.Length != right.Count)
{ {
return false; return false;
} }
for (var c = 0; c < left.Count; c++) for (var c = 0; c < left.Length; c++)
{ {
if (left[c] != right[c]) if (left[c] != right[c])
{ {

7
src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs

@ -1,5 +1,4 @@
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.VisualTree;
namespace Avalonia.Media.Immutable namespace Avalonia.Media.Immutable
{ {
@ -31,11 +30,11 @@ namespace Avalonia.Media.Immutable
RelativeRect? destinationRect = null, RelativeRect? destinationRect = null,
double opacity = 1, double opacity = 1,
ImmutableTransform? transform = null, ImmutableTransform? transform = null,
RelativePoint transformOrigin = new RelativePoint(), RelativePoint transformOrigin = default,
RelativeRect? sourceRect = null, RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform, Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None, TileMode tileMode = TileMode.None,
Imaging.BitmapInterpolationMode bitmapInterpolationMode = Imaging.BitmapInterpolationMode.Default) BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
: base( : base(
alignmentX, alignmentX,
alignmentY, alignmentY,
@ -62,6 +61,6 @@ namespace Avalonia.Media.Immutable
} }
/// <inheritdoc/> /// <inheritdoc/>
public Visual Visual { get; } public Visual? Visual { get; }
} }
} }

14
src/Avalonia.Base/Media/TextDecoration.cs

@ -22,8 +22,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Defines the <see cref="Stroke"/> property. /// Defines the <see cref="Stroke"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IBrush> StrokeProperty = public static readonly StyledProperty<IBrush?> StrokeProperty =
AvaloniaProperty.Register<TextDecoration, IBrush>(nameof(Stroke)); AvaloniaProperty.Register<TextDecoration, IBrush?>(nameof(Stroke));
/// <summary> /// <summary>
/// Defines the <see cref="StrokeThicknessUnit"/> property. /// Defines the <see cref="StrokeThicknessUnit"/> property.
@ -34,8 +34,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Defines the <see cref="StrokeDashArray"/> property. /// Defines the <see cref="StrokeDashArray"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<AvaloniaList<double>> StrokeDashArrayProperty = public static readonly StyledProperty<AvaloniaList<double>?> StrokeDashArrayProperty =
AvaloniaProperty.Register<TextDecoration, AvaloniaList<double>>(nameof(StrokeDashArray)); AvaloniaProperty.Register<TextDecoration, AvaloniaList<double>?>(nameof(StrokeDashArray));
/// <summary> /// <summary>
/// Defines the <see cref="StrokeDashOffset"/> property. /// Defines the <see cref="StrokeDashOffset"/> property.
@ -82,7 +82,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets or sets the <see cref="IBrush"/> that specifies how the <see cref="TextDecoration"/> is painted. /// Gets or sets the <see cref="IBrush"/> that specifies how the <see cref="TextDecoration"/> is painted.
/// </summary> /// </summary>
public IBrush Stroke public IBrush? Stroke
{ {
get { return GetValue(StrokeProperty); } get { return GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); } set { SetValue(StrokeProperty, value); }
@ -101,7 +101,7 @@ namespace Avalonia.Media
/// Gets or sets a collection of <see cref="double"/> values that indicate the pattern of dashes and gaps /// Gets or sets a collection of <see cref="double"/> values that indicate the pattern of dashes and gaps
/// that is used to draw the <see cref="TextDecoration"/>. /// that is used to draw the <see cref="TextDecoration"/>.
/// </summary> /// </summary>
public AvaloniaList<double> StrokeDashArray public AvaloniaList<double>? StrokeDashArray
{ {
get { return GetValue(StrokeDashArrayProperty); } get { return GetValue(StrokeDashArrayProperty); }
set { SetValue(StrokeDashArrayProperty, value); } set { SetValue(StrokeDashArrayProperty, value); }
@ -220,7 +220,7 @@ namespace Avalonia.Media
var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY)); var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY));
if (intersections != null && intersections.Count > 0) if (intersections.Count > 0)
{ {
var last = baselineOrigin.X; var last = baselineOrigin.X;
var finalPos = last + glyphRun.Size.Width; var finalPos = last + glyphRun.Size.Width;

7
src/Avalonia.Base/Media/VisualBrush.cs

@ -1,5 +1,4 @@
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
using Avalonia.VisualTree;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -11,8 +10,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Defines the <see cref="Visual"/> property. /// Defines the <see cref="Visual"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Visual> VisualProperty = public static readonly StyledProperty<Visual?> VisualProperty =
AvaloniaProperty.Register<VisualBrush, Visual>(nameof(Visual)); AvaloniaProperty.Register<VisualBrush, Visual?>(nameof(Visual));
static VisualBrush() static VisualBrush()
{ {
@ -38,7 +37,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets or sets the visual to draw. /// Gets or sets the visual to draw.
/// </summary> /// </summary>
public Visual Visual public Visual? Visual
{ {
get { return GetValue(VisualProperty); } get { return GetValue(VisualProperty); }
set { SetValue(VisualProperty, value); } set { SetValue(VisualProperty, value); }

4
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -49,7 +49,7 @@ namespace Avalonia.Platform
/// <param name="pen">The stroke pen.</param> /// <param name="pen">The stroke pen.</param>
/// <param name="p1">The first point of the line.</param> /// <param name="p1">The first point of the line.</param>
/// <param name="p2">The second point of the line.</param> /// <param name="p2">The second point of the line.</param>
void DrawLine(IPen pen, Point p1, Point p2); void DrawLine(IPen? pen, Point p1, Point p2);
/// <summary> /// <summary>
/// Draws a geometry. /// Draws a geometry.
@ -91,7 +91,7 @@ namespace Avalonia.Platform
/// </summary> /// </summary>
/// <param name="foreground">The foreground.</param> /// <param name="foreground">The foreground.</param>
/// <param name="glyphRun">The glyph run.</param> /// <param name="glyphRun">The glyph run.</param>
void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun); void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun);
/// <summary> /// <summary>
/// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer /// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer

33
src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -18,26 +19,21 @@ internal class AssemblyDescriptor : IAssemblyDescriptor
{ {
public AssemblyDescriptor(Assembly assembly) public AssemblyDescriptor(Assembly assembly)
{ {
Assembly = assembly; Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
Resources = assembly.GetManifestResourceNames()
.ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
Name = assembly.GetName().Name;
if (assembly != null) using var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName);
if (resources != null)
{ {
Resources = assembly.GetManifestResourceNames() Resources.Remove(Constants.AvaloniaResourceName);
.ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
Name = assembly.GetName().Name;
using (var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName))
{
if (resources != null)
{
Resources.Remove(Constants.AvaloniaResourceName);
var indexLength = new BinaryReader(resources).ReadInt32(); var indexLength = new BinaryReader(resources).ReadInt32();
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength)); var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
var baseOffset = indexLength + 4; var baseOffset = indexLength + 4;
AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor) AvaloniaResources = index.ToDictionary(GetPathRooted, r => (IAssetDescriptor)
new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size)); new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
}
}
} }
} }
@ -45,6 +41,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor
public Dictionary<string, IAssetDescriptor>? Resources { get; } public Dictionary<string, IAssetDescriptor>? Resources { get; }
public Dictionary<string, IAssetDescriptor>? AvaloniaResources { get; } public Dictionary<string, IAssetDescriptor>? AvaloniaResources { get; }
public string? Name { get; } public string? Name { get; }
private static string GetPathRooted(AvaloniaResourcesIndexEntry r) => private static string GetPathRooted(AvaloniaResourcesIndexEntry r) =>
r.Path![0] == '/' ? r.Path : '/' + r.Path; r.Path![0] == '/' ? r.Path : '/' + r.Path;
} }

2
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs

@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations
public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase
{ {
private readonly CompositionPropertySet _propertySet; private readonly CompositionPropertySet _propertySet;
internal CompositionAnimation(Compositor compositor) : base(compositor, null!) internal CompositionAnimation(Compositor compositor) : base(compositor, null)
{ {
_propertySet = new CompositionPropertySet(compositor); _propertySet = new CompositionPropertySet(compositor);
} }

2
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs

@ -19,7 +19,7 @@ namespace Avalonia.Rendering.Composition.Animations
public void Remove(CompositionAnimation value) => Animations.Remove(value); public void Remove(CompositionAnimation value) => Animations.Remove(value);
public void RemoveAll() => Animations.Clear(); public void RemoveAll() => Animations.Clear();
public CompositionAnimationGroup(Compositor compositor) : base(compositor, null!) public CompositionAnimationGroup(Compositor compositor) : base(compositor, null)
{ {
} }
} }

2
src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs

@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations
{ {
private Dictionary<string, ICompositionAnimationBase> _inner = new Dictionary<string, ICompositionAnimationBase>(); private Dictionary<string, ICompositionAnimationBase> _inner = new Dictionary<string, ICompositionAnimationBase>();
private IDictionary<string, ICompositionAnimationBase> _innerface; private IDictionary<string, ICompositionAnimationBase> _innerface;
internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null!) internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null)
{ {
_innerface = _inner; _innerface = _inner;
} }

82
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -20,15 +20,17 @@ public class CompositingRenderer : IRendererWithCompositor
{ {
private readonly IRenderRoot _root; private readonly IRenderRoot _root;
private readonly Compositor _compositor; private readonly Compositor _compositor;
CompositionDrawingContext _recorder = new(); private readonly CompositionDrawingContext _recorder = new();
DrawingContext _recordingContext; private readonly DrawingContext _recordingContext;
private HashSet<Visual> _dirty = new(); private readonly HashSet<Visual> _dirty = new();
private HashSet<Visual> _recalculateChildren = new(); private readonly HashSet<Visual> _recalculateChildren = new();
private readonly Action _update;
private bool _queuedUpdate; private bool _queuedUpdate;
private Action _update;
private bool _updating; private bool _updating;
private bool _isDisposed;
internal CompositionTarget CompositionTarget; internal CompositionTarget CompositionTarget { get; }
/// <summary> /// <summary>
/// Asks the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered. /// Asks the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered.
@ -38,6 +40,17 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/> /// <inheritdoc/>
public RendererDiagnostics Diagnostics { get; } public RendererDiagnostics Diagnostics { get; }
/// <inheritdoc />
public Compositor Compositor => _compositor;
/// <summary>
/// Initializes a new instance of <see cref="CompositingRenderer"/>
/// </summary>
/// <param name="root">The render root using this renderer.</param>
/// <param name="compositor">The associated compositors.</param>
/// <param name="surfaces">
/// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems.
/// </param>
public CompositingRenderer(IRenderRoot root, Compositor compositor, Func<IEnumerable<object>> surfaces) public CompositingRenderer(IRenderRoot root, Compositor compositor, Func<IEnumerable<object>> surfaces)
{ {
_root = root; _root = root;
@ -66,7 +79,7 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated; public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;
void QueueUpdate() private void QueueUpdate()
{ {
if(_queuedUpdate) if(_queuedUpdate)
return; return;
@ -77,9 +90,11 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/> /// <inheritdoc/>
public void AddDirty(Visual visual) public void AddDirty(Visual visual)
{ {
if (_isDisposed)
return;
if (_updating) if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass"); throw new InvalidOperationException("Visual was invalidated during the render pass");
_dirty.Add((Visual)visual); _dirty.Add(visual);
QueueUpdate(); QueueUpdate();
} }
@ -126,9 +141,11 @@ public class CompositingRenderer : IRendererWithCompositor
/// <inheritdoc/> /// <inheritdoc/>
public void RecalculateChildren(Visual visual) public void RecalculateChildren(Visual visual)
{ {
if (_isDisposed)
return;
if (_updating) if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass"); throw new InvalidOperationException("Visual was invalidated during the render pass");
_recalculateChildren.Add((Visual)visual); _recalculateChildren.Add(visual);
QueueUpdate(); QueueUpdate();
} }
@ -171,7 +188,7 @@ public class CompositingRenderer : IRendererWithCompositor
if (sortedChildren != null) if (sortedChildren != null)
for (var c = 0; c < visualChildren.Count; c++) for (var c = 0; c < visualChildren.Count; c++)
{ {
if (!ReferenceEquals(compositionChildren[c], ((Visual)sortedChildren[c].visual).CompositionVisual)) if (!ReferenceEquals(compositionChildren[c], sortedChildren[c].visual.CompositionVisual))
{ {
mismatch = true; mismatch = true;
break; break;
@ -179,7 +196,7 @@ public class CompositingRenderer : IRendererWithCompositor
} }
else else
for (var c = 0; c < visualChildren.Count; c++) for (var c = 0; c < visualChildren.Count; c++)
if (!ReferenceEquals(compositionChildren[c], ((Visual)visualChildren[c]).CompositionVisual)) if (!ReferenceEquals(compositionChildren[c], visualChildren[c].CompositionVisual))
{ {
mismatch = true; mismatch = true;
break; break;
@ -201,7 +218,7 @@ public class CompositingRenderer : IRendererWithCompositor
{ {
foreach (var ch in sortedChildren) foreach (var ch in sortedChildren)
{ {
var compositionChild = ((Visual)ch.visual).CompositionVisual; var compositionChild = ch.visual.CompositionVisual;
if (compositionChild != null) if (compositionChild != null)
compositionChildren.Add(compositionChild); compositionChildren.Add(compositionChild);
} }
@ -210,7 +227,7 @@ public class CompositingRenderer : IRendererWithCompositor
else else
foreach (var ch in v.GetVisualChildren()) foreach (var ch in v.GetVisualChildren())
{ {
var compositionChild = ((Visual)ch).CompositionVisual; var compositionChild = ch.CompositionVisual;
if (compositionChild != null) if (compositionChild != null)
compositionChildren.Add(compositionChild); compositionChildren.Add(compositionChild);
} }
@ -289,13 +306,18 @@ public class CompositingRenderer : IRendererWithCompositor
_updating = false; _updating = false;
} }
} }
/// <inheritdoc />
public void Resized(Size size) public void Resized(Size size)
{ {
} }
/// <inheritdoc />
public void Paint(Rect rect) public void Paint(Rect rect)
{ {
if (_isDisposed)
return;
QueueUpdate(); QueueUpdate();
CompositionTarget.RequestRedraw(); CompositionTarget.RequestRedraw();
if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground) if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground)
@ -304,17 +326,34 @@ public class CompositingRenderer : IRendererWithCompositor
CompositionTarget.ImmediateUIThreadRender(); CompositionTarget.ImmediateUIThreadRender();
} }
public void Start() => CompositionTarget.IsEnabled = true; /// <inheritdoc />
public void Start()
public void Stop()
{ {
CompositionTarget.IsEnabled = false; if (_isDisposed)
return;
CompositionTarget.IsEnabled = true;
} }
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType) => Compositor.TryGetRenderInterfaceFeature(featureType); /// <inheritdoc />
public void Stop()
=> CompositionTarget.IsEnabled = false;
/// <inheritdoc />
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
=> Compositor.TryGetRenderInterfaceFeature(featureType);
/// <inheritdoc />
public void Dispose() public void Dispose()
{ {
if (_isDisposed)
return;
_isDisposed = true;
_dirty.Clear();
_recalculateChildren.Clear();
SceneInvalidated = null;
Stop(); Stop();
CompositionTarget.Dispose(); CompositionTarget.Dispose();
@ -323,9 +362,4 @@ public class CompositingRenderer : IRendererWithCompositor
if (Compositor.Loop.RunsInBackground) if (Compositor.Loop.RunsInBackground)
_compositor.Commit().Wait(); _compositor.Commit().Wait();
} }
/// <summary>
/// The associated <see cref="Avalonia.Rendering.Composition.Compositor"/> object
/// </summary>
public Compositor Compositor => _compositor;
} }

3
src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs

@ -1,4 +1,3 @@
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
using Avalonia.Threading; using Avalonia.Threading;
@ -7,7 +6,7 @@ namespace Avalonia.Rendering.Composition;
public class CompositionDrawingSurface : CompositionSurface public class CompositionDrawingSurface : CompositionSurface
{ {
internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server; internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server!;
internal CompositionDrawingSurface(Compositor compositor) : base(compositor, new ServerCompositionDrawingSurface(compositor.Server)) internal CompositionDrawingSurface(Compositor compositor) : base(compositor, new ServerCompositionDrawingSurface(compositor.Server))
{ {
} }

4
src/Avalonia.Base/Rendering/Composition/CompositionObject.cs

@ -22,7 +22,7 @@ namespace Avalonia.Rendering.Composition
public ImplicitAnimationCollection? ImplicitAnimations { get; set; } public ImplicitAnimationCollection? ImplicitAnimations { get; set; }
private protected InlineDictionary<CompositionProperty, IAnimationInstance> PendingAnimations; private protected InlineDictionary<CompositionProperty, IAnimationInstance> PendingAnimations;
internal CompositionObject(Compositor compositor, ServerObject server) internal CompositionObject(Compositor compositor, ServerObject? server)
{ {
Compositor = compositor; Compositor = compositor;
Server = server; Server = server;
@ -32,7 +32,7 @@ namespace Avalonia.Rendering.Composition
/// The associated Compositor /// The associated Compositor
/// </summary> /// </summary>
public Compositor Compositor { get; } public Compositor Compositor { get; }
internal ServerObject Server { get; } internal ServerObject? Server { get; }
public bool IsDisposed { get; private set; } public bool IsDisposed { get; private set; }
private bool _registeredForSerialization; private bool _registeredForSerialization;

2
src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs

@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition
private readonly Dictionary<string, ExpressionVariant> _variants = new Dictionary<string, ExpressionVariant>(); private readonly Dictionary<string, ExpressionVariant> _variants = new Dictionary<string, ExpressionVariant>();
private readonly Dictionary<string, CompositionObject> _objects = new Dictionary<string, CompositionObject>(); private readonly Dictionary<string, CompositionObject> _objects = new Dictionary<string, CompositionObject>();
internal CompositionPropertySet(Compositor compositor) : base(compositor, null!) internal CompositionPropertySet(Compositor compositor) : base(compositor, null)
{ {
} }

14
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@ -88,8 +88,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
} }
/// <inheritdoc/> /// <inheritdoc/>
public void DrawLine(IPen pen, Point p1, Point p2) public void DrawLine(IPen? pen, Point p1, Point p2)
{ {
if (pen is null)
{
return;
}
var next = NextDrawAs<LineNode>(); var next = NextDrawAs<LineNode>();
if (next == null || !next.Item.Equals(Transform, pen, p1, p2)) if (next == null || !next.Item.Equals(Transform, pen, p1, p2))
@ -159,8 +164,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
public object? GetFeature(Type t) => null; public object? GetFeature(Type t) => null;
/// <inheritdoc/> /// <inheritdoc/>
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun) public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
{ {
if (foreground is null)
{
return;
}
var next = NextDrawAs<GlyphRunNode>(); var next = NextDrawAs<GlyphRunNode>();
if (next == null || !next.Item.Equals(Transform, foreground, glyphRun)) if (next == null || !next.Item.Equals(Transform, foreground, glyphRun))

2
src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs

@ -165,8 +165,6 @@ namespace Avalonia.Rendering.Composition.Expressions
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
{ {
if (context.ForeignFunctionInterface == null)
return default;
var args = new List<ExpressionVariant>(); var args = new List<ExpressionVariant>();
foreach (var expr in Parameters) foreach (var expr in Parameters)
args.Add(expr.Evaluate(ref context)); args.Add(expr.Evaluate(ref context));

1
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Rendering.Composition.Server;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see> // Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>

4
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@ -66,7 +66,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect); _impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect);
} }
public void DrawLine(IPen pen, Point p1, Point p2) public void DrawLine(IPen? pen, Point p1, Point p2)
{ {
_impl.DrawLine(pen, p1, p2); _impl.DrawLine(pen, p1, p2);
} }
@ -86,7 +86,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_impl.DrawEllipse(brush, pen, rect); _impl.DrawEllipse(brush, pen, rect);
} }
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun) public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
{ {
_impl.DrawGlyphRun(foreground, glyphRun); _impl.DrawGlyphRun(foreground, glyphRun);
} }

2
src/Avalonia.Base/Rendering/IRenderRoot.cs

@ -1,6 +1,4 @@
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering namespace Avalonia.Rendering
{ {

3
src/Avalonia.Base/Rendering/IRenderer.cs

@ -90,6 +90,9 @@ namespace Avalonia.Rendering
public interface IRendererWithCompositor : IRenderer public interface IRendererWithCompositor : IRenderer
{ {
/// <summary>
/// The associated <see cref="Avalonia.Rendering.Composition.Compositor"/> object
/// </summary>
Compositor Compositor { get; } Compositor Compositor { get; }
} }
} }

6
src/Avalonia.Base/Rendering/ImmediateRenderer.cs

@ -48,8 +48,10 @@ namespace Avalonia.Rendering
/// <inheritdoc/> /// <inheritdoc/>
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
{ {
var visual = brush.Visual; if (brush.Visual is { } visual)
Render(new DrawingContext(context), visual, visual.Bounds); {
Render(new DrawingContext(context), visual, visual.Bounds);
}
} }
internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds) internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds)

7
src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs

@ -80,11 +80,8 @@ namespace Avalonia.Rendering.SceneGraph
{ {
p *= Transform.Invert(); p *= Transform.Invert();
if (Material != null) var rect = Rect.Rect;
{ return rect.ContainsExclusive(p);
var rect = Rect.Rect;
return rect.ContainsExclusive(p);
}
} }
return false; return false;

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

@ -212,7 +212,7 @@ namespace Avalonia.Utilities
var toTypeConverter = TypeDescriptor.GetConverter(toUnderl); var toTypeConverter = TypeDescriptor.GetConverter(toUnderl);
if (toTypeConverter.CanConvertFrom(from) == true) if (toTypeConverter.CanConvertFrom(from))
{ {
result = toTypeConverter.ConvertFrom(null, culture, value); result = toTypeConverter.ConvertFrom(null, culture, value);
return true; return true;
@ -220,7 +220,7 @@ namespace Avalonia.Utilities
var fromTypeConverter = TypeDescriptor.GetConverter(from); var fromTypeConverter = TypeDescriptor.GetConverter(from);
if (fromTypeConverter.CanConvertTo(toUnderl) == true) if (fromTypeConverter.CanConvertTo(toUnderl))
{ {
result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl); result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl);
return true; return true;
@ -329,7 +329,7 @@ namespace Avalonia.Utilities
} }
[RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
public static T ConvertImplicit<T>(object value) public static T ConvertImplicit<T>(object? value)
{ {
if (TryConvertImplicit(typeof(T), value, out var result)) if (TryConvertImplicit(typeof(T), value, out var result))
{ {
@ -369,11 +369,6 @@ namespace Avalonia.Utilities
/// </remarks> /// </remarks>
public static bool IsNumeric(Type type) public static bool IsNumeric(Type type)
{ {
if (type == null)
{
return false;
}
var underlyingType = Nullable.GetUnderlyingType(type); var underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null) if (underlyingType != null)

68
src/Avalonia.Base/Utilities/WeakEvent.cs

@ -1,8 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Avalonia.Threading; using Avalonia.Threading;
@ -15,7 +11,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
{ {
private readonly Func<TSender, EventHandler<TEventArgs>, Action> _subscribe; private readonly Func<TSender, EventHandler<TEventArgs>, Action> _subscribe;
readonly ConditionalWeakTable<object, Subscription> _subscriptions = new(); private readonly ConditionalWeakTable<object, Subscription> _subscriptions = new();
internal WeakEvent( internal WeakEvent(
Action<TSender, EventHandler<TEventArgs>> subscribe, Action<TSender, EventHandler<TEventArgs>> subscribe,
@ -51,56 +47,6 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
private readonly WeakEvent<TSender, TEventArgs> _ev; private readonly WeakEvent<TSender, TEventArgs> _ev;
private readonly TSender _target; private readonly TSender _target;
private readonly Action _compact; private readonly Action _compact;
struct Entry
{
WeakReference<IWeakEventSubscriber<TEventArgs>>? _reference;
int _hashCode;
public Entry(IWeakEventSubscriber<TEventArgs> r)
{
if (r == null)
{
_reference = null;
_hashCode = 0;
return;
}
_hashCode = r.GetHashCode();
_reference = new WeakReference<IWeakEventSubscriber<TEventArgs>>(r);
}
public bool IsEmpty
{
get
{
if (_reference == null)
return true;
if (_reference.TryGetTarget(out _))
return false;
_reference = null;
return true;
}
}
public bool TryGetTarget([MaybeNullWhen(false)]out IWeakEventSubscriber<TEventArgs> target)
{
if (_reference == null)
{
target = null!;
return false;
}
return _reference.TryGetTarget(out target);
}
public bool Equals(IWeakEventSubscriber<TEventArgs> r)
{
if (_reference == null || r.GetHashCode() != _hashCode)
return false;
return _reference.TryGetTarget(out var target) && target == r;
}
}
private readonly Action _unsubscribe; private readonly Action _unsubscribe;
private readonly WeakHashList<IWeakEventSubscriber<TEventArgs>> _list = new(); private readonly WeakHashList<IWeakEventSubscriber<TEventArgs>> _list = new();
private bool _compactScheduled; private bool _compactScheduled;
@ -114,7 +60,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
_unsubscribe = ev._subscribe(target, OnEvent); _unsubscribe = ev._subscribe(target, OnEvent);
} }
void Destroy() private void Destroy()
{ {
if(_destroyed) if(_destroyed)
return; return;
@ -134,15 +80,15 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
ScheduleCompact(); ScheduleCompact();
} }
void ScheduleCompact() private void ScheduleCompact()
{ {
if(_compactScheduled || _destroyed) if(_compactScheduled || _destroyed)
return; return;
_compactScheduled = true; _compactScheduled = true;
Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background); Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background);
} }
void Compact() private void Compact()
{ {
if(!_compactScheduled) if(!_compactScheduled)
return; return;
@ -152,7 +98,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
Destroy(); Destroy();
} }
void OnEvent(object? sender, TEventArgs eventArgs) private void OnEvent(object? sender, TEventArgs eventArgs)
{ {
var alive = _list.GetAlive(); var alive = _list.GetAlive();
if(alive == null) if(alive == null)
@ -196,4 +142,4 @@ public class WeakEvent
return () => unsubscribe(s, handler); return () => unsubscribe(s, handler);
}); });
} }
} }

45
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@ -60,8 +60,7 @@ namespace Avalonia.Utilities
private static class SubscriptionTypeStorage<TArgs, TSubscriber> private static class SubscriptionTypeStorage<TArgs, TSubscriber>
where TArgs : EventArgs where TSubscriber : class where TArgs : EventArgs where TSubscriber : class
{ {
public static readonly ConditionalWeakTable<object, SubscriptionDic<TArgs, TSubscriber>> Subscribers public static readonly ConditionalWeakTable<object, SubscriptionDic<TArgs, TSubscriber>> Subscribers = new();
= new ConditionalWeakTable<object, SubscriptionDic<TArgs, TSubscriber>>();
} }
private class SubscriptionDic<T, TSubscriber> : Dictionary<string, Subscription<T, TSubscriber>> private class SubscriptionDic<T, TSubscriber> : Dictionary<string, Subscription<T, TSubscriber>>
@ -69,8 +68,7 @@ namespace Avalonia.Utilities
{ {
} }
private static readonly Dictionary<Type, Dictionary<string, EventInfo>> Accessors private static readonly Dictionary<Type, Dictionary<string, EventInfo>> s_accessors = new();
= new Dictionary<Type, Dictionary<string, EventInfo>>();
private class Subscription<T, TSubscriber> where T : EventArgs where TSubscriber : class private class Subscription<T, TSubscriber> where T : EventArgs where TSubscriber : class
{ {
@ -81,18 +79,17 @@ namespace Avalonia.Utilities
private readonly Delegate _delegate; private readonly Delegate _delegate;
private Descriptor[] _data = new Descriptor[2]; private Descriptor[] _data = new Descriptor[2];
private int _count = 0; private int _count;
delegate void CallerDelegate(TSubscriber s, object sender, T args); private delegate void CallerDelegate(TSubscriber s, object? sender, T args);
struct Descriptor private struct Descriptor
{ {
public WeakReference<TSubscriber> Subscriber; public WeakReference<TSubscriber>? Subscriber;
public CallerDelegate Caller; public CallerDelegate? Caller;
} }
private static Dictionary<MethodInfo, CallerDelegate> s_Callers = private static readonly Dictionary<MethodInfo, CallerDelegate> s_callers = new();
new Dictionary<MethodInfo, CallerDelegate>();
public Subscription(SubscriptionDic<T, TSubscriber> sdic, public Subscription(SubscriptionDic<T, TSubscriber> sdic,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] Type targetType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] Type targetType,
@ -101,8 +98,8 @@ namespace Avalonia.Utilities
_sdic = sdic; _sdic = sdic;
_target = target; _target = target;
_eventName = eventName; _eventName = eventName;
if (!Accessors.TryGetValue(targetType, out var evDic)) if (!s_accessors.TryGetValue(targetType, out var evDic))
Accessors[targetType] = evDic = new Dictionary<string, EventInfo>(); s_accessors[targetType] = evDic = new Dictionary<string, EventInfo>();
if (evDic.TryGetValue(eventName, out var info)) if (evDic.TryGetValue(eventName, out var info))
{ {
@ -123,12 +120,12 @@ namespace Avalonia.Utilities
var del = new Action<object, T>(OnEvent); var del = new Action<object, T>(OnEvent);
_delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target); _delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target);
_info.AddMethod!.Invoke(target, new[] { _delegate }); _info.AddMethod!.Invoke(target, new object?[] { _delegate });
} }
void Destroy() private void Destroy()
{ {
_info.RemoveMethod!.Invoke(_target, new[] { _delegate }); _info.RemoveMethod!.Invoke(_target, new object?[] { _delegate });
_sdic.Remove(_eventName); _sdic.Remove(_eventName);
} }
@ -146,8 +143,8 @@ namespace Avalonia.Utilities
MethodInfo method = s.Method; MethodInfo method = s.Method;
var subscriber = (TSubscriber)s.Target!; var subscriber = (TSubscriber)s.Target!;
if (!s_Callers.TryGetValue(method, out var caller)) if (!s_callers.TryGetValue(method, out var caller))
s_Callers[method] = caller = s_callers[method] = caller =
(CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, method); (CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, method);
_data[_count] = new Descriptor _data[_count] = new Descriptor
{ {
@ -178,7 +175,7 @@ namespace Avalonia.Utilities
} }
} }
void Compact(bool preventDestroy = false) private void Compact(bool preventDestroy = false)
{ {
int empty = -1; int empty = -1;
for (int c = 0; c < _count; c++) for (int c = 0; c < _count; c++)
@ -206,15 +203,15 @@ namespace Avalonia.Utilities
Destroy(); Destroy();
} }
void OnEvent(object sender, T eventArgs) private void OnEvent(object? sender, T eventArgs)
{ {
var needCompact = false; var needCompact = false;
for(var c=0; c<_count; c++) for (var c = 0; c < _count; c++)
{ {
var r = _data[c].Subscriber; var r = _data[c].Subscriber!;
if (r.TryGetTarget(out var sub)) if (r.TryGetTarget(out var sub))
{ {
_data[c].Caller(sub, sender, eventArgs); _data[c].Caller!(sub, sender, eventArgs);
} }
else else
needCompact = true; needCompact = true;

34
src/Avalonia.Base/Visual.cs

@ -348,7 +348,7 @@ namespace Avalonia
/// </summary> /// </summary>
public void InvalidateVisual() public void InvalidateVisual()
{ {
VisualRoot?.Renderer?.AddDirty(this); VisualRoot?.Renderer.AddDirty(this);
} }
/// <summary> /// <summary>
@ -449,7 +449,7 @@ namespace Avalonia
protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
base.LogicalChildrenCollectionChanged(sender, e); base.LogicalChildrenCollectionChanged(sender, e);
VisualRoot?.Renderer?.RecalculateChildren(this); VisualRoot?.Renderer.RecalculateChildren(this);
} }
/// <summary> /// <summary>
@ -477,23 +477,19 @@ namespace Avalonia
OnAttachedToVisualTree(e); OnAttachedToVisualTree(e);
AttachedToVisualTree?.Invoke(this, e); AttachedToVisualTree?.Invoke(this, e);
InvalidateVisual(); InvalidateVisual();
_visualRoot.Renderer?.RecalculateChildren(_visualParent!); _visualRoot.Renderer.RecalculateChildren(_visualParent!);
if (ZIndex != 0 && VisualParent is Visual parent) if (ZIndex != 0 && VisualParent is Visual parent)
parent.HasNonUniformZIndexChildren = true; parent.HasNonUniformZIndexChildren = true;
var visualChildren = VisualChildren; var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;
if (visualChildren != null) for (var i = 0; i < visualChildrenCount; i++)
{ {
var visualChildrenCount = visualChildren.Count; if (visualChildren[i] is { } child)
for (var i = 0; i < visualChildrenCount; i++)
{ {
if (visualChildren[i] is Visual child) child.OnAttachedToVisualTreeCore(e);
{
child.OnAttachedToVisualTreeCore(e);
}
} }
} }
} }
@ -540,20 +536,16 @@ namespace Avalonia
} }
DetachedFromVisualTree?.Invoke(this, e); DetachedFromVisualTree?.Invoke(this, e);
e.Root?.Renderer?.AddDirty(this); e.Root.Renderer.AddDirty(this);
var visualChildren = VisualChildren; var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;
if (visualChildren != null) for (var i = 0; i < visualChildrenCount; i++)
{ {
var visualChildrenCount = visualChildren.Count; if (visualChildren[i] is { } child)
for (var i = 0; i < visualChildrenCount; i++)
{ {
if (visualChildren[i] is Visual child) child.OnDetachedFromVisualTreeCore(e);
{
child.OnDetachedFromVisualTreeCore(e);
}
} }
} }
} }
@ -659,7 +651,7 @@ namespace Avalonia
parentVisual.HasNonUniformZIndexChildren = true; parentVisual.HasNonUniformZIndexChildren = true;
sender?.InvalidateVisual(); sender?.InvalidateVisual();
parent?.VisualRoot?.Renderer?.RecalculateChildren(parent); parent?.VisualRoot?.Renderer.RecalculateChildren(parent);
} }
/// <summary> /// <summary>

19
src/Avalonia.Base/VisualTree/VisualExtensions.cs

@ -46,7 +46,7 @@ namespace Avalonia.VisualTree
Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); Visual? v = visual ?? throw new ArgumentNullException(nameof(visual));
var result = 0; var result = 0;
v = v?.VisualParent; v = v.VisualParent;
while (v != null) while (v != null)
{ {
@ -64,17 +64,13 @@ namespace Avalonia.VisualTree
/// <param name="visual">The first visual.</param> /// <param name="visual">The first visual.</param>
/// <param name="target">The second visual.</param> /// <param name="target">The second visual.</param>
/// <returns>The common ancestor, or null if not found.</returns> /// <returns>The common ancestor, or null if not found.</returns>
public static Visual? FindCommonVisualAncestor(this Visual visual, Visual target) public static Visual? FindCommonVisualAncestor(this Visual? visual, Visual? target)
{ {
Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); if (visual is null || target is null)
if (target is null)
{ {
return null; return null;
} }
Visual? t = target;
void GoUpwards(ref Visual? node, int count) void GoUpwards(ref Visual? node, int count)
{ {
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
@ -83,6 +79,9 @@ namespace Avalonia.VisualTree
} }
} }
Visual? v = visual;
Visual? t = target;
// We want to find lowest node first, then make sure that both nodes are at the same height. // We want to find lowest node first, then make sure that both nodes are at the same height.
// By doing that we can sometimes find out that other node is our lowest common ancestor. // By doing that we can sometimes find out that other node is our lowest common ancestor.
var firstHeight = CalculateDistanceFromRoot(v); var firstHeight = CalculateDistanceFromRoot(v);
@ -144,7 +143,7 @@ namespace Avalonia.VisualTree
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param> /// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First ancestor of given type.</returns> /// <returns>First ancestor of given type.</returns>
public static T? FindAncestorOfType<T>(this Visual visual, bool includeSelf = false) where T : class public static T? FindAncestorOfType<T>(this Visual? visual, bool includeSelf = false) where T : class
{ {
if (visual is null) if (visual is null)
{ {
@ -173,7 +172,7 @@ namespace Avalonia.VisualTree
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param> /// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First descendant of given type.</returns> /// <returns>First descendant of given type.</returns>
public static T? FindDescendantOfType<T>(this Visual visual, bool includeSelf = false) where T : class public static T? FindDescendantOfType<T>(this Visual? visual, bool includeSelf = false) where T : class
{ {
if (visual is null) if (visual is null)
{ {
@ -392,7 +391,7 @@ namespace Avalonia.VisualTree
/// True if <paramref name="visual"/> is an ancestor of <paramref name="target"/>; /// True if <paramref name="visual"/> is an ancestor of <paramref name="target"/>;
/// otherwise false. /// otherwise false.
/// </returns> /// </returns>
public static bool IsVisualAncestorOf(this Visual visual, Visual target) public static bool IsVisualAncestorOf(this Visual? visual, Visual? target)
{ {
Visual? current = target?.VisualParent; Visual? current = target?.VisualParent;

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

@ -41,18 +41,6 @@ namespace Avalonia.Controls.Primitives
{ {
} }
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
}
/// <summary> /// <summary>
/// Updates the visual state of the control by applying latest PseudoClasses. /// Updates the visual state of the control by applying latest PseudoClasses.
/// </summary> /// </summary>
@ -123,28 +111,25 @@ namespace Avalonia.Controls.Primitives
IsAlphaMaxForced, IsAlphaMaxForced,
IsSaturationValueMaxForced); IsSaturationValueMaxForced);
if (bgraPixelData != null) if (_backgroundBitmap != null)
{ {
if (_backgroundBitmap != null) // TODO: CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER
{ //
// TODO: CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER // Re-use the existing WriteableBitmap
// // This assumes the height, width and byte counts are the same and must be set to null
// Re-use the existing WriteableBitmap // elsewhere if that assumption is ever not true.
// This assumes the height, width and byte counts are the same and must be set to null // ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData);
// elsewhere if that assumption is ever not true.
// ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); // TODO: ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES
//_backgroundBitmap?.Dispose();
// TODO: ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
//_backgroundBitmap?.Dispose();
_backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
}
else
{
_backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
}
Background = new ImageBrush(_backgroundBitmap);
} }
else
{
_backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
}
Background = new ImageBrush(_backgroundBitmap);
} }
} }

2
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -3979,7 +3979,7 @@ namespace Avalonia.Controls
{ {
if (focusedObject is Control element) if (focusedObject is Control element)
{ {
parent = element.Parent; parent = element.VisualParent;
if (parent != null) if (parent != null)
{ {
dataGridWillReceiveRoutedEvent = false; dataGridWillReceiveRoutedEvent = false;

2
src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs

@ -36,7 +36,7 @@ namespace Avalonia.Controls.Utils
{ {
if (child is Control childElement) if (child is Control childElement)
{ {
parent = childElement.Parent; parent = childElement.VisualParent;
} }
} }
child = parent; child = parent;

93
src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs

@ -44,8 +44,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Layout"/> property. /// Defines the <see cref="Layout"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<AttachedLayout> LayoutProperty = public static readonly StyledProperty<AttachedLayout?> LayoutProperty =
AvaloniaProperty.Register<ItemsRepeater, AttachedLayout>(nameof(Layout), new StackLayout()); AvaloniaProperty.Register<ItemsRepeater, AttachedLayout?>(nameof(Layout), new StackLayout());
/// <summary> /// <summary>
/// Defines the <see cref="VerticalCacheLength"/> property. /// Defines the <see cref="VerticalCacheLength"/> property.
@ -53,8 +53,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> VerticalCacheLengthProperty = public static readonly StyledProperty<double> VerticalCacheLengthProperty =
AvaloniaProperty.Register<ItemsRepeater, double>(nameof(VerticalCacheLength), 2.0); AvaloniaProperty.Register<ItemsRepeater, double>(nameof(VerticalCacheLength), 2.0);
private static readonly StyledProperty<VirtualizationInfo> VirtualizationInfoProperty = private static readonly StyledProperty<VirtualizationInfo?> VirtualizationInfoProperty =
AvaloniaProperty.RegisterAttached<ItemsRepeater, Control, VirtualizationInfo>("VirtualizationInfo"); AvaloniaProperty.RegisterAttached<ItemsRepeater, Control, VirtualizationInfo?>("VirtualizationInfo");
internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1); internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1);
internal static readonly Point ClearedElementsArrangePosition = new Point(-10000.0, -10000.0); internal static readonly Point ClearedElementsArrangePosition = new Point(-10000.0, -10000.0);
@ -63,7 +63,7 @@ namespace Avalonia.Controls
private readonly ViewportManager _viewportManager; private readonly ViewportManager _viewportManager;
private readonly TargetWeakEventSubscriber<ItemsRepeater, EventArgs> _layoutWeakSubscriber; private readonly TargetWeakEventSubscriber<ItemsRepeater, EventArgs> _layoutWeakSubscriber;
private IEnumerable? _items; private IEnumerable? _items;
private VirtualizingLayoutContext? _layoutContext; private RepeaterLayoutContext? _layoutContext;
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged; private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
private bool _isLayoutInProgress; private bool _isLayoutInProgress;
private NotifyCollectionChangedEventArgs? _processingItemsSourceChange; private NotifyCollectionChangedEventArgs? _processingItemsSourceChange;
@ -104,7 +104,7 @@ namespace Avalonia.Controls
/// The layout used to size and position elements. The default is a StackLayout with /// The layout used to size and position elements. The default is a StackLayout with
/// vertical orientation. /// vertical orientation.
/// </value> /// </value>
public AttachedLayout Layout public AttachedLayout? Layout
{ {
get => GetValue(LayoutProperty); get => GetValue(LayoutProperty);
set => SetValue(LayoutProperty, value); set => SetValue(LayoutProperty, value);
@ -164,18 +164,7 @@ namespace Avalonia.Controls
private bool IsProcessingCollectionChange => _processingItemsSourceChange != null; private bool IsProcessingCollectionChange => _processingItemsSourceChange != null;
private LayoutContext LayoutContext private RepeaterLayoutContext LayoutContext => _layoutContext ??= new RepeaterLayoutContext(this);
{
get
{
if (_layoutContext == null)
{
_layoutContext = new RepeaterLayoutContext(this);
}
return _layoutContext;
}
}
event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
{ {
@ -269,39 +258,22 @@ namespace Avalonia.Controls
internal void UnpinElement(Control element) => _viewManager.UpdatePin(element, false); internal void UnpinElement(Control element) => _viewManager.UpdatePin(element, false);
internal static VirtualizationInfo? TryGetVirtualizationInfo(Control element) internal static VirtualizationInfo? TryGetVirtualizationInfo(Control? element)
{ {
return (element as AvaloniaObject)?.GetValue(VirtualizationInfoProperty); return element?.GetValue(VirtualizationInfoProperty);
}
internal static VirtualizationInfo CreateAndInitializeVirtualizationInfo(Control element)
{
if (TryGetVirtualizationInfo(element) != null)
{
throw new InvalidOperationException("VirtualizationInfo already created.");
}
var result = new VirtualizationInfo();
element.SetValue(VirtualizationInfoProperty, result);
return result;
} }
internal static VirtualizationInfo GetVirtualizationInfo(Control element) internal static VirtualizationInfo GetVirtualizationInfo(Control element)
{ {
if (element is AvaloniaObject ao) var result = element.GetValue(VirtualizationInfoProperty);
{
var result = ao.GetValue(VirtualizationInfoProperty);
if (result == null)
{
result = new VirtualizationInfo();
ao.SetValue(VirtualizationInfoProperty, result);
}
return result; if (result == null)
{
result = new VirtualizationInfo();
element.SetValue(VirtualizationInfoProperty, result);
} }
throw new NotSupportedException("Custom implementations of AvaloniaObject not supported."); return result;
} }
private protected override void InvalidateMeasureOnChildrenChanged() private protected override void InvalidateMeasureOnChildrenChanged()
@ -309,6 +281,7 @@ namespace Avalonia.Controls
// Don't invalidate measure when children change. // Don't invalidate measure when children change.
} }
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
if (_isLayoutInProgress) if (_isLayoutInProgress)
@ -334,7 +307,7 @@ namespace Avalonia.Controls
if (layout != null) if (layout != null)
{ {
var layoutContext = GetLayoutContext(); var layoutContext = LayoutContext;
desiredSize = layout.Measure(layoutContext, availableSize); desiredSize = layout.Measure(layoutContext, availableSize);
extent = new Rect(LayoutOrigin.X, LayoutOrigin.Y, desiredSize.Width, desiredSize.Height); extent = new Rect(LayoutOrigin.X, LayoutOrigin.Y, desiredSize.Width, desiredSize.Height);
@ -364,6 +337,7 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
if (_isLayoutInProgress) if (_isLayoutInProgress)
@ -380,7 +354,7 @@ namespace Avalonia.Controls
try try
{ {
var arrangeSize = Layout?.Arrange(GetLayoutContext(), finalSize) ?? default; var arrangeSize = Layout?.Arrange(LayoutContext, finalSize) ?? default;
// The view manager might clear elements during this call. // The view manager might clear elements during this call.
// That's why we call it before arranging cleared elements // That's why we call it before arranging cleared elements
@ -421,6 +395,7 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
@ -428,11 +403,13 @@ namespace Avalonia.Controls
_viewportManager.ResetScrollers(); _viewportManager.ResetScrollers();
} }
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_viewportManager.ResetScrollers(); _viewportManager.ResetScrollers();
} }
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
if (change.Property == ItemsProperty) if (change.Property == ItemsProperty)
@ -501,7 +478,7 @@ namespace Avalonia.Controls
if (parent == this) if (parent == this)
{ {
var virtInfo = TryGetVirtualizationInfo(element); var virtInfo = TryGetVirtualizationInfo(element);
return _viewManager.GetElementIndex(virtInfo!); return _viewManager.GetElementIndex(virtInfo);
} }
return -1; return -1;
@ -529,7 +506,7 @@ namespace Avalonia.Controls
{ {
if (index >= 0 && index >= (ItemsSourceView?.Count ?? 0)) if (index >= 0 && index >= (ItemsSourceView?.Count ?? 0))
{ {
throw new ArgumentException("Argument index is invalid.", "index"); throw new ArgumentException("Argument index is invalid.", nameof(index));
} }
if (_isLayoutInProgress) if (_isLayoutInProgress)
@ -547,7 +524,7 @@ namespace Avalonia.Controls
throw new InvalidOperationException("Cannot make an Anchor when there is no attached layout."); throw new InvalidOperationException("Cannot make an Anchor when there is no attached layout.");
} }
element = (Control)GetLayoutContext().GetOrCreateElementAt(index); element = (Control)LayoutContext.GetOrCreateElementAt(index);
element.Measure(Size.Infinity); element.Measure(Size.Infinity);
} }
@ -647,9 +624,9 @@ namespace Avalonia.Controls
if (Layout is VirtualizingLayout virtualLayout) if (Layout is VirtualizingLayout virtualLayout)
{ {
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args); virtualLayout.OnItemsChanged(LayoutContext, newValue, args);
} }
else if (Layout is NonVirtualizingLayout nonVirtualLayout) else if (Layout is NonVirtualizingLayout)
{ {
// Walk through all the elements and make sure they are cleared for // Walk through all the elements and make sure they are cleared for
// non-virtualizing layouts. // non-virtualizing layouts.
@ -693,7 +670,7 @@ namespace Avalonia.Controls
try try
{ {
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args); virtualLayout.OnItemsChanged(LayoutContext, newValue, args);
} }
finally finally
{ {
@ -760,7 +737,7 @@ namespace Avalonia.Controls
AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, _layoutWeakSubscriber); AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, _layoutWeakSubscriber);
} }
bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout; bool isVirtualizingLayout = newValue is VirtualizingLayout;
_viewportManager.OnLayoutChanged(isVirtualizingLayout); _viewportManager.OnLayoutChanged(isVirtualizingLayout);
InvalidateMeasure(); InvalidateMeasure();
} }
@ -788,7 +765,7 @@ namespace Avalonia.Controls
{ {
if (Layout is VirtualizingLayout virtualLayout) if (Layout is VirtualizingLayout virtualLayout)
{ {
virtualLayout.OnItemsChanged(GetLayoutContext(), sender, args); virtualLayout.OnItemsChanged(LayoutContext, sender, args);
} }
else else
{ {
@ -807,15 +784,5 @@ namespace Avalonia.Controls
{ {
_viewportManager.OnBringIntoViewRequested(e); _viewportManager.OnBringIntoViewRequested(e);
} }
private VirtualizingLayoutContext GetLayoutContext()
{
if (_layoutContext == null)
{
_layoutContext = new RepeaterLayoutContext(this);
}
return _layoutContext;
}
} }
} }

15
src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs

@ -53,7 +53,7 @@ namespace Avalonia.Controls
} }
} }
} }
if (element == null) { element = GetElementFromUniqueIdResetPool(index); }; if (element == null) { element = GetElementFromUniqueIdResetPool(index); }
if (element == null) { element = GetElementFromPinnedElements(index); } if (element == null) { element = GetElementFromPinnedElements(index); }
if (element == null) { element = GetElementFromElementFactory(index); } if (element == null) { element = GetElementFromElementFactory(index); }
@ -221,7 +221,7 @@ namespace Avalonia.Controls
return nextElement; return nextElement;
} }
public int GetElementIndex(VirtualizationInfo virtInfo) public int GetElementIndex(VirtualizationInfo? virtInfo)
{ {
if (virtInfo == null) if (virtInfo == null)
{ {
@ -627,11 +627,7 @@ namespace Avalonia.Controls
var element = GetElement(); var element = GetElement();
var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element); var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
if (virtInfo == null)
{
virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element);
}
// Clear flag // Clear flag
virtInfo.MustClearDataContext = false; virtInfo.MustClearDataContext = false;
@ -710,9 +706,8 @@ namespace Avalonia.Controls
{ {
if (parent is ItemsRepeater repeater) if (parent is ItemsRepeater repeater)
{ {
var element = child as Control;
if (repeater == owner && if (repeater == owner &&
element is not null && child is Control element &&
ItemsRepeater.GetVirtualizationInfo(element).IsRealized) ItemsRepeater.GetVirtualizationInfo(element).IsRealized)
{ {
focusedElement = element; focusedElement = element;
@ -722,7 +717,7 @@ namespace Avalonia.Controls
} }
child = parent; child = parent;
parent = child?.GetVisualParent(); parent = child.GetVisualParent();
} }
} }

14
src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs

@ -166,7 +166,7 @@ namespace Avalonia.Controls
if (Math.Abs(_expectedViewportShift.X) > 1 || Math.Abs(_expectedViewportShift.Y) > 1) if (Math.Abs(_expectedViewportShift.X) > 1 || Math.Abs(_expectedViewportShift.Y) > 1)
{ {
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Expecting viewport shift of ({Shift})", Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Expecting viewport shift of ({Shift})",
_owner.Layout.LayoutId, _expectedViewportShift); _owner.Layout?.LayoutId, _expectedViewportShift);
// There are cases where we might be expecting a shift but not get it. We will // There are cases where we might be expecting a shift but not get it. We will
// be waiting for the effective viewport event but if the scroll viewer is not able // be waiting for the effective viewport event but if the scroll viewer is not able
@ -287,7 +287,7 @@ namespace Avalonia.Controls
if (_pendingViewportShift.X != 0 || _pendingViewportShift.Y != 0) if (_pendingViewportShift.X != 0 || _pendingViewportShift.Y != 0)
{ {
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Layout Updated with pending shift {Shift}- invalidating measure", Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Layout Updated with pending shift {Shift}- invalidating measure",
_owner.Layout.LayoutId, _owner.Layout?.LayoutId,
_pendingViewportShift); _pendingViewportShift);
// Assume this is never going to come. // Assume this is never going to come.
@ -436,7 +436,7 @@ namespace Avalonia.Controls
private void OnEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e) private void OnEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e)
{ {
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout.LayoutId); Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout?.LayoutId);
UpdateViewport(e.EffectiveViewport); UpdateViewport(e.EffectiveViewport);
_pendingViewportShift = default; _pendingViewportShift = default;
@ -490,14 +490,14 @@ namespace Avalonia.Controls
var previousVisibleWindow = _visibleWindow; var previousVisibleWindow = _visibleWindow;
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Effective Viewport: ({Before})->({After})", Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Effective Viewport: ({Before})->({After})",
_owner.Layout.LayoutId, _owner.Layout?.LayoutId,
previousVisibleWindow, previousVisibleWindow,
viewport); viewport);
if (-currentVisibleWindow.X <= ItemsRepeater.ClearedElementsArrangePosition.X && if (-currentVisibleWindow.X <= ItemsRepeater.ClearedElementsArrangePosition.X &&
-currentVisibleWindow.Y <= ItemsRepeater.ClearedElementsArrangePosition.Y) -currentVisibleWindow.Y <= ItemsRepeater.ClearedElementsArrangePosition.Y)
{ {
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Viewport is invalid. visible window cleared", _owner.Layout.LayoutId); Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Viewport is invalid. visible window cleared", _owner.Layout?.LayoutId);
// We got cleared. // We got cleared.
_visibleWindow = default; _visibleWindow = default;
} }
@ -509,7 +509,7 @@ namespace Avalonia.Controls
if (_visibleWindow != previousVisibleWindow) if (_visibleWindow != previousVisibleWindow)
{ {
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Used Viewport: ({Before})->({After})", Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Used Viewport: ({Before})->({After})",
_owner.Layout.LayoutId, _owner.Layout?.LayoutId,
previousVisibleWindow, previousVisibleWindow,
currentVisibleWindow); currentVisibleWindow);
TryInvalidateMeasure(); TryInvalidateMeasure();
@ -532,7 +532,7 @@ namespace Avalonia.Controls
// We invalidate measure instead of just invalidating arrange because // We invalidate measure instead of just invalidating arrange because
// we don't invalidate measure in UpdateViewport if the view is changing to // we don't invalidate measure in UpdateViewport if the view is changing to
// avoid layout cycles. // avoid layout cycles.
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Invalidating measure due to viewport change", _owner.Layout.LayoutId); Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Invalidating measure due to viewport change", _owner.Layout?.LayoutId);
_owner.InvalidateMeasure(); _owner.InvalidateMeasure();
} }
} }

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

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
@ -17,32 +16,34 @@ namespace Avalonia.Controls.ApplicationLifetimes
private int _exitCode; private int _exitCode;
private CancellationTokenSource? _cts; private CancellationTokenSource? _cts;
private bool _isShuttingDown; private bool _isShuttingDown;
private HashSet<Window> _windows = new HashSet<Window>(); private readonly HashSet<Window> _windows = new();
private static ClassicDesktopStyleApplicationLifetime? s_activeLifetime;
private static ClassicDesktopStyleApplicationLifetime? _activeLifetime;
static ClassicDesktopStyleApplicationLifetime() static ClassicDesktopStyleApplicationLifetime()
{ {
Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened); Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened);
Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent); Window.WindowClosedEvent.AddClassHandler(typeof(Window), OnWindowClosed);
} }
private static void WindowClosedEvent(object? sender, RoutedEventArgs e) private static void OnWindowClosed(object? sender, RoutedEventArgs e)
{ {
_activeLifetime?._windows.Remove((Window)sender!); var window = (Window)sender!;
_activeLifetime?.HandleWindowClosed((Window)sender!); s_activeLifetime?._windows.Remove(window);
s_activeLifetime?.HandleWindowClosed(window);
} }
private static void OnWindowOpened(object? sender, RoutedEventArgs e) private static void OnWindowOpened(object? sender, RoutedEventArgs e)
{ {
_activeLifetime?._windows.Add((Window)sender!); s_activeLifetime?._windows.Add((Window)sender!);
} }
public ClassicDesktopStyleApplicationLifetime() public ClassicDesktopStyleApplicationLifetime()
{ {
if (_activeLifetime != null) if (s_activeLifetime != null)
throw new InvalidOperationException( throw new InvalidOperationException(
"Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed"); "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed");
_activeLifetime = this; s_activeLifetime = this;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -65,9 +66,10 @@ namespace Avalonia.Controls.ApplicationLifetimes
/// <inheritdoc/> /// <inheritdoc/>
public Window? MainWindow { get; set; } public Window? MainWindow { get; set; }
/// <inheritdoc />
public IReadOnlyList<Window> Windows => _windows.ToArray(); public IReadOnlyList<Window> Windows => _windows.ToArray();
private void HandleWindowClosed(Window window) private void HandleWindowClosed(Window? window)
{ {
if (window == null) if (window == null)
return; return;
@ -130,8 +132,8 @@ namespace Avalonia.Controls.ApplicationLifetimes
public void Dispose() public void Dispose()
{ {
if (_activeLifetime == this) if (s_activeLifetime == this)
_activeLifetime = null; s_activeLifetime = null;
} }
private bool DoShutdown( private bool DoShutdown(

5
src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs

@ -40,7 +40,10 @@ namespace Avalonia.Controls.ApplicationLifetimes
/// The main window. /// The main window.
/// </value> /// </value>
Window? MainWindow { get; set; } Window? MainWindow { get; set; }
/// <summary>
/// Gets the list of all open windows in the application.
/// </summary>
IReadOnlyList<Window> Windows { get; } IReadOnlyList<Window> Windows { get; }
/// <summary> /// <summary>

4
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -792,7 +792,7 @@ namespace Avalonia.Controls
Control? element = focused as Control; Control? element = focused as Control;
if (element != null) if (element != null)
{ {
parent = element.Parent; parent = element.VisualParent;
} }
} }
focused = parent; focused = parent;
@ -1711,7 +1711,7 @@ namespace Avalonia.Controls
/// <param name="predicate">The predicate to use for the partial or /// <param name="predicate">The predicate to use for the partial or
/// exact match.</param> /// exact match.</param>
/// <returns>Returns the object or null.</returns> /// <returns>Returns the object or null.</returns>
private object? TryGetMatch(string? searchText, AvaloniaList<object> view, AutoCompleteFilterPredicate<string?>? predicate) private object? TryGetMatch(string? searchText, AvaloniaList<object>? view, AutoCompleteFilterPredicate<string?>? predicate)
{ {
if (predicate is null) if (predicate is null)
return null; return null;

282
src/Avalonia.Controls/Automation/AutomationProperties.cs

@ -38,8 +38,8 @@ namespace Avalonia.Automation
/// <summary> /// <summary>
/// Defines the AutomationProperties.AcceleratorKey attached property. /// Defines the AutomationProperties.AcceleratorKey attached property.
/// </summary> /// </summary>
public static readonly AttachedProperty<string> AcceleratorKeyProperty = public static readonly AttachedProperty<string?> AcceleratorKeyProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>( AvaloniaProperty.RegisterAttached<StyledElement, string?>(
"AcceleratorKey", "AcceleratorKey",
typeof(AutomationProperties)); typeof(AutomationProperties));
@ -54,16 +54,16 @@ namespace Avalonia.Automation
/// <summary> /// <summary>
/// Defines the AutomationProperties.AccessKey attached property /// Defines the AutomationProperties.AccessKey attached property
/// </summary> /// </summary>
public static readonly AttachedProperty<string> AccessKeyProperty = public static readonly AttachedProperty<string?> AccessKeyProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>( AvaloniaProperty.RegisterAttached<StyledElement, string?>(
"AccessKey", "AccessKey",
typeof(AutomationProperties)); typeof(AutomationProperties));
/// <summary> /// <summary>
/// Defines the AutomationProperties.AutomationId attached property. /// Defines the AutomationProperties.AutomationId attached property.
/// </summary> /// </summary>
public static readonly AttachedProperty<string> AutomationIdProperty = public static readonly AttachedProperty<string?> AutomationIdProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>( AvaloniaProperty.RegisterAttached<StyledElement, string?>(
"AutomationId", "AutomationId",
typeof(AutomationProperties)); typeof(AutomationProperties));
@ -78,8 +78,8 @@ namespace Avalonia.Automation
/// <summary> /// <summary>
/// Defines the AutomationProperties.HelpText attached property. /// Defines the AutomationProperties.HelpText attached property.
/// </summary> /// </summary>
public static readonly AttachedProperty<string> HelpTextProperty = public static readonly AttachedProperty<string?> HelpTextProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>( AvaloniaProperty.RegisterAttached<StyledElement, string?>(
"HelpText", "HelpText",
typeof(AutomationProperties)); typeof(AutomationProperties));
@ -122,16 +122,16 @@ namespace Avalonia.Automation
/// <summary> /// <summary>
/// Defines the AutomationProperties.ItemStatus attached property. /// Defines the AutomationProperties.ItemStatus attached property.
/// </summary> /// </summary>
public static readonly AttachedProperty<string> ItemStatusProperty = public static readonly AttachedProperty<string?> ItemStatusProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>( AvaloniaProperty.RegisterAttached<StyledElement, string?>(
"ItemStatus", "ItemStatus",
typeof(AutomationProperties)); typeof(AutomationProperties));
/// <summary> /// <summary>
/// Defines the AutomationProperties.ItemType attached property. /// Defines the AutomationProperties.ItemType attached property.
/// </summary> /// </summary>
public static readonly AttachedProperty<string> ItemTypeProperty = public static readonly AttachedProperty<string?> ItemTypeProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>( AvaloniaProperty.RegisterAttached<StyledElement, string?>(
"ItemType", "ItemType",
typeof(AutomationProperties)); typeof(AutomationProperties));
@ -155,8 +155,8 @@ namespace Avalonia.Automation
/// <summary> /// <summary>
/// Defines the AutomationProperties.Name attached attached property. /// Defines the AutomationProperties.Name attached attached property.
/// </summary> /// </summary>
public static readonly AttachedProperty<string> NameProperty = public static readonly AttachedProperty<string?> NameProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>( AvaloniaProperty.RegisterAttached<StyledElement, string?>(
"Name", "Name",
typeof(AutomationProperties)); typeof(AutomationProperties));
@ -193,25 +193,17 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetAcceleratorKey(StyledElement element, string value) public static void SetAcceleratorKey(StyledElement element, string value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(AcceleratorKeyProperty, value); element.SetValue(AcceleratorKeyProperty, value);
} }
/// <summary> /// <summary>
/// Helper for reading AcceleratorKey property from a StyledElement. /// Helper for reading AcceleratorKey property from a StyledElement.
/// </summary> /// </summary>
public static string GetAcceleratorKey(StyledElement element) public static string? GetAcceleratorKey(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(AcceleratorKeyProperty);
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(AcceleratorKeyProperty));
} }
/// <summary> /// <summary>
@ -219,11 +211,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetAccessibilityView(StyledElement element, AccessibilityView value) public static void SetAccessibilityView(StyledElement element, AccessibilityView value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(AccessibilityViewProperty, value); element.SetValue(AccessibilityViewProperty, value);
} }
@ -232,11 +220,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static AccessibilityView GetAccessibilityView(StyledElement element) public static AccessibilityView GetAccessibilityView(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
return element.GetValue(AccessibilityViewProperty); return element.GetValue(AccessibilityViewProperty);
} }
@ -245,50 +229,34 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetAccessKey(StyledElement element, string value) public static void SetAccessKey(StyledElement element, string value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(AccessKeyProperty, value); element.SetValue(AccessKeyProperty, value);
} }
/// <summary> /// <summary>
/// Helper for reading AccessKey property from a StyledElement. /// Helper for reading AccessKey property from a StyledElement.
/// </summary> /// </summary>
public static string GetAccessKey(StyledElement element) public static string? GetAccessKey(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(AccessKeyProperty);
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(AccessKeyProperty));
} }
/// <summary> /// <summary>
/// Helper for setting AutomationId property on a StyledElement. /// Helper for setting AutomationId property on a StyledElement.
/// </summary> /// </summary>
public static void SetAutomationId(StyledElement element, string value) public static void SetAutomationId(StyledElement element, string? value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(AutomationIdProperty, value); element.SetValue(AutomationIdProperty, value);
} }
/// <summary> /// <summary>
/// Helper for reading AutomationId property from a StyledElement. /// Helper for reading AutomationId property from a StyledElement.
/// </summary> /// </summary>
public static string GetAutomationId(StyledElement element) public static string? GetAutomationId(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
return element.GetValue(AutomationIdProperty); return element.GetValue(AutomationIdProperty);
} }
@ -297,11 +265,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetControlTypeOverride(StyledElement element, AutomationControlType? value) public static void SetControlTypeOverride(StyledElement element, AutomationControlType? value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(ControlTypeOverrideProperty, value); element.SetValue(ControlTypeOverrideProperty, value);
} }
@ -310,38 +274,26 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static AutomationControlType? GetControlTypeOverride(StyledElement element) public static AutomationControlType? GetControlTypeOverride(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
return element.GetValue(ControlTypeOverrideProperty); return element.GetValue(ControlTypeOverrideProperty);
} }
/// <summary> /// <summary>
/// Helper for setting HelpText property on a StyledElement. /// Helper for setting HelpText property on a StyledElement.
/// </summary> /// </summary>
public static void SetHelpText(StyledElement element, string value) public static void SetHelpText(StyledElement element, string? value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(HelpTextProperty, value); element.SetValue(HelpTextProperty, value);
} }
/// <summary> /// <summary>
/// Helper for reading HelpText property from a StyledElement. /// Helper for reading HelpText property from a StyledElement.
/// </summary> /// </summary>
public static string GetHelpText(StyledElement element) public static string? GetHelpText(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(HelpTextProperty);
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(HelpTextProperty));
} }
/// <summary> /// <summary>
@ -349,11 +301,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetIsColumnHeader(StyledElement element, bool value) public static void SetIsColumnHeader(StyledElement element, bool value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(IsColumnHeaderProperty, value); element.SetValue(IsColumnHeaderProperty, value);
} }
@ -362,12 +310,8 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static bool GetIsColumnHeader(StyledElement element) public static bool GetIsColumnHeader(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(IsColumnHeaderProperty);
throw new ArgumentNullException(nameof(element));
}
return ((bool)element.GetValue(IsColumnHeaderProperty));
} }
/// <summary> /// <summary>
@ -375,11 +319,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetIsRequiredForForm(StyledElement element, bool value) public static void SetIsRequiredForForm(StyledElement element, bool value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(IsRequiredForFormProperty, value); element.SetValue(IsRequiredForFormProperty, value);
} }
@ -388,12 +328,8 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static bool GetIsRequiredForForm(StyledElement element) public static bool GetIsRequiredForForm(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(IsRequiredForFormProperty);
throw new ArgumentNullException(nameof(element));
}
return ((bool)element.GetValue(IsRequiredForFormProperty));
} }
/// <summary> /// <summary>
@ -401,12 +337,8 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static bool GetIsRowHeader(StyledElement element) public static bool GetIsRowHeader(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(IsRowHeaderProperty);
throw new ArgumentNullException(nameof(element));
}
return ((bool)element.GetValue(IsRowHeaderProperty));
} }
/// <summary> /// <summary>
@ -414,11 +346,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetIsRowHeader(StyledElement element, bool value) public static void SetIsRowHeader(StyledElement element, bool value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(IsRowHeaderProperty, value); element.SetValue(IsRowHeaderProperty, value);
} }
@ -427,11 +355,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetIsOffscreenBehavior(StyledElement element, IsOffscreenBehavior value) public static void SetIsOffscreenBehavior(StyledElement element, IsOffscreenBehavior value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(IsOffscreenBehaviorProperty, value); element.SetValue(IsOffscreenBehaviorProperty, value);
} }
@ -440,64 +364,44 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static IsOffscreenBehavior GetIsOffscreenBehavior(StyledElement element) public static IsOffscreenBehavior GetIsOffscreenBehavior(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(IsOffscreenBehaviorProperty);
throw new ArgumentNullException(nameof(element));
}
return ((IsOffscreenBehavior)element.GetValue(IsOffscreenBehaviorProperty));
} }
/// <summary> /// <summary>
/// Helper for setting ItemStatus property on a StyledElement. /// Helper for setting ItemStatus property on a StyledElement.
/// </summary> /// </summary>
public static void SetItemStatus(StyledElement element, string value) public static void SetItemStatus(StyledElement element, string? value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(ItemStatusProperty, value); element.SetValue(ItemStatusProperty, value);
} }
/// <summary> /// <summary>
/// Helper for reading ItemStatus property from a StyledElement. /// Helper for reading ItemStatus property from a StyledElement.
/// </summary> /// </summary>
public static string GetItemStatus(StyledElement element) public static string? GetItemStatus(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(ItemStatusProperty);
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(ItemStatusProperty));
} }
/// <summary> /// <summary>
/// Helper for setting ItemType property on a StyledElement. /// Helper for setting ItemType property on a StyledElement.
/// </summary> /// </summary>
public static void SetItemType(StyledElement element, string value) public static void SetItemType(StyledElement element, string? value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(ItemTypeProperty, value); element.SetValue(ItemTypeProperty, value);
} }
/// <summary> /// <summary>
/// Helper for reading ItemType property from a StyledElement. /// Helper for reading ItemType property from a StyledElement.
/// </summary> /// </summary>
public static string GetItemType(StyledElement element) public static string? GetItemType(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(ItemTypeProperty);
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(ItemTypeProperty));
} }
/// <summary> /// <summary>
@ -505,11 +409,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetLabeledBy(StyledElement element, Control value) public static void SetLabeledBy(StyledElement element, Control value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(LabeledByProperty, value); element.SetValue(LabeledByProperty, value);
} }
@ -518,11 +418,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static Control GetLabeledBy(StyledElement element) public static Control GetLabeledBy(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
return element.GetValue(LabeledByProperty); return element.GetValue(LabeledByProperty);
} }
@ -531,11 +427,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetLiveSetting(StyledElement element, AutomationLiveSetting value) public static void SetLiveSetting(StyledElement element, AutomationLiveSetting value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(LiveSettingProperty, value); element.SetValue(LiveSettingProperty, value);
} }
@ -544,38 +436,26 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static AutomationLiveSetting GetLiveSetting(StyledElement element) public static AutomationLiveSetting GetLiveSetting(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(LiveSettingProperty);
throw new ArgumentNullException(nameof(element));
}
return ((AutomationLiveSetting)element.GetValue(LiveSettingProperty));
} }
/// <summary> /// <summary>
/// Helper for setting Name property on a StyledElement. /// Helper for setting Name property on a StyledElement.
/// </summary> /// </summary>
public static void SetName(StyledElement element, string value) public static void SetName(StyledElement element, string? value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(NameProperty, value); element.SetValue(NameProperty, value);
} }
/// <summary> /// <summary>
/// Helper for reading Name property from a StyledElement. /// Helper for reading Name property from a StyledElement.
/// </summary> /// </summary>
public static string GetName(StyledElement element) public static string? GetName(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(NameProperty);
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(NameProperty));
} }
/// <summary> /// <summary>
@ -583,11 +463,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetPositionInSet(StyledElement element, int value) public static void SetPositionInSet(StyledElement element, int value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(PositionInSetProperty, value); element.SetValue(PositionInSetProperty, value);
} }
@ -596,12 +472,8 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static int GetPositionInSet(StyledElement element) public static int GetPositionInSet(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(PositionInSetProperty);
throw new ArgumentNullException(nameof(element));
}
return ((int)element.GetValue(PositionInSetProperty));
} }
/// <summary> /// <summary>
@ -609,11 +481,7 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static void SetSizeOfSet(StyledElement element, int value) public static void SetSizeOfSet(StyledElement element, int value)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(SizeOfSetProperty, value); element.SetValue(SizeOfSetProperty, value);
} }
@ -622,12 +490,8 @@ namespace Avalonia.Automation
/// </summary> /// </summary>
public static int GetSizeOfSet(StyledElement element) public static int GetSizeOfSet(StyledElement element)
{ {
if (element == null) _ = element ?? throw new ArgumentNullException(nameof(element));
{ return element.GetValue(SizeOfSetProperty);
throw new ArgumentNullException(nameof(element));
}
return ((int)element.GetValue(SizeOfSetProperty));
} }
} }
} }

62
src/Avalonia.Controls/Automation/Peers/ProgressBarAutomationPeer.cs

@ -0,0 +1,62 @@
using System;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Provider;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls.Automation.Peers
{
public class ProgressBarAutomationPeer : RangeBaseAutomationPeer, IRangeValueProvider
{
public ProgressBarAutomationPeer(RangeBase owner) : base(owner)
{
}
protected override string GetClassNameCore()
{
return "ProgressBar";
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.ProgressBar;
}
/// <summary>
/// Request to set the value that this UI element is representing
/// </summary>
/// <param name="val">Value to set the UI to, as an object</param>
/// <returns>true if the UI element was successfully set to the specified value</returns>
void IRangeValueProvider.SetValue(double val)
{
throw new InvalidOperationException("ProgressBar is ReadOnly, value can't be set.");
}
///<summary>Indicates that the value can only be read, not modified.
///returns True if the control is read-only</summary>
bool IRangeValueProvider.IsReadOnly
{
get
{
return true;
}
}
///<summary>Value of a Large Change</summary>
double IRangeValueProvider.LargeChange
{
get
{
return double.NaN;
}
}
///<summary>Value of a Small Change</summary>
double IRangeValueProvider.SmallChange
{
get
{
return double.NaN;
}
}
}
}

4
src/Avalonia.Controls/Button.cs

@ -394,10 +394,10 @@ namespace Avalonia.Controls
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{ {
IsPressed = true; IsPressed = true;
e.Handled = true;
if (ClickMode == ClickMode.Press) if (ClickMode == ClickMode.Press)
{ {
e.Handled = true;
OnClick(); OnClick();
} }
} }
@ -411,11 +411,11 @@ namespace Avalonia.Controls
if (IsPressed && e.InitialPressMouseButton == MouseButton.Left) if (IsPressed && e.InitialPressMouseButton == MouseButton.Left)
{ {
IsPressed = false; IsPressed = false;
e.Handled = true;
if (ClickMode == ClickMode.Release && if (ClickMode == ClickMode.Release &&
this.GetVisualsAt(e.GetPosition(this)).Any(c => this == c || this.IsVisualAncestorOf(c))) this.GetVisualsAt(e.GetPosition(this)).Any(c => this == c || this.IsVisualAncestorOf(c)))
{ {
e.Handled = true;
OnClick(); OnClick();
} }
} }

18
src/Avalonia.Controls/Calendar/Calendar.cs

@ -237,11 +237,11 @@ namespace Avalonia.Controls
private DateTime _selectedYear; private DateTime _selectedYear;
private DateTime _displayDate = DateTime.Today; private DateTime _displayDate = DateTime.Today;
private DateTime? _displayDateStart = null; private DateTime? _displayDateStart;
private DateTime? _displayDateEnd = null; private DateTime? _displayDateEnd;
private bool _isShiftPressed; private bool _isShiftPressed;
private bool _displayDateIsChanging = false; private bool _displayDateIsChanging;
internal CalendarDayButton? FocusButton { get; set; } internal CalendarDayButton? FocusButton { get; set; }
internal CalendarButton? FocusCalendarButton { get; set; } internal CalendarButton? FocusCalendarButton { get; set; }
@ -291,7 +291,7 @@ namespace Avalonia.Controls
} }
else else
{ {
throw new ArgumentOutOfRangeException("d", "Invalid DayOfWeek"); throw new ArgumentOutOfRangeException(nameof(e), "Invalid DayOfWeek");
} }
} }
@ -346,10 +346,10 @@ namespace Avalonia.Controls
} }
} }
public static readonly StyledProperty<IBrush> HeaderBackgroundProperty = public static readonly StyledProperty<IBrush?> HeaderBackgroundProperty =
AvaloniaProperty.Register<Calendar, IBrush>(nameof(HeaderBackground)); AvaloniaProperty.Register<Calendar, IBrush?>(nameof(HeaderBackground));
public IBrush HeaderBackground public IBrush? HeaderBackground
{ {
get { return GetValue(HeaderBackgroundProperty); } get { return GetValue(HeaderBackgroundProperty); }
set { SetValue(HeaderBackgroundProperty, value); } set { SetValue(HeaderBackgroundProperty, value); }
@ -478,7 +478,7 @@ namespace Avalonia.Controls
} }
else else
{ {
throw new ArgumentOutOfRangeException("d", "Invalid SelectionMode"); throw new ArgumentOutOfRangeException(nameof(e), "Invalid SelectionMode");
} }
} }
@ -574,7 +574,7 @@ namespace Avalonia.Controls
} }
else else
{ {
throw new ArgumentOutOfRangeException("d", "SelectedDate value is not valid."); throw new ArgumentOutOfRangeException(nameof(e), "SelectedDate value is not valid.");
} }
} }
else else

14
src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// The Calendar whose dates this object represents. /// The Calendar whose dates this object represents.
/// </summary> /// </summary>
private Calendar _owner; private readonly Calendar _owner;
/// <summary> /// <summary>
/// Initializes a new instance of the /// Initializes a new instance of the
@ -79,13 +79,13 @@ namespace Avalonia.Controls.Primitives
if (DateTime.Compare(end, start) > -1) if (DateTime.Compare(end, start) > -1)
{ {
rangeStart = DateTimeHelper.DiscardTime(start).Value; rangeStart = DateTimeHelper.DiscardTime(start);
rangeEnd = DateTimeHelper.DiscardTime(end).Value; rangeEnd = DateTimeHelper.DiscardTime(end);
} }
else else
{ {
rangeStart = DateTimeHelper.DiscardTime(end).Value; rangeStart = DateTimeHelper.DiscardTime(end);
rangeEnd = DateTimeHelper.DiscardTime(start).Value; rangeEnd = DateTimeHelper.DiscardTime(start);
} }
int count = Count; int count = Count;
@ -144,7 +144,7 @@ namespace Avalonia.Controls.Primitives
if (!IsValid(item)) if (!IsValid(item))
{ {
throw new ArgumentOutOfRangeException("Value is not valid."); throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid.");
} }
base.InsertItem(index, item); base.InsertItem(index, item);
@ -186,7 +186,7 @@ namespace Avalonia.Controls.Primitives
if (!IsValid(item)) if (!IsValid(item))
{ {
throw new ArgumentOutOfRangeException("Value is not valid."); throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid.");
} }
base.SetItem(index, item); base.SetItem(index, item);

112
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -44,30 +44,30 @@ namespace Avalonia.Controls.Primitives
private ITemplate<Control>? _dayTitleTemplate; private ITemplate<Control>? _dayTitleTemplate;
private DateTime _currentMonth; private DateTime _currentMonth;
private bool _isMouseLeftButtonDown = false; private bool _isMouseLeftButtonDown;
private bool _isMouseLeftButtonDownYearView = false; private bool _isMouseLeftButtonDownYearView;
private bool _isControlPressed = false; private bool _isControlPressed;
private System.Globalization.Calendar _calendar = new System.Globalization.GregorianCalendar(); private readonly System.Globalization.Calendar _calendar = new GregorianCalendar();
private PointerPressedEventArgs? _downEventArg;
private PointerPressedEventArgs? _downEventArgYearView;
internal Calendar? Owner { get; set; } internal Calendar? Owner { get; set; }
internal CalendarDayButton? CurrentButton { get; set; } internal CalendarDayButton? CurrentButton { get; set; }
public static readonly StyledProperty<IBrush> HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner<CalendarItem>(); public static readonly StyledProperty<IBrush?> HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner<CalendarItem>();
public IBrush HeaderBackground
public IBrush? HeaderBackground
{ {
get { return GetValue(HeaderBackgroundProperty); } get { return GetValue(HeaderBackgroundProperty); }
set { SetValue(HeaderBackgroundProperty, value); } set { SetValue(HeaderBackgroundProperty, value); }
} }
public static readonly DirectProperty<CalendarItem, ITemplate<Control>?> DayTitleTemplateProperty = public static readonly DirectProperty<CalendarItem, ITemplate<Control>?> DayTitleTemplateProperty =
AvaloniaProperty.RegisterDirect<CalendarItem, ITemplate<Control>?>( AvaloniaProperty.RegisterDirect<CalendarItem, ITemplate<Control>?>(
nameof(DayTitleTemplate), nameof(DayTitleTemplate),
o => o.DayTitleTemplate, o => o.DayTitleTemplate,
(o,v) => o.DayTitleTemplate = v, (o,v) => o.DayTitleTemplate = v,
defaultBindingMode: BindingMode.OneTime); defaultBindingMode: BindingMode.OneTime);
public ITemplate<Control>? DayTitleTemplate public ITemplate<Control>? DayTitleTemplate
{ {
get { return _dayTitleTemplate; } get { return _dayTitleTemplate; }
@ -178,7 +178,7 @@ namespace Avalonia.Controls.Primitives
{ {
if (_dayTitleTemplate != null) if (_dayTitleTemplate != null)
{ {
var cell = (Control) _dayTitleTemplate.Build(); var cell = _dayTitleTemplate.Build();
cell.DataContext = string.Empty; cell.DataContext = string.Empty;
cell.SetValue(Grid.RowProperty, 0); cell.SetValue(Grid.RowProperty, 0);
cell.SetValue(Grid.ColumnProperty, i); cell.SetValue(Grid.ColumnProperty, i);
@ -308,16 +308,13 @@ namespace Avalonia.Controls.Primitives
for (int childIndex = 0; childIndex < Calendar.ColumnsPerMonth; childIndex++) for (int childIndex = 0; childIndex < Calendar.ColumnsPerMonth; childIndex++)
{ {
var daytitle = MonthView!.Children[childIndex]; var daytitle = MonthView!.Children[childIndex];
if (daytitle != null) if (Owner != null)
{ {
if (Owner != null) daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek];
{ }
daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek]; else
} {
else daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek];
{
daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek];
}
} }
} }
} }
@ -527,7 +524,7 @@ namespace Avalonia.Controls.Primitives
childButton.Content = dateToAdd.Day.ToString(DateTimeHelper.GetCurrentDateFormat()); childButton.Content = dateToAdd.Day.ToString(DateTimeHelper.GetCurrentDateFormat());
childButton.DataContext = dateToAdd; childButton.DataContext = dateToAdd;
if (DateTime.Compare((DateTime)DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0) if (DateTime.Compare(DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0)
{ {
// Since we are sure DisplayDate is not equal to // Since we are sure DisplayDate is not equal to
// DateTime.MaxValue, it is safe to use AddDays // DateTime.MaxValue, it is safe to use AddDays
@ -587,7 +584,7 @@ namespace Avalonia.Controls.Primitives
{ {
if (Owner != null) if (Owner != null)
{ {
_currentMonth = (DateTime)Owner.SelectedMonth; _currentMonth = Owner.SelectedMonth;
} }
else else
{ {
@ -676,7 +673,7 @@ namespace Avalonia.Controls.Primitives
if (Owner != null) if (Owner != null)
{ {
selectedYear = Owner.SelectedYear; selectedYear = Owner.SelectedYear;
_currentMonth = (DateTime)Owner.SelectedMonth; _currentMonth = Owner.SelectedMonth;
} }
else else
{ {
@ -696,9 +693,9 @@ namespace Avalonia.Controls.Primitives
SetYearButtons(decade, decadeEnd); SetYearButtons(decade, decadeEnd);
} }
} }
internal void UpdateYearViewSelection(CalendarButton calendarButton) internal void UpdateYearViewSelection(CalendarButton? calendarButton)
{ {
if (Owner != null && calendarButton != null && calendarButton.DataContext != null) if (Owner != null && calendarButton?.DataContext is DateTime selectedDate)
{ {
Owner.FocusCalendarButton!.IsCalendarButtonFocused = false; Owner.FocusCalendarButton!.IsCalendarButtonFocused = false;
Owner.FocusCalendarButton = calendarButton; Owner.FocusCalendarButton = calendarButton;
@ -706,11 +703,11 @@ namespace Avalonia.Controls.Primitives
if (Owner.DisplayMode == CalendarMode.Year) if (Owner.DisplayMode == CalendarMode.Year)
{ {
Owner.SelectedMonth = (DateTime)calendarButton.DataContext; Owner.SelectedMonth = selectedDate;
} }
else else
{ {
Owner.SelectedYear = (DateTime)calendarButton.DataContext; Owner.SelectedYear = selectedDate;
} }
} }
} }
@ -719,7 +716,7 @@ namespace Avalonia.Controls.Primitives
{ {
int year; int year;
int count = -1; int count = -1;
foreach (object child in YearView!.Children) foreach (var child in YearView!.Children)
{ {
CalendarButton childButton = (CalendarButton)child; CalendarButton childButton = (CalendarButton)child;
year = decade + count; year = decade + count;
@ -859,7 +856,8 @@ namespace Avalonia.Controls.Primitives
{ {
if (Owner != null) if (Owner != null)
{ {
if (_isMouseLeftButtonDown && sender is CalendarDayButton b && b.IsEnabled && !b.IsBlackout) if (_isMouseLeftButtonDown
&& sender is CalendarDayButton { IsEnabled: true, IsBlackout: false, DataContext: DateTime selectedDate } b)
{ {
// Update the states of all buttons to be selected starting // Update the states of all buttons to be selected starting
// from HoverStart to b // from HoverStart to b
@ -867,7 +865,6 @@ namespace Avalonia.Controls.Primitives
{ {
case CalendarSelectionMode.SingleDate: case CalendarSelectionMode.SingleDate:
{ {
DateTime selectedDate = (DateTime)b.DataContext!;
Owner.CalendarDatePickerDisplayDateFlag = true; Owner.CalendarDatePickerDisplayDateFlag = true;
if (Owner.SelectedDates.Count == 0) if (Owner.SelectedDates.Count == 0)
{ {
@ -882,10 +879,9 @@ namespace Avalonia.Controls.Primitives
case CalendarSelectionMode.SingleRange: case CalendarSelectionMode.SingleRange:
case CalendarSelectionMode.MultipleRange: case CalendarSelectionMode.MultipleRange:
{ {
Debug.Assert(b.DataContext != null, "The DataContext should not be null!");
Owner.UnHighlightDays(); Owner.UnHighlightDays();
Owner.HoverEndIndex = b.Index; Owner.HoverEndIndex = b.Index;
Owner.HoverEnd = (DateTime?)b.DataContext; Owner.HoverEnd = selectedDate;
// Update the States of the buttons // Update the States of the buttons
Owner.HighlightDays(); Owner.HighlightDays();
return; return;
@ -904,22 +900,14 @@ namespace Avalonia.Controls.Primitives
Owner.Focus(); Owner.Focus();
} }
bool ctrl, shift; CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out var ctrl, out var shift);
CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out ctrl, out shift);
CalendarDayButton b = (CalendarDayButton)sender!;
if (b != null) if (sender is CalendarDayButton b)
{ {
_isControlPressed = ctrl; _isControlPressed = ctrl;
if (b.IsEnabled && !b.IsBlackout) if (b.IsEnabled && !b.IsBlackout && b.DataContext is DateTime selectedDate)
{ {
DateTime selectedDate = (DateTime)b.DataContext!;
_isMouseLeftButtonDown = true; _isMouseLeftButtonDown = true;
// null check is added for unit tests
if (e != null)
{
_downEventArg = e;
}
switch (Owner.SelectionMode) switch (Owner.SelectionMode)
{ {
@ -1010,12 +998,12 @@ namespace Avalonia.Controls.Primitives
} }
} }
} }
private void AddSelection(CalendarDayButton b) private void AddSelection(CalendarDayButton b, DateTime selectedDate)
{ {
if (Owner != null) if (Owner != null)
{ {
Owner.HoverEndIndex = b.Index; Owner.HoverEndIndex = b.Index;
Owner.HoverEnd = (DateTime)b.DataContext!; Owner.HoverEnd = selectedDate;
if (Owner.HoverEnd != null && Owner.HoverStart != null) if (Owner.HoverEnd != null && Owner.HoverStart != null)
{ {
@ -1025,7 +1013,7 @@ namespace Avalonia.Controls.Primitives
// SelectionMode // SelectionMode
Owner.IsMouseSelection = true; Owner.IsMouseSelection = true;
Owner.SelectedDates.AddRange(Owner.HoverStart.Value, Owner.HoverEnd.Value); Owner.SelectedDates.AddRange(Owner.HoverStart.Value, Owner.HoverEnd.Value);
Owner.OnDayClick((DateTime)b.DataContext); Owner.OnDayClick(selectedDate);
} }
} }
} }
@ -1039,11 +1027,11 @@ namespace Avalonia.Controls.Primitives
Owner.OnDayButtonMouseUp(e); Owner.OnDayButtonMouseUp(e);
} }
_isMouseLeftButtonDown = false; _isMouseLeftButtonDown = false;
if (b != null && b.DataContext != null) if (b != null && b.DataContext is DateTime selectedDate)
{ {
if (Owner.SelectionMode == CalendarSelectionMode.None || Owner.SelectionMode == CalendarSelectionMode.SingleDate) if (Owner.SelectionMode == CalendarSelectionMode.None || Owner.SelectionMode == CalendarSelectionMode.SingleDate)
{ {
Owner.OnDayClick((DateTime)b.DataContext); Owner.OnDayClick(selectedDate);
return; return;
} }
if (Owner.HoverStart.HasValue) if (Owner.HoverStart.HasValue)
@ -1058,14 +1046,14 @@ namespace Avalonia.Controls.Primitives
Owner.RemovedItems.Add(item); Owner.RemovedItems.Add(item);
} }
Owner.SelectedDates.ClearInternal(); Owner.SelectedDates.ClearInternal();
AddSelection(b); AddSelection(b, selectedDate);
return; return;
} }
case CalendarSelectionMode.MultipleRange: case CalendarSelectionMode.MultipleRange:
{ {
// add the selection (either single day or // add the selection (either single day or
// SingleRange day) // SingleRange day)
AddSelection(b); AddSelection(b, selectedDate);
return; return;
} }
} }
@ -1076,7 +1064,7 @@ namespace Avalonia.Controls.Primitives
// be able to switch months // be able to switch months
if (b.IsInactive && b.IsBlackout) if (b.IsInactive && b.IsBlackout)
{ {
Owner.OnDayClick((DateTime)b.DataContext); Owner.OnDayClick(selectedDate);
} }
} }
} }
@ -1095,9 +1083,9 @@ namespace Avalonia.Controls.Primitives
Owner.HoverStart = null; Owner.HoverStart = null;
_isMouseLeftButtonDown = false; _isMouseLeftButtonDown = false;
b.IsSelected = false; b.IsSelected = false;
if (b.DataContext != null) if (b.DataContext is DateTime selectedDate)
{ {
Owner.SelectedDates.Remove((DateTime)b.DataContext); Owner.SelectedDates.Remove(selectedDate);
} }
} }
} }
@ -1107,35 +1095,26 @@ namespace Avalonia.Controls.Primitives
private void Month_CalendarButtonMouseDown(object? sender, PointerPressedEventArgs e) private void Month_CalendarButtonMouseDown(object? sender, PointerPressedEventArgs e)
{ {
CalendarButton b = (CalendarButton)sender!;
_isMouseLeftButtonDownYearView = true; _isMouseLeftButtonDownYearView = true;
if (e != null) UpdateYearViewSelection(sender as CalendarButton);
{
_downEventArgYearView = e;
}
UpdateYearViewSelection(b);
} }
internal void Month_CalendarButtonMouseUp(object? sender, PointerReleasedEventArgs e) internal void Month_CalendarButtonMouseUp(object? sender, PointerReleasedEventArgs e)
{ {
_isMouseLeftButtonDownYearView = false; _isMouseLeftButtonDownYearView = false;
if (Owner != null) if (Owner != null && (sender as CalendarButton)?.DataContext is DateTime newMonth)
{ {
DateTime newmonth = (DateTime)((CalendarButton)sender!).DataContext!;
if (Owner.DisplayMode == CalendarMode.Year) if (Owner.DisplayMode == CalendarMode.Year)
{ {
Owner.DisplayDate = newmonth; Owner.DisplayDate = newMonth;
Owner.DisplayMode = CalendarMode.Month; Owner.DisplayMode = CalendarMode.Month;
} }
else else
{ {
Debug.Assert(Owner.DisplayMode == CalendarMode.Decade, "The owning Calendar should be in decade mode!"); Debug.Assert(Owner.DisplayMode == CalendarMode.Decade, "The owning Calendar should be in decade mode!");
Owner.SelectedMonth = newmonth; Owner.SelectedMonth = newMonth;
Owner.DisplayMode = CalendarMode.Year; Owner.DisplayMode = CalendarMode.Year;
} }
} }
@ -1145,8 +1124,7 @@ namespace Avalonia.Controls.Primitives
{ {
if (_isMouseLeftButtonDownYearView) if (_isMouseLeftButtonDownYearView)
{ {
CalendarButton b = (CalendarButton)sender!; UpdateYearViewSelection(sender as CalendarButton);
UpdateYearViewSelection(b);
} }
} }

33
src/Avalonia.Controls/Calendar/DateTimeHelper.cs

@ -53,7 +53,7 @@ namespace Avalonia.Controls
public static int CompareDays(DateTime dt1, DateTime dt2) public static int CompareDays(DateTime dt1, DateTime dt2)
{ {
return DateTime.Compare(DiscardTime(dt1).Value, DiscardTime(dt2).Value); return DateTime.Compare(DiscardTime(dt1), DiscardTime(dt2));
} }
public static int CompareYearMonth(DateTime dt1, DateTime dt2) public static int CompareYearMonth(DateTime dt1, DateTime dt2)
@ -71,14 +71,9 @@ namespace Avalonia.Controls
return new DateTime(d.Year, d.Month, 1, 0, 0, 0); return new DateTime(d.Year, d.Month, 1, 0, 0, 0);
} }
[return: NotNullIfNotNull("d")] public static DateTime DiscardTime(DateTime d)
public static DateTime? DiscardTime(DateTime? d)
{ {
if (d == null) return d.Date;
{
return null;
}
return d.Value.Date;
} }
public static int EndOfDecade(DateTime date) public static int EndOfDecade(DateTime date)
@ -127,28 +122,14 @@ namespace Avalonia.Controls
public static string ToYearMonthPatternString(DateTime date) public static string ToYearMonthPatternString(DateTime date)
{ {
string result = string.Empty; var format = GetCurrentDateFormat();
DateTimeFormatInfo format = GetCurrentDateFormat(); return date.ToString(format.YearMonthPattern, format);
if (format != null)
{
result = date.ToString(format.YearMonthPattern, format);
}
return result;
} }
public static string ToYearString(DateTime date) public static string ToYearString(DateTime date)
{ {
string result = string.Empty; var format = GetCurrentDateFormat();
DateTimeFormatInfo format = GetCurrentDateFormat(); return date.Year.ToString(format);
if (format != null)
{
result = date.Year.ToString(format);
}
return result;
} }
} }
} }

20
src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs

@ -51,11 +51,11 @@ namespace Avalonia.Controls
private bool _isDropDownOpen; private bool _isDropDownOpen;
private DateTime? _selectedDate; private DateTime? _selectedDate;
private string? _text; private string? _text;
private bool _suspendTextChangeHandler = false; private bool _suspendTextChangeHandler;
private bool _isPopupClosing = false; private bool _isPopupClosing;
private bool _ignoreButtonClick = false; private bool _ignoreButtonClick;
private bool _isFlyoutOpen = false; private bool _isFlyoutOpen;
private bool _isPressed = false; private bool _isPressed;
/// <summary> /// <summary>
/// Occurs when the drop-down /// Occurs when the drop-down
@ -185,7 +185,7 @@ namespace Avalonia.Controls
{ {
_textBox.KeyDown += TextBox_KeyDown; _textBox.KeyDown += TextBox_KeyDown;
_textBox.GotFocus += TextBox_GotFocus; _textBox.GotFocus += TextBox_GotFocus;
_textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBox_TextChanged()); _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(_ => TextBox_TextChanged());
if(SelectedDate.HasValue) if(SelectedDate.HasValue)
{ {
@ -292,7 +292,7 @@ namespace Avalonia.Controls
// Text // Text
else if (change.Property == TextProperty) else if (change.Property == TextProperty)
{ {
var (oldValue, newValue) = change.GetOldAndNewValue<string>(); var (_, newValue) = change.GetOldAndNewValue<string?>();
if (!_suspendTextChangeHandler) if (!_suspendTextChangeHandler)
{ {
@ -595,9 +595,9 @@ namespace Avalonia.Controls
private void Calendar_KeyDown(object? sender, KeyEventArgs e) private void Calendar_KeyDown(object? sender, KeyEventArgs e)
{ {
Calendar? c = sender as Calendar ?? throw new ArgumentException("Sender must be Calendar.", nameof(sender)); if (!e.Handled
&& sender is Calendar { DisplayMode: CalendarMode.Month }
if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month) && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape))
{ {
Focus(); Focus();
IsDropDownOpen = false; IsDropDownOpen = false;

40
src/Avalonia.Controls/Chrome/TitleBar.cs

@ -17,28 +17,26 @@ namespace Avalonia.Controls.Chrome
private void UpdateSize(Window window) private void UpdateSize(Window window)
{ {
if (window != null) Margin = new Thickness(
window.OffScreenMargin.Left,
window.OffScreenMargin.Top,
window.OffScreenMargin.Right,
window.OffScreenMargin.Bottom);
if (window.WindowState != WindowState.FullScreen)
{ {
Margin = new Thickness( Height = window.WindowDecorationMargin.Top;
window.OffScreenMargin.Left,
window.OffScreenMargin.Top,
window.OffScreenMargin.Right,
window.OffScreenMargin.Bottom);
if (window.WindowState != WindowState.FullScreen) if (_captionButtons != null)
{ {
Height = window.WindowDecorationMargin.Top; _captionButtons.Height = Height;
if (_captionButtons != null)
{
_captionButtons.Height = Height;
}
} }
IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false;
} }
IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false;
} }
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
@ -55,6 +53,7 @@ namespace Avalonia.Controls.Chrome
} }
} }
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
@ -64,13 +63,13 @@ namespace Avalonia.Controls.Chrome
_disposables = new CompositeDisposable(6) _disposables = new CompositeDisposable(6)
{ {
window.GetObservable(Window.WindowDecorationMarginProperty) window.GetObservable(Window.WindowDecorationMarginProperty)
.Subscribe(x => UpdateSize(window)), .Subscribe(_ => UpdateSize(window)),
window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty)
.Subscribe(x => UpdateSize(window)), .Subscribe(_ => UpdateSize(window)),
window.GetObservable(Window.OffScreenMarginProperty) window.GetObservable(Window.OffScreenMarginProperty)
.Subscribe(x => UpdateSize(window)), .Subscribe(_ => UpdateSize(window)),
window.GetObservable(Window.ExtendClientAreaChromeHintsProperty) window.GetObservable(Window.ExtendClientAreaChromeHintsProperty)
.Subscribe(x => UpdateSize(window)), .Subscribe(_ => UpdateSize(window)),
window.GetObservable(Window.WindowStateProperty) window.GetObservable(Window.WindowStateProperty)
.Subscribe(x => .Subscribe(x =>
{ {
@ -80,11 +79,12 @@ namespace Avalonia.Controls.Chrome
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
}), }),
window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty) window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty)
.Subscribe(x => UpdateSize(window)) .Subscribe(_ => UpdateSize(window))
}; };
} }
} }
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTree(e);

4
src/Avalonia.Controls/Control.cs

@ -2,14 +2,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
@ -211,8 +209,6 @@ namespace Avalonia.Controls
remove => RemoveHandler(SizeChangedEvent, value); remove => RemoveHandler(SizeChangedEvent, value);
} }
public new Control? Parent => (Control?)base.Parent;
/// <inheritdoc/> /// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;

18
src/Avalonia.Controls/Controls.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Collections; using Avalonia.Collections;
@ -13,7 +14,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public Controls() public Controls()
{ {
ResetBehavior = ResetBehavior.Remove; Configure();
} }
/// <summary> /// <summary>
@ -21,9 +22,22 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="items">The initial items in the collection.</param> /// <param name="items">The initial items in the collection.</param>
public Controls(IEnumerable<Control> items) public Controls(IEnumerable<Control> items)
: base(items) {
Configure();
AddRange(items); // virtual member call in ctor, ok for our current implementation
}
private void Configure()
{ {
ResetBehavior = ResetBehavior.Remove; ResetBehavior = ResetBehavior.Remove;
Validate = item =>
{
if (item is null)
{
throw new ArgumentNullException(nameof(item),
$"A null control cannot be added to a {nameof(Controls)} collection.");
}
};
} }
} }
} }

1
src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs

@ -14,7 +14,6 @@ namespace Avalonia.Controls.Converters
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture) public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{ {
if (parameter == null || if (parameter == null ||
values == null ||
values.Count != 4 || values.Count != 4 ||
!(values[0] is ScrollBarVisibility visibility) || !(values[0] is ScrollBarVisibility visibility) ||
!(values[1] is double offset) || !(values[1] is double offset) ||

90
src/Avalonia.Controls/DefinitionBase.cs

@ -21,9 +21,9 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// SharedSizeGroup property. /// SharedSizeGroup property.
/// </summary> /// </summary>
public string SharedSizeGroup public string? SharedSizeGroup
{ {
get { return (string)GetValue(SharedSizeGroupProperty); } get { return GetValue(SharedSizeGroupProperty); }
set { SetValue(SharedSizeGroupProperty, value); } set { SetValue(SharedSizeGroupProperty, value); }
} }
@ -32,20 +32,15 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
internal void OnEnterParentTree() internal void OnEnterParentTree()
{ {
this.InheritanceParent = Parent; InheritanceParent = Parent;
if (_sharedState == null) if (_sharedState == null)
{ {
// start with getting SharedSizeGroup value. // start with getting SharedSizeGroup value.
// this property is NOT inherited which should result in better overall perf. // this property is NOT inherited which should result in better overall perf.
string sharedSizeGroupId = SharedSizeGroup; if (SharedSizeGroup is { } sharedSizeGroupId && PrivateSharedSizeScope is { } privateSharedSizeScope)
if (sharedSizeGroupId != null)
{ {
SharedSizeScope? privateSharedSizeScope = PrivateSharedSizeScope; _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
if (privateSharedSizeScope != null) _sharedState.AddMember(this);
{
_sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
_sharedState.AddMember(this);
}
} }
} }
@ -321,13 +316,12 @@ namespace Avalonia.Controls
return ((_flags & flags) == flags); return ((_flags & flags) == flags);
} }
private static void OnSharedSizeGroupPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition,
AvaloniaPropertyChangedEventArgs<string?> e)
{ {
DefinitionBase definition = (DefinitionBase)d;
if (definition.Parent != null) if (definition.Parent != null)
{ {
string sharedSizeGroupId = (string)e.NewValue!; string? sharedSizeGroupId = e.NewValue.Value;
if (definition._sharedState != null) if (definition._sharedState != null)
{ {
@ -337,16 +331,14 @@ namespace Avalonia.Controls
definition._sharedState = null; definition._sharedState = null;
} }
if ((definition._sharedState == null) && (sharedSizeGroupId != null)) if (definition._sharedState == null
&& sharedSizeGroupId != null
&& definition.PrivateSharedSizeScope is { } privateSharedSizeScope)
{ {
SharedSizeScope? privateSharedSizeScope = definition.PrivateSharedSizeScope; // if definition is not registered and both: shared size group id AND private shared scope
if (privateSharedSizeScope != null) // are available, then register definition.
{ definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
// if definition is not registered and both: shared size group id AND private shared scope definition._sharedState.AddMember(definition);
// are available, then register definition.
definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
definition._sharedState.AddMember(definition);
}
} }
} }
} }
@ -357,17 +349,15 @@ namespace Avalonia.Controls
/// b) contains only letters, digits and underscore ('_'). /// b) contains only letters, digits and underscore ('_').
/// c) does not start with a digit. /// c) does not start with a digit.
/// </remarks> /// </remarks>
private static bool SharedSizeGroupPropertyValueValid(string value) private static bool SharedSizeGroupPropertyValueValid(string? id)
{ {
// null is default value // null is default value
if (value == null) if (id == null)
{ {
return true; return true;
} }
string id = (string)value; if (id.Length > 0)
if (!string.IsNullOrEmpty(id))
{ {
int i = -1; int i = -1;
while (++i < id.Length) while (++i < id.Length)
@ -397,14 +387,11 @@ namespace Avalonia.Controls
/// existing scope just left. In both cases if the DefinitionBase object is already registered /// existing scope just left. In both cases if the DefinitionBase object is already registered
/// in SharedSizeState, it should un-register and register itself in a new one. /// in SharedSizeState, it should un-register and register itself in a new one.
/// </remark> /// </remark>
private static void OnPrivateSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) private static void OnPrivateSharedSizeScopePropertyChanged(DefinitionBase definition,
AvaloniaPropertyChangedEventArgs<SharedSizeScope?> e)
{ {
DefinitionBase definition = (DefinitionBase)d;
if (definition.Parent != null) if (definition.Parent != null)
{ {
SharedSizeScope privateSharedSizeScope = (SharedSizeScope)e.NewValue!;
if (definition._sharedState != null) if (definition._sharedState != null)
{ {
// if definition is already registered And shared size scope is changing, // if definition is already registered And shared size scope is changing,
@ -413,16 +400,14 @@ namespace Avalonia.Controls
definition._sharedState = null; definition._sharedState = null;
} }
if ((definition._sharedState == null) && (privateSharedSizeScope != null)) if (definition._sharedState == null
&& e.NewValue.Value is { } privateSharedSizeScope
&& definition.SharedSizeGroup is { } sharedSizeGroup)
{ {
string sharedSizeGroup = definition.SharedSizeGroup; // if definition is not registered and both: shared size group id AND private shared scope
if (sharedSizeGroup != null) // are available, then register definition.
{ definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroup);
// if definition is not registered and both: shared size group id AND private shared scope definition._sharedState.AddMember(definition);
// are available, then register definition.
definition._sharedState = privateSharedSizeScope.EnsureSharedState(definition.SharedSizeGroup);
definition._sharedState.AddMember(definition);
}
} }
} }
} }
@ -432,7 +417,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private SharedSizeScope? PrivateSharedSizeScope private SharedSizeScope? PrivateSharedSizeScope
{ {
get { return (SharedSizeScope?)GetValue(PrivateSharedSizeScopeProperty); } get { return GetValue(PrivateSharedSizeScopeProperty); }
} }
/// <summary> /// <summary>
@ -465,7 +450,7 @@ namespace Avalonia.Controls
private SharedSizeState? _sharedState; // reference to shared state object this instance is registered with private SharedSizeState? _sharedState; // reference to shared state object this instance is registered with
[System.Flags] [Flags]
private enum Flags : byte private enum Flags : byte
{ {
// //
@ -520,11 +505,10 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId) internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId)
{ {
Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null);
_sharedSizeScope = sharedSizeScope; _sharedSizeScope = sharedSizeScope;
_sharedSizeGroupId = sharedSizeGroupId; _sharedSizeGroupId = sharedSizeGroupId;
_registry = new List<DefinitionBase>(); _registry = new List<DefinitionBase>();
_layoutUpdated = new EventHandler(OnLayoutUpdated); _layoutUpdated = OnLayoutUpdated;
_broadcastInvalidation = true; _broadcastInvalidation = true;
} }
@ -568,7 +552,7 @@ namespace Avalonia.Controls
{ {
for (int i = 0, count = _registry.Count; i < count; ++i) for (int i = 0, count = _registry.Count; i < count; ++i)
{ {
Grid parentGrid = (Grid)(_registry[i].Parent!); Grid parentGrid = _registry[i].Parent!;
parentGrid.Invalidate(); parentGrid.Invalidate();
} }
_broadcastInvalidation = false; _broadcastInvalidation = false;
@ -703,7 +687,7 @@ namespace Avalonia.Controls
// measure is invalid - it used the old shared size, // measure is invalid - it used the old shared size,
// which is larger than d's (possibly changed) minSize // which is larger than d's (possibly changed) minSize
measureIsValid = (definitionBase.LayoutWasUpdated && measureIsValid = (definitionBase.LayoutWasUpdated &&
MathUtilities.GreaterThanOrClose(definitionBase._minSize, this.MinSize)); MathUtilities.GreaterThanOrClose(definitionBase._minSize, MinSize));
} }
if(!measureIsValid) if(!measureIsValid)
@ -786,8 +770,8 @@ namespace Avalonia.Controls
/// </description></item> /// </description></item>
/// </list> /// </list>
/// </remarks> /// </remarks>
public static readonly AttachedProperty<string> SharedSizeGroupProperty = public static readonly AttachedProperty<string?> SharedSizeGroupProperty =
AvaloniaProperty.RegisterAttached<DefinitionBase, Control, string>( AvaloniaProperty.RegisterAttached<DefinitionBase, Control, string?>(
"SharedSizeGroup", "SharedSizeGroup",
validate: SharedSizeGroupPropertyValueValid); validate: SharedSizeGroupPropertyValueValid);
@ -796,8 +780,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
static DefinitionBase() static DefinitionBase()
{ {
SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged); SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase, string?>(OnSharedSizeGroupPropertyChanged);
PrivateSharedSizeScopeProperty.Changed.AddClassHandler<DefinitionBase>(OnPrivateSharedSizeScopePropertyChanged); PrivateSharedSizeScopeProperty.Changed.AddClassHandler<DefinitionBase, SharedSizeScope?>(OnPrivateSharedSizeScopePropertyChanged);
} }
/// <summary> /// <summary>

9
src/Avalonia.Controls/DockPanel.cs

@ -101,9 +101,6 @@ namespace Avalonia.Controls
Size childConstraint; // Contains the suggested input constraint for this child. Size childConstraint; // Contains the suggested input constraint for this child.
Size childDesiredSize; // Contains the return size from child measure. Size childDesiredSize; // Contains the return size from child measure.
if (child == null)
{ continue; }
// Child constraint is the remaining size; this is total size minus size consumed by previous children. // Child constraint is the remaining size; this is total size minus size consumed by previous children.
childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth), childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth),
Math.Max(0.0, constraint.Height - accumulatedHeight)); Math.Max(0.0, constraint.Height - accumulatedHeight));
@ -122,7 +119,7 @@ namespace Avalonia.Controls
// will deal with computing our minimum size (parentSize) due to that accumulation. // will deal with computing our minimum size (parentSize) due to that accumulation.
// Therefore, we only need to compute our minimum size (parentSize) in dimensions that this child does // Therefore, we only need to compute our minimum size (parentSize) in dimensions that this child does
// not accumulate: Width for Top/Bottom, Height for Left/Right. // not accumulate: Width for Top/Bottom, Height for Left/Right.
switch (DockPanel.GetDock((Control)child)) switch (GetDock(child))
{ {
case Dock.Left: case Dock.Left:
case Dock.Right: case Dock.Right:
@ -164,8 +161,6 @@ namespace Avalonia.Controls
for (int i = 0; i < totalChildrenCount; ++i) for (int i = 0; i < totalChildrenCount; ++i)
{ {
var child = children[i]; var child = children[i];
if (child == null)
{ continue; }
Size childDesiredSize = child.DesiredSize; Size childDesiredSize = child.DesiredSize;
Rect rcChild = new Rect( Rect rcChild = new Rect(
@ -176,7 +171,7 @@ namespace Avalonia.Controls
if (i < nonFillChildrenCount) if (i < nonFillChildrenCount)
{ {
switch (DockPanel.GetDock((Control)child)) switch (GetDock(child))
{ {
case Dock.Left: case Dock.Left:
accumulatedLeft += childDesiredSize.Width; accumulatedLeft += childDesiredSize.Width;

9
src/Avalonia.Controls/Documents/Inline.cs

@ -13,8 +13,8 @@ namespace Avalonia.Controls.Documents
/// <summary> /// <summary>
/// AvaloniaProperty for <see cref="TextDecorations" /> property. /// AvaloniaProperty for <see cref="TextDecorations" /> property.
/// </summary> /// </summary>
public static readonly StyledProperty<TextDecorationCollection> TextDecorationsProperty = public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty =
AvaloniaProperty.Register<Inline, TextDecorationCollection>( AvaloniaProperty.Register<Inline, TextDecorationCollection?>(
nameof(TextDecorations)); nameof(TextDecorations));
/// <summary> /// <summary>
@ -28,7 +28,7 @@ namespace Avalonia.Controls.Documents
/// <summary> /// <summary>
/// The TextDecorations property specifies decorations that are added to the text of an element. /// The TextDecorations property specifies decorations that are added to the text of an element.
/// </summary> /// </summary>
public TextDecorationCollection TextDecorations public TextDecorationCollection? TextDecorations
{ {
get { return GetValue(TextDecorationsProperty); } get { return GetValue(TextDecorationsProperty); }
set { SetValue(TextDecorationsProperty, value); } set { SetValue(TextDecorationsProperty, value); }
@ -83,7 +83,8 @@ namespace Avalonia.Controls.Documents
return new GenericTextRunProperties(new Typeface(FontFamily, fontStyle, fontWeight), FontSize, return new GenericTextRunProperties(new Typeface(FontFamily, fontStyle, fontWeight), FontSize,
textDecorations, Foreground, background, BaselineAlignment); textDecorations, Foreground, background, BaselineAlignment);
} }
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);

18
src/Avalonia.Controls/Documents/InlineUIContainer.cs

@ -64,5 +64,23 @@ namespace Avalonia.Controls.Documents
internal override void AppendText(StringBuilder stringBuilder) internal override void AppendText(StringBuilder stringBuilder)
{ {
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ChildProperty)
{
if(change.OldValue is Control oldChild)
{
LogicalChildren.Remove(oldChild);
}
if(change.NewValue is Control newChild)
{
LogicalChildren.Add(newChild);
}
}
}
} }
} }

14
src/Avalonia.Controls/Documents/Span.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
@ -51,6 +52,7 @@ namespace Avalonia.Controls.Documents
} }
} }
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
@ -68,26 +70,26 @@ namespace Avalonia.Controls.Documents
{ {
base.OnInlineHostChanged(oldValue, newValue); base.OnInlineHostChanged(oldValue, newValue);
if (Inlines is not null) Inlines.InlineHost = newValue;
{
Inlines.InlineHost = newValue;
}
} }
private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue) private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue)
{ {
void OnInlinesInvalidated(object? sender, EventArgs e)
=> InlineHost?.Invalidate();
if (oldValue is not null) if (oldValue is not null)
{ {
oldValue.LogicalChildren = null; oldValue.LogicalChildren = null;
oldValue.InlineHost = null; oldValue.InlineHost = null;
oldValue.Invalidated -= (s, e) => InlineHost?.Invalidate(); oldValue.Invalidated -= OnInlinesInvalidated;
} }
if (newValue is not null) if (newValue is not null)
{ {
newValue.LogicalChildren = LogicalChildren; newValue.LogicalChildren = LogicalChildren;
newValue.InlineHost = InlineHost; newValue.InlineHost = InlineHost;
newValue.Invalidated += (s, e) => InlineHost?.Invalidate(); newValue.Invalidated += OnInlinesInvalidated;
} }
} }
} }

230
src/Avalonia.Controls/Grid.cs

@ -164,20 +164,21 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Returns a ColumnDefinitions of column definitions. /// Returns a ColumnDefinitions of column definitions.
/// </summary> /// </summary>
[MemberNotNull(nameof(_extData))]
public ColumnDefinitions ColumnDefinitions public ColumnDefinitions ColumnDefinitions
{ {
get get
{ {
if (_data == null) { _data = new ExtendedData(); } if (_extData == null) { _extData = new ExtendedData(); }
if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; } if (_extData.ColumnDefinitions == null) { _extData.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; }
return (_data.ColumnDefinitions); return (_extData.ColumnDefinitions);
} }
set set
{ {
if (_data == null) { _data = new ExtendedData(); } if (_extData == null) { _extData = new ExtendedData(); }
_data.ColumnDefinitions = value; _extData.ColumnDefinitions = value;
_data.ColumnDefinitions.Parent = this; _extData.ColumnDefinitions.Parent = this;
InvalidateMeasure(); InvalidateMeasure();
} }
} }
@ -185,20 +186,21 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Returns a RowDefinitions of row definitions. /// Returns a RowDefinitions of row definitions.
/// </summary> /// </summary>
[MemberNotNull(nameof(_extData))]
public RowDefinitions RowDefinitions public RowDefinitions RowDefinitions
{ {
get get
{ {
if (_data == null) { _data = new ExtendedData(); } if (_extData == null) { _extData = new ExtendedData(); }
if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitions() { Parent = this }; } if (_extData.RowDefinitions == null) { _extData.RowDefinitions = new RowDefinitions() { Parent = this }; }
return (_data.RowDefinitions); return (_extData.RowDefinitions);
} }
set set
{ {
if (_data == null) { _data = new ExtendedData(); } if (_extData == null) { _extData = new ExtendedData(); }
_data.RowDefinitions = value; _extData.RowDefinitions = value;
_data.RowDefinitions.Parent = this; _extData.RowDefinitions.Parent = this;
InvalidateMeasure(); InvalidateMeasure();
} }
} }
@ -211,7 +213,7 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size constraint) protected override Size MeasureOverride(Size constraint)
{ {
Size gridDesiredSize; Size gridDesiredSize;
ExtendedData extData = ExtData; var extData = _extData;
try try
{ {
@ -221,17 +223,14 @@ namespace Avalonia.Controls
if (extData == null) if (extData == null)
{ {
gridDesiredSize = new Size(); gridDesiredSize = new Size();
var children = this.Children; var children = Children;
for (int i = 0, count = children.Count; i < count; ++i) for (int i = 0, count = children.Count; i < count; ++i)
{ {
var child = children[i]; var child = children[i];
if (child != null) child.Measure(constraint);
{ gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width),
child.Measure(constraint); Math.Max(gridDesiredSize.Height, child.DesiredSize.Height));
gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width),
Math.Max(gridDesiredSize.Height, child.DesiredSize.Height));
}
} }
} }
else else
@ -512,17 +511,14 @@ namespace Avalonia.Controls
{ {
ArrangeOverrideInProgress = true; ArrangeOverrideInProgress = true;
if (_data == null) if (_extData is null)
{ {
var children = this.Children; var children = Children;
for (int i = 0, count = children.Count; i < count; ++i) for (int i = 0, count = children.Count; i < count; ++i)
{ {
var child = children[i]; var child = children[i];
if (child != null) child.Arrange(new Rect(arrangeSize));
{
child.Arrange(new Rect(arrangeSize));
}
} }
} }
else else
@ -532,15 +528,11 @@ namespace Avalonia.Controls
SetFinalSize(DefinitionsU, arrangeSize.Width, true); SetFinalSize(DefinitionsU, arrangeSize.Width, true);
SetFinalSize(DefinitionsV, arrangeSize.Height, false); SetFinalSize(DefinitionsV, arrangeSize.Height, false);
var children = this.Children; var children = Children;
for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell)
{ {
var cell = children[currentCell]; var cell = children[currentCell];
if (cell == null)
{
continue;
}
int columnIndex = PrivateCells[currentCell].ColumnIndex; int columnIndex = PrivateCells[currentCell].ColumnIndex;
int rowIndex = PrivateCells[currentCell].RowIndex; int rowIndex = PrivateCells[currentCell].RowIndex;
@ -599,7 +591,7 @@ namespace Avalonia.Controls
{ {
double value = 0.0; double value = 0.0;
Debug.Assert(_data != null); Debug.Assert(_extData != null);
// actual value calculations require structure to be up-to-date // actual value calculations require structure to be up-to-date
if (!ColumnDefinitionsDirty) if (!ColumnDefinitionsDirty)
@ -621,7 +613,7 @@ namespace Avalonia.Controls
{ {
double value = 0.0; double value = 0.0;
Debug.Assert(_data != null); Debug.Assert(_extData != null);
// actual value calculations require structure to be up-to-date // actual value calculations require structure to be up-to-date
if (!RowDefinitionsDirty) if (!RowDefinitionsDirty)
@ -654,18 +646,20 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Convenience accessor to ValidDefinitionsUStructure bit flag. /// Convenience accessor to ValidDefinitionsUStructure bit flag.
/// </summary> /// </summary>
[MemberNotNull(nameof(_extData))]
internal bool ColumnDefinitionsDirty internal bool ColumnDefinitionsDirty
{ {
get => ColumnDefinitions?.IsDirty ?? false; get => ColumnDefinitions.IsDirty;
set => ColumnDefinitions.IsDirty = value; set => ColumnDefinitions.IsDirty = value;
} }
/// <summary> /// <summary>
/// Convenience accessor to ValidDefinitionsVStructure bit flag. /// Convenience accessor to ValidDefinitionsVStructure bit flag.
/// </summary> /// </summary>
[MemberNotNull(nameof(_extData))]
internal bool RowDefinitionsDirty internal bool RowDefinitionsDirty
{ {
get => RowDefinitions?.IsDirty ?? false; get => RowDefinitions.IsDirty;
set => RowDefinitions.IsDirty = value; set => RowDefinitions.IsDirty = value;
} }
@ -686,8 +680,10 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private void ValidateCellsCore() private void ValidateCellsCore()
{ {
var children = this.Children; Debug.Assert(_extData is not null);
ExtendedData extData = ExtData;
var children = Children;
var extData = _extData!;
extData.CellCachesCollection = new CellCache[children.Count]; extData.CellCachesCollection = new CellCache[children.Count];
extData.CellGroup1 = int.MaxValue; extData.CellGroup1 = int.MaxValue;
@ -702,10 +698,6 @@ namespace Avalonia.Controls
for (int i = PrivateCells.Length - 1; i >= 0; --i) for (int i = PrivateCells.Length - 1; i >= 0; --i)
{ {
var child = children[i]; var child = children[i];
if (child == null)
{
continue;
}
CellCache cell = new CellCache(); CellCache cell = new CellCache();
@ -713,19 +705,19 @@ namespace Avalonia.Controls
// Read indices from the corresponding properties: // Read indices from the corresponding properties:
// clamp to value < number_of_columns // clamp to value < number_of_columns
// column >= 0 is guaranteed by property value validation callback // column >= 0 is guaranteed by property value validation callback
cell.ColumnIndex = Math.Min(GetColumn((Control)child), DefinitionsU.Count - 1); cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Count - 1);
// clamp to value < number_of_rows // clamp to value < number_of_rows
// row >= 0 is guaranteed by property value validation callback // row >= 0 is guaranteed by property value validation callback
cell.RowIndex = Math.Min(GetRow((Control)child), DefinitionsV.Count - 1); cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Count - 1);
// Read span properties: // Read span properties:
// clamp to not exceed beyond right side of the grid // clamp to not exceed beyond right side of the grid
// column_span > 0 is guaranteed by property value validation callback // column_span > 0 is guaranteed by property value validation callback
cell.ColumnSpan = Math.Min(GetColumnSpan((Control)child), DefinitionsU.Count - cell.ColumnIndex); cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Count - cell.ColumnIndex);
// clamp to not exceed beyond bottom side of the grid // clamp to not exceed beyond bottom side of the grid
// row_span > 0 is guaranteed by property value validation callback // row_span > 0 is guaranteed by property value validation callback
cell.RowSpan = Math.Min(GetRowSpan((Control)child), DefinitionsV.Count - cell.RowIndex); cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Count - cell.RowIndex);
Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Count); Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Count);
Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Count); Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Count);
@ -792,7 +784,7 @@ namespace Avalonia.Controls
{ {
if (ColumnDefinitionsDirty) if (ColumnDefinitionsDirty)
{ {
ExtendedData extData = ExtData; var extData = _extData;
if (extData.ColumnDefinitions == null) if (extData.ColumnDefinitions == null)
{ {
@ -818,7 +810,7 @@ namespace Avalonia.Controls
ColumnDefinitionsDirty = false; ColumnDefinitionsDirty = false;
} }
Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Count > 0); Debug.Assert(_extData is { DefinitionsU.Count: > 0 });
} }
/// <summary> /// <summary>
@ -833,7 +825,7 @@ namespace Avalonia.Controls
{ {
if (RowDefinitionsDirty) if (RowDefinitionsDirty)
{ {
ExtendedData extData = ExtData; var extData = _extData;
if (extData.RowDefinitions == null) if (extData.RowDefinitions == null)
{ {
@ -859,7 +851,7 @@ namespace Avalonia.Controls
RowDefinitionsDirty = false; RowDefinitionsDirty = false;
} }
Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Count > 0); Debug.Assert(_extData is { DefinitionsV.Count: > 0 });
} }
/// <summary> /// <summary>
@ -965,8 +957,7 @@ namespace Avalonia.Controls
bool ignoreDesiredSizeU, bool ignoreDesiredSizeU,
bool forceInfinityV) bool forceInfinityV)
{ {
bool unusedHasDesiredSizeUChanged; MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out _);
MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out unusedHasDesiredSizeUChanged);
} }
/// <summary> /// <summary>
@ -994,7 +985,7 @@ namespace Avalonia.Controls
return; return;
} }
var children = this.Children; var children = Children;
Hashtable? spanStore = null; Hashtable? spanStore = null;
bool ignoreDesiredSizeV = forceInfinityV; bool ignoreDesiredSizeV = forceInfinityV;
@ -1101,8 +1092,6 @@ namespace Avalonia.Controls
int cell, int cell,
bool forceInfinityV) bool forceInfinityV)
{ {
double cellMeasureWidth; double cellMeasureWidth;
double cellMeasureHeight; double cellMeasureHeight;
@ -1144,15 +1133,9 @@ namespace Avalonia.Controls
} }
var child = this.Children[cell]; var child = Children[cell];
if (child != null) Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight);
{ child.Measure(childConstraint);
Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight);
child.Measure(childConstraint);
}
} }
/// <summary> /// <summary>
@ -1230,7 +1213,7 @@ namespace Avalonia.Controls
// avoid processing when asked to distribute "0" // avoid processing when asked to distribute "0"
if (!MathUtilities.IsZero(requestedSize)) if (!MathUtilities.IsZero(requestedSize))
{ {
DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting DefinitionBase?[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting
int end = start + count; int end = start + count;
int autoDefinitionsCount = 0; int autoDefinitionsCount = 0;
double rangeMinSize = 0; double rangeMinSize = 0;
@ -1288,20 +1271,24 @@ namespace Avalonia.Controls
Array.Sort(tempDefinitions, 0, count, s_spanPreferredDistributionOrderComparer); Array.Sort(tempDefinitions, 0, count, s_spanPreferredDistributionOrderComparer);
for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i) for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i)
{ {
var tempDefinition = tempDefinitions[i]!;
// sanity check: only auto definitions allowed in this loop // sanity check: only auto definitions allowed in this loop
Debug.Assert(tempDefinitions[i].UserSize.IsAuto); Debug.Assert(tempDefinition.UserSize.IsAuto);
// adjust sizeToDistribute value by subtracting auto definition min size // adjust sizeToDistribute value by subtracting auto definition min size
sizeToDistribute -= (tempDefinitions[i].MinSize); sizeToDistribute -= (tempDefinition.MinSize);
} }
for (; i < count; ++i) for (; i < count; ++i)
{ {
var tempDefinition = tempDefinitions[i]!;
// sanity check: no auto definitions allowed in this loop // sanity check: no auto definitions allowed in this loop
Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); Debug.Assert(!tempDefinition.UserSize.IsAuto);
double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize); double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinition.PreferredSize);
if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); } if (newMinSize > tempDefinition.MinSize) { tempDefinition.UpdateMinSize(newMinSize); }
sizeToDistribute -= newMinSize; sizeToDistribute -= newMinSize;
} }
@ -1325,24 +1312,28 @@ namespace Avalonia.Controls
Array.Sort(tempDefinitions, 0, count, s_spanMaxDistributionOrderComparer); Array.Sort(tempDefinitions, 0, count, s_spanMaxDistributionOrderComparer);
for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i) for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i)
{ {
var tempDefinition = tempDefinitions[i]!;
// sanity check: no auto definitions allowed in this loop // sanity check: no auto definitions allowed in this loop
Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); Debug.Assert(!tempDefinition.UserSize.IsAuto);
double preferredSize = tempDefinitions[i].PreferredSize; double preferredSize = tempDefinition.PreferredSize;
double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i); double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i);
tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); tempDefinition.UpdateMinSize(Math.Min(newMinSize, tempDefinition.SizeCache));
sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); sizeToDistribute -= (tempDefinition.MinSize - preferredSize);
} }
for (; i < count; ++i) for (; i < count; ++i)
{ {
var tempDefinition = tempDefinitions[i]!;
// sanity check: only auto definitions allowed in this loop // sanity check: only auto definitions allowed in this loop
Debug.Assert(tempDefinitions[i].UserSize.IsAuto); Debug.Assert(tempDefinition.UserSize.IsAuto);
double preferredSize = tempDefinitions[i].MinSize; double preferredSize = tempDefinition.MinSize;
double newMinSize = preferredSize + sizeToDistribute / (count - i); double newMinSize = preferredSize + sizeToDistribute / (count - i);
tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); tempDefinition.UpdateMinSize(Math.Min(newMinSize, tempDefinition.SizeCache));
sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); sizeToDistribute -= (tempDefinition.MinSize - preferredSize);
} }
// sanity check: requested size must all be distributed // sanity check: requested size must all be distributed
@ -1376,8 +1367,10 @@ namespace Avalonia.Controls
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize; var tempDefinition = tempDefinitions[i]!;
tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize);
double deltaSize = (maxMaxSize - tempDefinition.SizeCache) * sizeToDistribute / totalRemainingSize;
tempDefinition.UpdateMinSize(tempDefinition.SizeCache + deltaSize);
} }
} }
else else
@ -1388,7 +1381,7 @@ namespace Avalonia.Controls
// //
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
tempDefinitions[i].UpdateMinSize(equalSize); tempDefinitions[i]!.UpdateMinSize(equalSize);
} }
} }
} }
@ -1429,7 +1422,7 @@ namespace Avalonia.Controls
double availableSize) double availableSize)
{ {
int defCount = definitions.Count; int defCount = definitions.Count;
DefinitionBase[] tempDefinitions = TempDefinitions; DefinitionBase?[] tempDefinitions = TempDefinitions;
int minCount = 0, maxCount = 0; int minCount = 0, maxCount = 0;
double takenSize = 0; double takenSize = 0;
double totalStarWeight = 0.0; double totalStarWeight = 0.0;
@ -1560,8 +1553,8 @@ namespace Avalonia.Controls
remainingStarWeight = totalStarWeight - takenStarWeight; remainingStarWeight = totalStarWeight - takenStarWeight;
} }
double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : Double.PositiveInfinity; double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1]!.MeasureSize : Double.PositiveInfinity;
double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0; double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1]!.SizeCache : -1.0;
// choose the def with larger ratio to the current proportion ("max discrepancy") // choose the def with larger ratio to the current proportion ("max discrepancy")
double proportion = remainingStarWeight / remainingAvailableSize; double proportion = remainingStarWeight / remainingAvailableSize;
@ -1579,13 +1572,13 @@ namespace Avalonia.Controls
double resolvedSize; double resolvedSize;
if (chooseMin == true) if (chooseMin == true)
{ {
resolvedDef = tempDefinitions[minCount - 1]; resolvedDef = tempDefinitions[minCount - 1]!;
resolvedSize = resolvedDef.MinSize; resolvedSize = resolvedDef.MinSize;
--minCount; --minCount;
} }
else else
{ {
resolvedDef = tempDefinitions[defCount + maxCount - 1]; resolvedDef = tempDefinitions[defCount + maxCount - 1]!;
resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize);
--maxCount; --maxCount;
} }
@ -1603,12 +1596,12 @@ namespace Avalonia.Controls
// advance to the next candidate defs, removing ones that have been resolved. // advance to the next candidate defs, removing ones that have been resolved.
// Both counts are advanced, as a def might appear in both lists. // Both counts are advanced, as a def might appear in both lists.
while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0) while (minCount > 0 && tempDefinitions[minCount - 1]!.MeasureSize < 0.0)
{ {
--minCount; --minCount;
tempDefinitions[minCount] = null!; tempDefinitions[minCount] = null!;
} }
while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0) while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1]!.MeasureSize < 0.0)
{ {
--maxCount; --maxCount;
tempDefinitions[defCount + maxCount] = null!; tempDefinitions[defCount + maxCount] = null!;
@ -1637,8 +1630,7 @@ namespace Avalonia.Controls
// resolved as 'min'. Their allocation can be increased to make up the gap. // resolved as 'min'. Their allocation can be increased to make up the gap.
for (int i = minCount; i < minCountPhase2; ++i) for (int i = minCount; i < minCountPhase2; ++i)
{ {
DefinitionBase def = tempDefinitions[i]; if (tempDefinitions[i] is { } def)
if (def != null)
{ {
def.MeasureSize = 1.0; // mark as 'not yet resolved' def.MeasureSize = 1.0; // mark as 'not yet resolved'
++starCount; ++starCount;
@ -1653,8 +1645,7 @@ namespace Avalonia.Controls
// resolved as 'max'. Their allocation can be decreased to make up the gap. // resolved as 'max'. Their allocation can be decreased to make up the gap.
for (int i = maxCount; i < maxCountPhase2; ++i) for (int i = maxCount; i < maxCountPhase2; ++i)
{ {
DefinitionBase def = tempDefinitions[defCount + i]; if (tempDefinitions[defCount + i] is { } def)
if (def != null)
{ {
def.MeasureSize = 1.0; // mark as 'not yet resolved' def.MeasureSize = 1.0; // mark as 'not yet resolved'
++starCount; ++starCount;
@ -1695,7 +1686,7 @@ namespace Avalonia.Controls
totalStarWeight = 0.0; totalStarWeight = 0.0;
for (int i = 0; i < starCount; ++i) for (int i = 0; i < starCount; ++i)
{ {
DefinitionBase def = tempDefinitions[i]; DefinitionBase def = tempDefinitions[i]!;
totalStarWeight += def.MeasureSize; totalStarWeight += def.MeasureSize;
def.SizeCache = totalStarWeight; def.SizeCache = totalStarWeight;
} }
@ -1703,7 +1694,7 @@ namespace Avalonia.Controls
// resolve the defs, in decreasing order of weight // resolve the defs, in decreasing order of weight
for (int i = starCount - 1; i >= 0; --i) for (int i = starCount - 1; i >= 0; --i)
{ {
DefinitionBase def = tempDefinitions[i]; DefinitionBase def = tempDefinitions[i]!;
double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0;
// min and max should have no effect by now, but just in case... // min and max should have no effect by now, but just in case...
@ -2095,7 +2086,7 @@ namespace Avalonia.Controls
{ {
// DpiScale dpiScale = GetDpi(); // DpiScale dpiScale = GetDpi();
// double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; // double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY;
var dpi = (VisualRoot as Layout.ILayoutRoot)?.LayoutScaling ?? 1.0; var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
double[] roundingErrors = RoundingErrors; double[] roundingErrors = RoundingErrors;
double roundedTakenSize = 0.0; double roundedTakenSize = 0.0;
@ -2302,8 +2293,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private void SetValid() private void SetValid()
{ {
ExtendedData extData = ExtData; if (_extData is { } extData)
if (extData != null)
{ {
// for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid (); // for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid ();
// for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid (); // for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid ();
@ -2330,12 +2320,12 @@ namespace Avalonia.Controls
if (ShowGridLines && (_gridLinesRenderer == null)) if (ShowGridLines && (_gridLinesRenderer == null))
{ {
_gridLinesRenderer = new GridLinesRenderer(); _gridLinesRenderer = new GridLinesRenderer();
this.VisualChildren.Add(_gridLinesRenderer); VisualChildren.Add(_gridLinesRenderer);
} }
if ((!ShowGridLines) && (_gridLinesRenderer != null)) if ((!ShowGridLines) && (_gridLinesRenderer != null))
{ {
this.VisualChildren.Add(_gridLinesRenderer); VisualChildren.Add(_gridLinesRenderer);
_gridLinesRenderer = null; _gridLinesRenderer = null;
} }
@ -2364,7 +2354,7 @@ namespace Avalonia.Controls
{ {
Grid grid = (Grid)d; Grid grid = (Grid)d;
if (grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway if (grid._extData != null // trivial grid is 1 by 1. there is no grid lines anyway
&& grid.ListenToNotifications) && grid.ListenToNotifications)
{ {
grid.InvalidateVisual(); grid.InvalidateVisual();
@ -2375,13 +2365,11 @@ namespace Avalonia.Controls
private static void OnCellAttachedPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) private static void OnCellAttachedPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{ {
Visual? child = d as Visual; if (d is Visual child)
if (child != null)
{ {
Grid? grid = child.GetVisualParent<Grid>(); Grid? grid = child.GetVisualParent<Grid>();
if (grid != null if (grid != null
&& grid.ExtData != null && grid._extData != null
&& grid.ListenToNotifications) && grid.ListenToNotifications)
{ {
grid.CellsStructureDirty = true; grid.CellsStructureDirty = true;
@ -2427,7 +2415,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private IReadOnlyList<DefinitionBase> DefinitionsU private IReadOnlyList<DefinitionBase> DefinitionsU
{ {
get { return (ExtData.DefinitionsU!); } get { return _extData!.DefinitionsU!; }
} }
/// <summary> /// <summary>
@ -2435,17 +2423,19 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private IReadOnlyList<DefinitionBase> DefinitionsV private IReadOnlyList<DefinitionBase> DefinitionsV
{ {
get { return (ExtData.DefinitionsV!); } get { return _extData!.DefinitionsV!; }
} }
/// <summary> /// <summary>
/// Helper accessor to layout time array of definitions. /// Helper accessor to layout time array of definitions.
/// </summary> /// </summary>
private DefinitionBase[] TempDefinitions private DefinitionBase?[] TempDefinitions
{ {
get get
{ {
ExtendedData extData = ExtData; Debug.Assert(_extData is not null);
var extData = _extData!;
int requiredLength = Math.Max(DefinitionsU.Count, DefinitionsV.Count) * 2; int requiredLength = Math.Max(DefinitionsU.Count, DefinitionsV.Count) * 2;
if (extData.TempDefinitions == null if (extData.TempDefinitions == null
@ -2516,7 +2506,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private CellCache[] PrivateCells private CellCache[] PrivateCells
{ {
get { return (ExtData.CellCachesCollection!); } get { return _extData!.CellCachesCollection!; }
} }
/// <summary> /// <summary>
@ -2582,18 +2572,10 @@ namespace Avalonia.Controls
set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); } set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); }
} }
/// <summary>
/// Returns reference to extended data bag.
/// </summary>
private ExtendedData ExtData
{
get { return (_data!); }
}
/// <summary> /// <summary>
/// Returns *-weight, adjusted for scale computed during Phase 1 /// Returns *-weight, adjusted for scale computed during Phase 1
/// </summary> /// </summary>
static double StarWeight(DefinitionBase def, double scale) private static double StarWeight(DefinitionBase def, double scale)
{ {
if (scale < 0.0) if (scale < 0.0)
{ {
@ -2609,17 +2591,17 @@ namespace Avalonia.Controls
} }
// Extended data instantiated on demand, for non-trivial case handling only // Extended data instantiated on demand, for non-trivial case handling only
private ExtendedData? _data; private ExtendedData? _extData;
// Grid validity / property caches dirtiness flags // Grid validity / property caches dirtiness flags
private Flags _flags; private Flags _flags;
private GridLinesRenderer? _gridLinesRenderer; private GridLinesRenderer? _gridLinesRenderer;
// Keeps track of definition indices. // Keeps track of definition indices.
int[]? _definitionIndices; private int[]? _definitionIndices;
// Stores unrounded values and rounding errors during layout rounding. // Stores unrounded values and rounding errors during layout rounding.
double[]? _roundingErrors; private double[]? _roundingErrors;
// 5 is an arbitrary constant chosen to end the measure loop // 5 is an arbitrary constant chosen to end the measure loop
private const int c_layoutLoopMaxCount = 5; private const int c_layoutLoopMaxCount = 5;
@ -2645,14 +2627,14 @@ namespace Avalonia.Controls
internal int CellGroup2; // index of the first cell in second cell group internal int CellGroup2; // index of the first cell in second cell group
internal int CellGroup3; // index of the first cell in third cell group internal int CellGroup3; // index of the first cell in third cell group
internal int CellGroup4; // index of the first cell in forth cell group internal int CellGroup4; // index of the first cell in forth cell group
internal DefinitionBase[]? TempDefinitions; // temporary array used during layout for various purposes internal DefinitionBase?[]? TempDefinitions; // temporary array used during layout for various purposes
// TempDefinitions.Length == Max(definitionsU.Length, definitionsV.Length) // TempDefinitions.Length == Max(definitionsU.Length, definitionsV.Length)
} }
/// <summary> /// <summary>
/// Grid validity / property caches dirtiness flags /// Grid validity / property caches dirtiness flags
/// </summary> /// </summary>
[System.Flags] [Flags]
private enum Flags private enum Flags
{ {
// //
@ -2768,7 +2750,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// LayoutTimeSizeType is used internally and reflects layout-time size type. /// LayoutTimeSizeType is used internally and reflects layout-time size type.
/// </summary> /// </summary>
[System.Flags] [Flags]
internal enum LayoutTimeSizeType : byte internal enum LayoutTimeSizeType : byte
{ {
None = 0x00, None = 0x00,
@ -3317,7 +3299,7 @@ namespace Avalonia.Controls
internal void UpdateRenderBounds(Size arrangeSize) internal void UpdateRenderBounds(Size arrangeSize)
{ {
_lastArrangeSize = arrangeSize; _lastArrangeSize = arrangeSize;
this.InvalidateVisual(); InvalidateVisual();
} }
private static Size _lastArrangeSize; private static Size _lastArrangeSize;

7
src/Avalonia.Controls/Image.cs

@ -14,8 +14,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Source"/> property. /// Defines the <see cref="Source"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IImage> SourceProperty = public static readonly StyledProperty<IImage?> SourceProperty =
AvaloniaProperty.Register<Image, IImage>(nameof(Source)); AvaloniaProperty.Register<Image, IImage?>(nameof(Source));
/// <summary> /// <summary>
/// Defines the <see cref="Stretch"/> property. /// Defines the <see cref="Stretch"/> property.
@ -42,7 +42,7 @@ namespace Avalonia.Controls
/// Gets or sets the image that will be displayed. /// Gets or sets the image that will be displayed.
/// </summary> /// </summary>
[Content] [Content]
public IImage Source public IImage? Source
{ {
get { return GetValue(SourceProperty); } get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); } set { SetValue(SourceProperty, value); }
@ -66,6 +66,7 @@ namespace Avalonia.Controls
set { SetValue(StretchDirectionProperty, value); } set { SetValue(StretchDirectionProperty, value); }
} }
/// <inheritdoc />
protected override bool BypassFlowDirectionPolicies => true; protected override bool BypassFlowDirectionPolicies => true;
/// <summary> /// <summary>

41
src/Avalonia.Controls/ItemsControl.cs

@ -2,7 +2,6 @@ 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.Collections;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
@ -17,7 +16,6 @@ using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -91,10 +89,11 @@ namespace Avalonia.Controls
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item. /// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
/// </summary> /// </summary>
[AssignBinding] [AssignBinding]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? DisplayMemberBinding public IBinding? DisplayMemberBinding
{ {
get { return GetValue(DisplayMemberBindingProperty); } get => GetValue(DisplayMemberBindingProperty);
set { SetValue(DisplayMemberBindingProperty, value); } set => SetValue(DisplayMemberBindingProperty, value);
} }
private IEnumerable? _items = new AvaloniaList<object>(); private IEnumerable? _items = new AvaloniaList<object>();
@ -106,7 +105,6 @@ namespace Avalonia.Controls
private Tuple<int, Control>? _containerBeingPrepared; private Tuple<int, Control>? _containerBeingPrepared;
private ScrollViewer? _scrollViewer; private ScrollViewer? _scrollViewer;
private ItemsPresenter? _itemsPresenter; private ItemsPresenter? _itemsPresenter;
private IScrollSnapPointsInfo? _scrolSnapPointInfo;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ItemsControl"/> class. /// Initializes a new instance of the <see cref="ItemsControl"/> class.
@ -134,8 +132,8 @@ namespace Avalonia.Controls
[Content] [Content]
public IEnumerable? Items public IEnumerable? Items
{ {
get { return _items; } get => _items;
set { SetAndRaise(ItemsProperty, ref _items, value); } set => SetAndRaise(ItemsProperty, ref _items, value);
} }
/// <summary> /// <summary>
@ -143,8 +141,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ControlTheme? ItemContainerTheme public ControlTheme? ItemContainerTheme
{ {
get { return GetValue(ItemContainerThemeProperty); } get => GetValue(ItemContainerThemeProperty);
set { SetValue(ItemContainerThemeProperty, value); } set => SetValue(ItemContainerThemeProperty, value);
} }
/// <summary> /// <summary>
@ -161,8 +159,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ITemplate<Panel> ItemsPanel public ITemplate<Panel> ItemsPanel
{ {
get { return GetValue(ItemsPanelProperty); } get => GetValue(ItemsPanelProperty);
set { SetValue(ItemsPanelProperty, value); } set => SetValue(ItemsPanelProperty, value);
} }
/// <summary> /// <summary>
@ -171,8 +169,8 @@ namespace Avalonia.Controls
[InheritDataTypeFromItems(nameof(Items))] [InheritDataTypeFromItems(nameof(Items))]
public IDataTemplate? ItemTemplate public IDataTemplate? ItemTemplate
{ {
get { return GetValue(ItemTemplateProperty); } get => GetValue(ItemTemplateProperty);
set { SetValue(ItemTemplateProperty, value); } set => SetValue(ItemTemplateProperty, value);
} }
/// <summary> /// <summary>
@ -221,6 +219,7 @@ namespace Avalonia.Controls
} }
/// <inheritdoc />
public event EventHandler<RoutedEventArgs> HorizontalSnapPointsChanged public event EventHandler<RoutedEventArgs> HorizontalSnapPointsChanged
{ {
add add
@ -240,6 +239,7 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc />
public event EventHandler<RoutedEventArgs> VerticalSnapPointsChanged public event EventHandler<RoutedEventArgs> VerticalSnapPointsChanged
{ {
add add
@ -264,8 +264,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool AreHorizontalSnapPointsRegular public bool AreHorizontalSnapPointsRegular
{ {
get { return GetValue(AreHorizontalSnapPointsRegularProperty); } get => GetValue(AreHorizontalSnapPointsRegularProperty);
set { SetValue(AreHorizontalSnapPointsRegularProperty, value); } set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
} }
/// <summary> /// <summary>
@ -273,8 +273,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool AreVerticalSnapPointsRegular public bool AreVerticalSnapPointsRegular
{ {
get { return GetValue(AreVerticalSnapPointsRegularProperty); } get => GetValue(AreVerticalSnapPointsRegularProperty);
set { SetValue(AreVerticalSnapPointsRegularProperty, value); } set => SetValue(AreVerticalSnapPointsRegularProperty, value);
} }
/// <summary> /// <summary>
@ -424,13 +424,12 @@ namespace Avalonia.Controls
/// <returns>true if the item is (or is eligible to be) its own container; otherwise, false.</returns> /// <returns>true if the item is (or is eligible to be) its own container; otherwise, false.</returns>
protected internal virtual bool IsItemItsOwnContainerOverride(Control item) => true; protected internal virtual bool IsItemItsOwnContainerOverride(Control item) => true;
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
_scrollViewer = e.NameScope.Find<ScrollViewer>("PART_ScrollViewer"); _scrollViewer = e.NameScope.Find<ScrollViewer>("PART_ScrollViewer");
_itemsPresenter = e.NameScope.Find<ItemsPresenter>("PART_ItemsPresenter"); _itemsPresenter = e.NameScope.Find<ItemsPresenter>("PART_ItemsPresenter");
_scrolSnapPointInfo = _itemsPresenter as IScrollSnapPointsInfo;
} }
/// <summary> /// <summary>
@ -477,11 +476,13 @@ namespace Avalonia.Controls
base.OnKeyDown(e); base.OnKeyDown(e);
} }
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() protected override AutomationPeer OnCreateAutomationPeer()
{ {
return new ItemsControlAutomationPeer(this); return new ItemsControlAutomationPeer(this);
} }
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
@ -748,11 +749,13 @@ namespace Avalonia.Controls
return true; return true;
} }
/// <inheritdoc />
public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment) public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{ {
return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List<double>(); return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List<double>();
} }
/// <inheritdoc />
public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset) public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
{ {
offset = 0; offset = 0;

20
src/Avalonia.Controls/LayoutTransformControl.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls
.AddClassHandler<LayoutTransformControl>((x, e) => x.OnLayoutTransformChanged(e)); .AddClassHandler<LayoutTransformControl>((x, e) => x.OnLayoutTransformChanged(e));
ChildProperty.Changed ChildProperty.Changed
.AddClassHandler<LayoutTransformControl>((x, e) => x.OnChildChanged(e)); .AddClassHandler<LayoutTransformControl>((x, _) => x.OnChildChanged());
UseRenderTransformProperty.Changed UseRenderTransformProperty.Changed
.AddClassHandler<LayoutTransformControl>((x, e) => x.OnUseRenderTransformPropertyChanged(e)); .AddClassHandler<LayoutTransformControl>((x, e) => x.OnUseRenderTransformPropertyChanged(e));
@ -146,7 +146,7 @@ namespace Avalonia.Controls
return transformedDesiredSize; return transformedDesiredSize;
} }
IDisposable? _renderTransformChangedEvent; private IDisposable? _renderTransformChangedEvent;
private void OnUseRenderTransformPropertyChanged(AvaloniaPropertyChangedEventArgs e) private void OnUseRenderTransformPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{ {
@ -167,8 +167,7 @@ namespace Avalonia.Controls
.Subscribe( .Subscribe(
(x) => (x) =>
{ {
var target2 = x.Sender as LayoutTransformControl; if (x.Sender is LayoutTransformControl target2)
if (target2 != null)
{ {
target2.LayoutTransform = target2.RenderTransform; target2.LayoutTransform = target2.RenderTransform;
} }
@ -182,7 +181,7 @@ namespace Avalonia.Controls
} }
} }
private void OnChildChanged(AvaloniaPropertyChangedEventArgs e) private void OnChildChanged()
{ {
if (null != TransformRoot) if (null != TransformRoot)
{ {
@ -206,18 +205,18 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method). /// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method).
/// </summary> /// </summary>
private Size _childActualSize = default; private Size _childActualSize;
/// <summary> /// <summary>
/// RenderTransform/MatrixTransform applied to TransformRoot. /// RenderTransform/MatrixTransform applied to TransformRoot.
/// </summary> /// </summary>
private MatrixTransform _matrixTransform = new MatrixTransform(); private readonly MatrixTransform _matrixTransform = new();
/// <summary> /// <summary>
/// Transformation matrix corresponding to _matrixTransform. /// Transformation matrix corresponding to _matrixTransform.
/// </summary> /// </summary>
private Matrix _transformation; private Matrix _transformation;
private IDisposable? _transformChangedEvent = null; private IDisposable? _transformChangedEvent;
/// <summary> /// <summary>
/// Returns true if Size a is smaller than Size b in either dimension. /// Returns true if Size a is smaller than Size b in either dimension.
@ -263,10 +262,7 @@ namespace Avalonia.Controls
// Get the transform matrix and apply it // Get the transform matrix and apply it
_transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); _transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound);
if (null != _matrixTransform) _matrixTransform.Matrix = _transformation;
{
_matrixTransform.Matrix = _transformation;
}
// New transform means re-layout is necessary // New transform means re-layout is necessary
InvalidateMeasure(); InvalidateMeasure();

1
src/Avalonia.Controls/ListBox.cs

@ -104,6 +104,7 @@ namespace Avalonia.Controls
public void UnselectAll() => Selection.Clear(); public void UnselectAll() => Selection.Clear();
protected internal override Control CreateContainerForItemOverride() => new ListBoxItem(); protected internal override Control CreateContainerForItemOverride() => new ListBoxItem();
protected internal override bool IsItemItsOwnContainerOverride(Control item) => item is ListBoxItem;
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnGotFocus(GotFocusEventArgs e) protected override void OnGotFocus(GotFocusEventArgs e)

12
src/Avalonia.Controls/MaskedTextBox.cs

@ -178,12 +178,11 @@ namespace Avalonia.Controls
} }
} }
} }
Type IStyleable.StyleKey => typeof(TextBox); Type IStyleable.StyleKey => typeof(TextBox);
/// <inheritdoc />
protected override void OnGotFocus(GotFocusEventArgs e) protected override void OnGotFocus(GotFocusEventArgs e)
{ {
if (HidePromptOnLeave == true && MaskProvider != null) if (HidePromptOnLeave == true && MaskProvider != null)
@ -193,6 +192,7 @@ namespace Avalonia.Controls
base.OnGotFocus(e); base.OnGotFocus(e);
} }
/// <inheritdoc />
protected override async void OnKeyDown(KeyEventArgs e) protected override async void OnKeyDown(KeyEventArgs e)
{ {
if (MaskProvider == null) if (MaskProvider == null)
@ -271,15 +271,17 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc />
protected override void OnLostFocus(RoutedEventArgs e) protected override void OnLostFocus(RoutedEventArgs e)
{ {
if (HidePromptOnLeave == true && MaskProvider != null) if (HidePromptOnLeave && MaskProvider != null)
{ {
Text = MaskProvider.ToString(!HidePromptOnLeave, true); Text = MaskProvider.ToString(!HidePromptOnLeave, true);
} }
base.OnLostFocus(e); base.OnLostFocus(e);
} }
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
void UpdateMaskProvider() void UpdateMaskProvider()
@ -357,6 +359,8 @@ namespace Avalonia.Controls
} }
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
} }
/// <inheritdoc />
protected override void OnTextInput(TextInputEventArgs e) protected override void OnTextInput(TextInputEventArgs e)
{ {
_ignoreTextChanges = true; _ignoreTextChanges = true;
@ -423,7 +427,7 @@ namespace Avalonia.Controls
return startPosition; return startPosition;
} }
private void RefreshText(MaskedTextProvider provider, int position) private void RefreshText(MaskedTextProvider? provider, int position)
{ {
if (provider != null) if (provider != null)
{ {

22
src/Avalonia.Controls/NativeControlHost.cs

@ -16,19 +16,17 @@ namespace Avalonia.Controls
private IPlatformHandle? _nativeControlHandle; private IPlatformHandle? _nativeControlHandle;
private bool _queuedForDestruction; private bool _queuedForDestruction;
private bool _queuedForMoveResize; private bool _queuedForMoveResize;
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>(); private readonly List<Visual> _propertyChangedSubscriptions = new();
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_currentRoot = e.Root as TopLevel; _currentRoot = e.Root as TopLevel;
var visual = (Visual)this; var visual = (Visual)this;
while (visual != null) while (visual != null)
{ {
if (visual is Visual v) visual.PropertyChanged += PropertyChangedHandler;
{ _propertyChangedSubscriptions.Add(visual);
v.PropertyChanged += PropertyChangedHandler;
_propertyChangedSubscriptions.Add(v);
}
visual = visual.GetVisualParent(); visual = visual.GetVisualParent();
} }
@ -42,15 +40,13 @@ namespace Avalonia.Controls
EnqueueForMoveResize(); EnqueueForMoveResize();
} }
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_currentRoot = null; _currentRoot = null;
if (_propertyChangedSubscriptions != null) foreach (var v in _propertyChangedSubscriptions)
{ v.PropertyChanged -= PropertyChangedHandler;
foreach (var v in _propertyChangedSubscriptions) _propertyChangedSubscriptions.Clear();
v.PropertyChanged -= PropertyChangedHandler;
_propertyChangedSubscriptions.Clear();
}
UpdateHost(); UpdateHost();
} }
@ -128,7 +124,7 @@ namespace Avalonia.Controls
return new Rect(position.Value, bounds.Size); return new Rect(position.Value, bounds.Size);
} }
void EnqueueForMoveResize() private void EnqueueForMoveResize()
{ {
if(_queuedForMoveResize) if(_queuedForMoveResize)
return; return;

20
src/Avalonia.Controls/NativeMenu.Export.cs

@ -12,10 +12,10 @@ namespace Avalonia.Controls
public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty); public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty);
private static readonly AttachedProperty<NativeMenuInfo> s_nativeMenuInfoProperty = private static readonly AttachedProperty<NativeMenuInfo?> s_nativeMenuInfoProperty =
AvaloniaProperty.RegisterAttached<NativeMenu, TopLevel, NativeMenuInfo>("___NativeMenuInfo"); AvaloniaProperty.RegisterAttached<NativeMenu, TopLevel, NativeMenuInfo?>("___NativeMenuInfo");
class NativeMenuInfo private sealed class NativeMenuInfo
{ {
public bool ChangingIsExported { get; set; } public bool ChangingIsExported { get; set; }
public ITopLevelNativeMenuExporter? Exporter { get; } public ITopLevelNativeMenuExporter? Exporter { get; }
@ -33,7 +33,7 @@ namespace Avalonia.Controls
} }
} }
static NativeMenuInfo GetInfo(TopLevel target) private static NativeMenuInfo GetInfo(TopLevel target)
{ {
var rv = target.GetValue(s_nativeMenuInfoProperty); var rv = target.GetValue(s_nativeMenuInfoProperty);
if (rv == null) if (rv == null)
@ -45,18 +45,18 @@ namespace Avalonia.Controls
return rv; return rv;
} }
static void SetIsNativeMenuExported(TopLevel tl, bool value) private static void SetIsNativeMenuExported(TopLevel tl, bool value)
{ {
GetInfo(tl).ChangingIsExported = true; GetInfo(tl).ChangingIsExported = true;
tl.SetValue(IsNativeMenuExportedProperty, value); tl.SetValue(IsNativeMenuExportedProperty, value);
} }
public static readonly AttachedProperty<NativeMenu> MenuProperty public static readonly AttachedProperty<NativeMenu?> MenuProperty
= AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu"); = AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu?>("Menu");
public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu); public static void SetMenu(AvaloniaObject o, NativeMenu? menu) => o.SetValue(MenuProperty, menu);
public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty); public static NativeMenu? GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
static NativeMenu() static NativeMenu()
{ {

2
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -553,7 +553,7 @@ namespace Avalonia.Controls.Platform
} }
} }
protected static IMenuItem? GetMenuItem(Control? item) protected static IMenuItem? GetMenuItem(StyledElement? item)
{ {
while (true) while (true)
{ {

12
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -34,8 +34,8 @@ namespace Avalonia.Controls.Primitives
public static readonly AttachedProperty<Control?> AdornerProperty = public static readonly AttachedProperty<Control?> AdornerProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Control?>("Adorner"); AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Control?>("Adorner");
private static readonly AttachedProperty<AdornedElementInfo> s_adornedElementInfoProperty = private static readonly AttachedProperty<AdornedElementInfo?> s_adornedElementInfoProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo>("AdornedElementInfo"); AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo?>("AdornedElementInfo");
private static readonly AttachedProperty<AdornerLayer?> s_savedAdornerLayerProperty = private static readonly AttachedProperty<AdornerLayer?> s_savedAdornerLayerProperty =
AvaloniaProperty.RegisterAttached<Visual, Visual, AdornerLayer?>("SavedAdornerLayer"); AvaloniaProperty.RegisterAttached<Visual, Visual, AdornerLayer?>("SavedAdornerLayer");
@ -159,8 +159,8 @@ namespace Avalonia.Controls.Primitives
return; return;
} }
AdornerLayer.SetAdornedElement(adorner, visual); SetAdornedElement(adorner, visual);
AdornerLayer.SetIsClipEnabled(adorner, false); SetIsClipEnabled(adorner, false);
((ISetLogicalParent) adorner).SetParent(visual); ((ISetLogicalParent) adorner).SetParent(visual);
layer.Children.Add(adorner); layer.Children.Add(adorner);
@ -177,6 +177,7 @@ namespace Avalonia.Controls.Primitives
((ISetLogicalParent) adorner).SetParent(null); ((ISetLogicalParent) adorner).SetParent(null);
} }
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
foreach (var child in Children) foreach (var child in Children)
@ -199,6 +200,7 @@ namespace Avalonia.Controls.Primitives
return default; return default;
} }
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
foreach (var child in Children) foreach (var child in Children)
@ -217,7 +219,7 @@ namespace Avalonia.Controls.Primitives
} }
else else
{ {
ArrangeChild((Control) child, finalSize); ArrangeChild(child, finalSize);
} }
} }
} }

36
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Reactive;
using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
@ -18,8 +17,8 @@ namespace Avalonia.Controls.Primitives
PopupRoot.TransformProperty.AddOwner<OverlayPopupHost>(); PopupRoot.TransformProperty.AddOwner<OverlayPopupHost>();
private readonly OverlayLayer _overlayLayer; private readonly OverlayLayer _overlayLayer;
private PopupPositionerParameters _positionerParameters = new PopupPositionerParameters(); private readonly ManagedPopupPositioner _positioner;
private ManagedPopupPositioner _positioner; private PopupPositionerParameters _positionerParameters;
private Point _lastRequestedPosition; private Point _lastRequestedPosition;
private bool _shown; private bool _shown;
@ -29,13 +28,16 @@ namespace Avalonia.Controls.Primitives
_positioner = new ManagedPopupPositioner(this); _positioner = new ManagedPopupPositioner(this);
} }
/// <inheritdoc />
public void SetChild(Control? control) public void SetChild(Control? control)
{ {
Content = control; Content = control;
} }
/// <inheritdoc />
public Visual? HostedVisualTreeRoot => null; public Visual? HostedVisualTreeRoot => null;
/// <inheritdoc />
public Transform? Transform public Transform? Transform
{ {
get => GetValue(TransformProperty); get => GetValue(TransformProperty);
@ -48,23 +50,27 @@ namespace Avalonia.Controls.Primitives
set { /* Not currently supported in overlay popups */ } set { /* Not currently supported in overlay popups */ }
} }
protected internal override Interactive? InteractiveParent => Parent; /// <inheritdoc />
protected internal override Interactive? InteractiveParent => (Interactive?)VisualParent;
/// <inheritdoc />
public void Dispose() => Hide(); public void Dispose() => Hide();
/// <inheritdoc />
public void Show() public void Show()
{ {
_overlayLayer.Children.Add(this); _overlayLayer.Children.Add(this);
_shown = true; _shown = true;
} }
/// <inheritdoc />
public void Hide() public void Hide()
{ {
_overlayLayer.Children.Remove(this); _overlayLayer.Children.Remove(this);
_shown = false; _shown = false;
} }
/// <inheritdoc />
public void ConfigurePosition(Visual target, PlacementMode placement, Point offset, public void ConfigurePosition(Visual target, PlacementMode placement, Point offset,
PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None, PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None,
PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All, PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All,
@ -75,6 +81,7 @@ namespace Avalonia.Controls.Primitives
UpdatePosition(); UpdatePosition();
} }
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
if (_positionerParameters.Size != finalSize) if (_positionerParameters.Size != finalSize)
@ -123,17 +130,18 @@ namespace Avalonia.Controls.Primitives
public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver) public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver)
{ {
var platform = TopLevel.GetTopLevel(target)?.PlatformImpl?.CreatePopup(); if (TopLevel.GetTopLevel(target) is { } topLevel && topLevel.PlatformImpl?.CreatePopup() is { } popupImpl)
if (platform != null) {
return new PopupRoot((TopLevel)target.GetVisualRoot()!, platform, dependencyResolver); return new PopupRoot(topLevel, popupImpl, dependencyResolver);
}
var overlayLayer = OverlayLayer.GetOverlayLayer(target);
if (overlayLayer == null)
throw new InvalidOperationException(
"Unable to create IPopupImpl and no overlay layer is found for the target control");
if (OverlayLayer.GetOverlayLayer(target) is { } overlayLayer)
{
return new OverlayPopupHost(overlayLayer);
}
return new OverlayPopupHost(overlayLayer); throw new InvalidOperationException(
"Unable to create IPopupImpl and no overlay layer is found for the target control");
} }
} }
} }

14
src/Avalonia.Controls/Primitives/Popup.cs

@ -120,7 +120,7 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<bool> TopmostProperty = public static readonly StyledProperty<bool> TopmostProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(Topmost)); AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
private bool _isOpenRequested = false; private bool _isOpenRequested;
private bool _isOpen; private bool _isOpen;
private bool _ignoreIsOpenChanged; private bool _ignoreIsOpenChanged;
private PopupOpenState? _openState; private PopupOpenState? _openState;
@ -377,9 +377,9 @@ namespace Avalonia.Controls.Primitives
popupHost.SetChild(Child); popupHost.SetChild(Child);
((ISetLogicalParent)popupHost).SetParent(this); ((ISetLogicalParent)popupHost).SetParent(this);
if (InheritsTransform && placementTarget is Control c) if (InheritsTransform)
{ {
TransformTrackingHelper.Track(c, PlacementTargetTransformChanged) TransformTrackingHelper.Track(placementTarget, PlacementTargetTransformChanged)
.DisposeWith(handlerCleanup); .DisposeWith(handlerCleanup);
} }
else else
@ -518,6 +518,7 @@ namespace Avalonia.Controls.Primitives
Close(); Close();
} }
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
@ -579,7 +580,7 @@ namespace Avalonia.Controls.Primitives
var scaleX = 1.0; var scaleX = 1.0;
var scaleY = 1.0; var scaleY = 1.0;
if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is Matrix m) if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is { } m)
{ {
scaleX = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12); scaleX = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12);
scaleY = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12); scaleY = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12);
@ -623,6 +624,7 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() protected override AutomationPeer OnCreateAutomationPeer()
{ {
return new PopupAutomationPeer(this); return new PopupAutomationPeer(this);
@ -723,7 +725,7 @@ namespace Avalonia.Controls.Primitives
while (e is object && (!e.Focusable || !e.IsEffectivelyEnabled || !e.IsVisible)) while (e is object && (!e.Focusable || !e.IsEffectivelyEnabled || !e.IsVisible))
{ {
e = e.Parent; e = e.VisualParent as Control;
} }
if (e is object) if (e is object)
@ -850,7 +852,7 @@ namespace Avalonia.Controls.Primitives
var popupHost = _openState.PopupHost; var popupHost = _openState.PopupHost;
return popupHost != null && ((Visual)popupHost).IsVisualAncestorOf(visual); return ((Visual)popupHost).IsVisualAncestorOf(visual);
} }
public bool IsPointerOverPopup => ((IInputElement?)_openState?.PopupHost)?.IsPointerOver ?? false; public bool IsPointerOverPopup => ((IInputElement?)_openState?.PopupHost)?.IsPointerOver ?? false;

4
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -72,12 +72,12 @@ namespace Avalonia.Controls.Primitives
/// <remarks> /// <remarks>
/// Popup events are passed to their parent window. This facilitates this. /// Popup events are passed to their parent window. This facilitates this.
/// </remarks> /// </remarks>
protected internal override Interactive? InteractiveParent => Parent; protected internal override Interactive? InteractiveParent => (Interactive?)Parent;
/// <summary> /// <summary>
/// Gets the control that is hosting the popup root. /// Gets the control that is hosting the popup root.
/// </summary> /// </summary>
Visual? IHostedVisualTreeRoot.Host => Parent; Visual? IHostedVisualTreeRoot.Host => VisualParent;
/// <summary> /// <summary>
/// Gets the styling parent of the popup root. /// Gets the styling parent of the popup root.

312
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -3,16 +3,16 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Xml.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Selection; using Avalonia.Controls.Selection;
using Avalonia.Controls.Utils;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -66,6 +66,19 @@ namespace Avalonia.Controls.Primitives
(o, v) => o.SelectedItem = v, (o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
/// <summary>
/// Defines the <see cref="SelectedValue"/> property
/// </summary>
public static readonly StyledProperty<object?> SelectedValueProperty =
AvaloniaProperty.Register<SelectingItemsControl, object?>(nameof(SelectedValue),
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="SelectedValueBinding"/> property
/// </summary>
public static readonly StyledProperty<IBinding?> SelectedValueBindingProperty =
AvaloniaProperty.Register<SelectingItemsControl, IBinding?>(nameof(SelectedValueBinding));
/// <summary> /// <summary>
/// Defines the <see cref="SelectedItems"/> property. /// Defines the <see cref="SelectedItems"/> property.
/// </summary> /// </summary>
@ -129,6 +142,8 @@ namespace Avalonia.Controls.Primitives
private bool _ignoreContainerSelectionChanged; private bool _ignoreContainerSelectionChanged;
private UpdateState? _updateState; private UpdateState? _updateState;
private bool _hasScrolledToSelectedItem; private bool _hasScrolledToSelectedItem;
private BindingHelper? _bindingHelper;
private bool _isSelectionChangeActive;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="SelectingItemsControl"/> class. /// Initializes static members of the <see cref="SelectingItemsControl"/> class.
@ -143,8 +158,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public event EventHandler<SelectionChangedEventArgs>? SelectionChanged public event EventHandler<SelectionChangedEventArgs>? SelectionChanged
{ {
add { AddHandler(SelectionChangedEvent, value); } add => AddHandler(SelectionChangedEvent, value);
remove { RemoveHandler(SelectionChangedEvent, value); } remove => RemoveHandler(SelectionChangedEvent, value);
} }
/// <summary> /// <summary>
@ -152,8 +167,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public bool AutoScrollToSelectedItem public bool AutoScrollToSelectedItem
{ {
get { return GetValue(AutoScrollToSelectedItemProperty); } get => GetValue(AutoScrollToSelectedItemProperty);
set { SetValue(AutoScrollToSelectedItemProperty, value); } set => SetValue(AutoScrollToSelectedItemProperty, value);
} }
/// <summary> /// <summary>
@ -209,6 +224,28 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <summary>
/// Gets the <see cref="IBinding"/> instance used to obtain the
/// <see cref="SelectedValue"/> property
/// </summary>
[AssignBinding]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? SelectedValueBinding
{
get => GetValue(SelectedValueBindingProperty);
set => SetValue(SelectedValueBindingProperty, value);
}
/// <summary>
/// Gets or sets the value of the selected item, obtained using
/// <see cref="SelectedValueBinding"/>
/// </summary>
public object? SelectedValue
{
get => GetValue(SelectedValueProperty);
set => SetValue(SelectedValueProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the selected items. /// Gets or sets the selected items.
/// </summary> /// </summary>
@ -255,6 +292,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Gets or sets the model that holds the current selection. /// Gets or sets the model that holds the current selection.
/// </summary> /// </summary>
[AllowNull]
protected ISelectionModel Selection protected ISelectionModel Selection
{ {
get get
@ -322,8 +360,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public bool IsTextSearchEnabled public bool IsTextSearchEnabled
{ {
get { return GetValue(IsTextSearchEnabledProperty); } get => GetValue(IsTextSearchEnabledProperty);
set { SetValue(IsTextSearchEnabledProperty, value); } set => SetValue(IsTextSearchEnabledProperty, value);
} }
/// <summary> /// <summary>
@ -332,8 +370,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public bool WrapSelection public bool WrapSelection
{ {
get { return GetValue(WrapSelectionProperty); } get => GetValue(WrapSelectionProperty);
set { SetValue(WrapSelectionProperty, value); } set => SetValue(WrapSelectionProperty, value);
} }
/// <summary> /// <summary>
@ -345,8 +383,8 @@ namespace Avalonia.Controls.Primitives
/// </remarks> /// </remarks>
protected SelectionMode SelectionMode protected SelectionMode SelectionMode
{ {
get { return GetValue(SelectionModeProperty); } get => GetValue(SelectionModeProperty);
set { SetValue(SelectionModeProperty, value); } set => SetValue(SelectionModeProperty, value);
} }
/// <summary> /// <summary>
@ -399,6 +437,7 @@ namespace Avalonia.Controls.Primitives
return null; return null;
} }
/// <inheritdoc />
protected override void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) protected override void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
base.ItemsCollectionChanged(sender!, e); base.ItemsCollectionChanged(sender!, e);
@ -409,12 +448,14 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
AutoScrollToSelectedItemIfNecessary(); AutoScrollToSelectedItemIfNecessary();
} }
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
@ -431,6 +472,7 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <inheritdoc />
protected internal override void PrepareContainerForItemOverride(Control element, object? item, int index) protected internal override void PrepareContainerForItemOverride(Control element, object? item, int index)
{ {
base.PrepareContainerForItemOverride(element, item, index); base.PrepareContainerForItemOverride(element, item, index);
@ -447,12 +489,14 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <inheritdoc />
protected override void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex) protected override void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex)
{ {
base.ContainerIndexChangedOverride(container, oldIndex, newIndex); base.ContainerIndexChangedOverride(container, oldIndex, newIndex);
MarkContainerSelected(container, Selection.IsSelected(newIndex)); MarkContainerSelected(container, Selection.IsSelected(newIndex));
} }
/// <inheritdoc />
protected internal override void ClearContainerForItemOverride(Control element) protected internal override void ClearContainerForItemOverride(Control element)
{ {
base.ClearContainerForItemOverride(element); base.ClearContainerForItemOverride(element);
@ -463,7 +507,7 @@ namespace Avalonia.Controls.Primitives
KeyboardNavigation.SetTabOnceActiveElement(panel, null); KeyboardNavigation.SetTabOnceActiveElement(panel, null);
} }
if (element is ISelectable selectable) if (element is ISelectable)
MarkContainerSelected(element, false); MarkContainerSelected(element, false);
} }
@ -498,7 +542,8 @@ namespace Avalonia.Controls.Primitives
DataValidationErrors.SetError(this, error); DataValidationErrors.SetError(this, error);
} }
} }
/// <inheritdoc />
protected override void OnInitialized() protected override void OnInitialized()
{ {
base.OnInitialized(); base.OnInitialized();
@ -509,6 +554,7 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <inheritdoc />
protected override void OnTextInput(TextInputEventArgs e) protected override void OnTextInput(TextInputEventArgs e)
{ {
if (!e.Handled) if (!e.Handled)
@ -551,6 +597,7 @@ namespace Avalonia.Controls.Primitives
base.OnTextInput(e); base.OnTextInput(e);
} }
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
base.OnKeyDown(e); base.OnKeyDown(e);
@ -582,6 +629,7 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
@ -592,7 +640,7 @@ namespace Avalonia.Controls.Primitives
} }
if (change.Property == ItemsProperty && _updateState is null && _selection is object) if (change.Property == ItemsProperty && _updateState is null && _selection is object)
{ {
var newValue = change.GetNewValue<IEnumerable>(); var newValue = change.GetNewValue<IEnumerable?>();
_selection.Source = newValue; _selection.Source = newValue;
if (newValue is null) if (newValue is null)
@ -609,6 +657,60 @@ namespace Avalonia.Controls.Primitives
{ {
WrapFocus = WrapSelection; WrapFocus = WrapSelection;
} }
else if (change.Property == SelectedValueProperty)
{
if (_isSelectionChangeActive)
return;
if (_updateState is not null)
{
_updateState.SelectedValue = change.NewValue;
return;
}
SelectItemWithValue(change.NewValue);
}
else if (change.Property == SelectedValueBindingProperty)
{
var idx = SelectedIndex;
// If no selection is active, don't do anything as SelectedValue is already null
if (idx == -1)
{
return;
}
var value = change.GetNewValue<IBinding>();
if (value is null)
{
// Clearing SelectedValueBinding makes the SelectedValue the item itself
SelectedValue = SelectedItem;
return;
}
var selectedItem = SelectedItem;
try
{
_isSelectionChangeActive = true;
if (_bindingHelper is null)
{
_bindingHelper = new BindingHelper(value);
}
else
{
_bindingHelper.UpdateBinding(value);
}
// Re-evaluate SelectedValue with the new binding
SelectedValue = _bindingHelper.Evaluate(selectedItem);
}
finally
{
_isSelectionChangeActive = false;
}
}
} }
/// <summary> /// <summary>
@ -695,7 +797,7 @@ namespace Avalonia.Controls.Primitives
{ {
if (multi) if (multi)
{ {
if (Selection.IsSelected(index) == true) if (Selection.IsSelected(index))
{ {
Selection.Deselect(index); Selection.Deselect(index);
} }
@ -716,12 +818,10 @@ namespace Avalonia.Controls.Primitives
Selection.Select(index); Selection.Select(index);
} }
if (Presenter?.Panel != null) if (Presenter?.Panel is { } panel)
{ {
var container = ContainerFromIndex(index); var container = ContainerFromIndex(index);
KeyboardNavigation.SetTabOnceActiveElement( KeyboardNavigation.SetTabOnceActiveElement(panel, container);
(InputElement)Presenter.Panel,
container);
} }
} }
@ -815,6 +915,10 @@ namespace Avalonia.Controls.Primitives
new BindingValue<IList?>(SelectedItems)); new BindingValue<IList?>(SelectedItems));
_oldSelectedItems = SelectedItems; _oldSelectedItems = SelectedItems;
} }
else if (e.PropertyName == nameof(ISelectionModel.Source))
{
ClearValue(SelectedValueProperty);
}
} }
/// <summary> /// <summary>
@ -845,6 +949,11 @@ namespace Avalonia.Controls.Primitives
Mark(i, false); Mark(i, false);
} }
if (!_isSelectionChangeActive)
{
UpdateSelectedValueFromItem();
}
var route = BuildEventRoute(SelectionChangedEvent); var route = BuildEventRoute(SelectionChangedEvent);
if (route.HasHandlers) if (route.HasHandlers)
@ -871,6 +980,109 @@ namespace Avalonia.Controls.Primitives
} }
} }
private void SelectItemWithValue(object? value)
{
if (ItemCount == 0 || _isSelectionChangeActive)
return;
try
{
_isSelectionChangeActive = true;
var si = FindItemWithValue(value);
if (si != AvaloniaProperty.UnsetValue)
{
SelectedItem = si;
}
else
{
SelectedItem = null;
}
}
finally
{
_isSelectionChangeActive = false;
}
}
private object FindItemWithValue(object? value)
{
if (ItemCount == 0 || value is null)
{
return AvaloniaProperty.UnsetValue;
}
var items = Items;
var binding = SelectedValueBinding;
if (binding is null)
{
// No SelectedValueBinding set, SelectedValue is the item itself
// Still verify the value passed in is in the Items list
var index = items!.IndexOf(value);
if (index >= 0)
{
return value;
}
else
{
return AvaloniaProperty.UnsetValue;
}
}
_bindingHelper ??= new BindingHelper(binding);
// Matching UWP behavior, if duplicates are present, return the first item matching
// the SelectedValue provided
foreach (var item in items!)
{
var itemValue = _bindingHelper.Evaluate(item);
if (itemValue.Equals(value))
{
return item;
}
}
return AvaloniaProperty.UnsetValue;
}
private void UpdateSelectedValueFromItem()
{
if (_isSelectionChangeActive)
return;
var binding = SelectedValueBinding;
var item = SelectedItem;
if (binding is null || item is null)
{
// No SelectedValueBinding, SelectedValue is Item itself
try
{
_isSelectionChangeActive = true;
SelectedValue = item;
}
finally
{
_isSelectionChangeActive = false;
}
return;
}
_bindingHelper ??= new BindingHelper(binding);
try
{
_isSelectionChangeActive = true;
SelectedValue = _bindingHelper.Evaluate(item);
}
finally
{
_isSelectionChangeActive = false;
}
}
private void AutoScrollToSelectedItemIfNecessary() private void AutoScrollToSelectedItemIfNecessary()
{ {
if (AutoScrollToSelectedItem && if (AutoScrollToSelectedItem &&
@ -940,7 +1152,7 @@ namespace Avalonia.Controls.Primitives
private void UpdateContainerSelection() private void UpdateContainerSelection()
{ {
if (Presenter?.Panel is Panel panel) if (Presenter?.Panel is { } panel)
{ {
foreach (var container in panel.Children) foreach (var container in panel.Children)
{ {
@ -1037,6 +1249,13 @@ namespace Avalonia.Controls.Primitives
Selection.Clear(); Selection.Clear();
} }
if (state.SelectedValue.HasValue)
{
var item = FindItemWithValue(state.SelectedValue.Value);
if (item != AvaloniaProperty.UnsetValue)
state.SelectedItem = item;
}
if (state.SelectedIndex.HasValue) if (state.SelectedIndex.HasValue)
{ {
SelectedIndex = state.SelectedIndex.Value; SelectedIndex = state.SelectedIndex.Value;
@ -1098,6 +1317,7 @@ namespace Avalonia.Controls.Primitives
{ {
private Optional<int> _selectedIndex; private Optional<int> _selectedIndex;
private Optional<object?> _selectedItem; private Optional<object?> _selectedItem;
private Optional<object?> _selectedValue;
public int UpdateCount { get; set; } public int UpdateCount { get; set; }
public Optional<ISelectionModel> Selection { get; set; } public Optional<ISelectionModel> Selection { get; set; }
@ -1122,6 +1342,54 @@ namespace Avalonia.Controls.Primitives
_selectedIndex = default; _selectedIndex = default;
} }
} }
public Optional<object?> SelectedValue
{
get => _selectedValue;
set
{
_selectedValue = value;
}
}
}
/// <summary>
/// Helper class for evaluating a binding from an Item and IBinding instance
/// </summary>
private class BindingHelper : StyledElement
{
public BindingHelper(IBinding binding)
{
UpdateBinding(binding);
}
public static readonly StyledProperty<object> ValueProperty =
AvaloniaProperty.Register<BindingHelper, object>("Value");
public object Evaluate(object? dataContext)
{
dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext));
// Only update the DataContext if necessary
if (!dataContext.Equals(DataContext))
DataContext = dataContext;
return GetValue(ValueProperty);
}
public void UpdateBinding(IBinding binding)
{
_lastBinding = binding;
var ib = binding.Initiate(this, ValueProperty);
if (ib is null)
{
throw new InvalidOperationException("Unable to create binding");
}
BindingOperations.Apply(this, ValueProperty, ib, null);
}
private IBinding? _lastBinding;
} }
} }
} }

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

@ -290,12 +290,6 @@ namespace Avalonia.Controls.Primitives
ApplyTemplatedParent(child, this); ApplyTemplatedParent(child, this);
((ISetLogicalParent)child).SetParent(this); ((ISetLogicalParent)child).SetParent(this);
VisualChildren.Add(child); VisualChildren.Add(child);
// Existing code kinda expect to see a NameScope even if it's empty
if (nameScope == null)
{
nameScope = new NameScope();
}
var e = new TemplateAppliedEventArgs(nameScope); var e = new TemplateAppliedEventArgs(nameScope);
OnApplyTemplate(e); OnApplyTemplate(e);
@ -320,6 +314,7 @@ namespace Avalonia.Controls.Primitives
return this; return this;
} }
/// <inheritdoc />
protected sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) protected sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
{ {
var count = VisualChildren.Count; var count = VisualChildren.Count;

8
src/Avalonia.Controls/Primitives/TextSearch.cs

@ -11,15 +11,15 @@ namespace Avalonia.Controls.Primitives
/// Defines the Text attached property. /// Defines the Text attached property.
/// This text will be considered during text search in <see cref="SelectingItemsControl"/> (such as <see cref="ComboBox"/>) /// This text will be considered during text search in <see cref="SelectingItemsControl"/> (such as <see cref="ComboBox"/>)
/// </summary> /// </summary>
public static readonly AttachedProperty<string> TextProperty public static readonly AttachedProperty<string?> TextProperty
= AvaloniaProperty.RegisterAttached<Interactive, string>("Text", typeof(TextSearch)); = AvaloniaProperty.RegisterAttached<Interactive, string?>("Text", typeof(TextSearch));
/// <summary> /// <summary>
/// Sets the <see cref="TextProperty"/> for a control. /// Sets the <see cref="TextProperty"/> for a control.
/// </summary> /// </summary>
/// <param name="control">The control</param> /// <param name="control">The control</param>
/// <param name="text">The search text to set</param> /// <param name="text">The search text to set</param>
public static void SetText(Control control, string text) public static void SetText(Control control, string? text)
{ {
control.SetValue(TextProperty, text); control.SetValue(TextProperty, text);
} }
@ -29,7 +29,7 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
/// <param name="control">The control</param> /// <param name="control">The control</param>
/// <returns>The property value</returns> /// <returns>The property value</returns>
public static string GetText(Control control) public static string? GetText(Control control)
{ {
return control.GetValue(TextProperty); return control.GetValue(TextProperty);
} }

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

@ -20,7 +20,7 @@ namespace Avalonia.Controls.Primitives
nameof(IsChecked), nameof(IsChecked),
o => o.IsChecked, o => o.IsChecked,
(o, v) => o.IsChecked = v, (o, v) => o.IsChecked = v,
unsetValue: null, unsetValue: false,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>

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

@ -5,7 +5,6 @@
using System; using System;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -31,14 +30,14 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<Orientation> OrientationProperty = public static readonly StyledProperty<Orientation> OrientationProperty =
ScrollBar.OrientationProperty.AddOwner<Track>(); ScrollBar.OrientationProperty.AddOwner<Track>();
public static readonly StyledProperty<Thumb> ThumbProperty = public static readonly StyledProperty<Thumb?> ThumbProperty =
AvaloniaProperty.Register<Track, Thumb>(nameof(Thumb)); AvaloniaProperty.Register<Track, Thumb?>(nameof(Thumb));
public static readonly StyledProperty<Button> IncreaseButtonProperty = public static readonly StyledProperty<Button?> IncreaseButtonProperty =
AvaloniaProperty.Register<Track, Button>(nameof(IncreaseButton)); AvaloniaProperty.Register<Track, Button?>(nameof(IncreaseButton));
public static readonly StyledProperty<Button> DecreaseButtonProperty = public static readonly StyledProperty<Button?> DecreaseButtonProperty =
AvaloniaProperty.Register<Track, Button>(nameof(DecreaseButton)); AvaloniaProperty.Register<Track, Button?>(nameof(DecreaseButton));
public static readonly StyledProperty<bool> IsDirectionReversedProperty = public static readonly StyledProperty<bool> IsDirectionReversedProperty =
AvaloniaProperty.Register<Track, bool>(nameof(IsDirectionReversed)); AvaloniaProperty.Register<Track, bool>(nameof(IsDirectionReversed));
@ -94,19 +93,19 @@ namespace Avalonia.Controls.Primitives
} }
[Content] [Content]
public Thumb Thumb public Thumb? Thumb
{ {
get { return GetValue(ThumbProperty); } get { return GetValue(ThumbProperty); }
set { SetValue(ThumbProperty, value); } set { SetValue(ThumbProperty, value); }
} }
public Button IncreaseButton public Button? IncreaseButton
{ {
get { return GetValue(IncreaseButtonProperty); } get { return GetValue(IncreaseButtonProperty); }
set { SetValue(IncreaseButtonProperty, value); } set { SetValue(IncreaseButtonProperty, value); }
} }
public Button DecreaseButton public Button? DecreaseButton
{ {
get { return GetValue(DecreaseButtonProperty); } get { return GetValue(DecreaseButtonProperty); }
set { SetValue(DecreaseButtonProperty, value); } set { SetValue(DecreaseButtonProperty, value); }

16
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -12,10 +11,10 @@ namespace Avalonia.Controls.Primitives
private const int OverlayZIndex = int.MaxValue - 97; private const int OverlayZIndex = int.MaxValue - 97;
private ILogicalRoot? _logicalRoot; private ILogicalRoot? _logicalRoot;
private readonly List<Control> _layers = new List<Control>(); private readonly List<Control> _layers = new();
public static readonly StyledProperty<ChromeOverlayLayer> ChromeOverlayLayerProperty = public static readonly StyledProperty<ChromeOverlayLayer?> ChromeOverlayLayerProperty =
AvaloniaProperty.Register<VisualLayerManager, ChromeOverlayLayer>(nameof(ChromeOverlayLayer)); AvaloniaProperty.Register<VisualLayerManager, ChromeOverlayLayer?>(nameof(ChromeOverlayLayer));
public bool IsPopup { get; set; } public bool IsPopup { get; set; }
@ -81,7 +80,7 @@ namespace Avalonia.Controls.Primitives
} }
} }
T? FindLayer<T>() where T : class private T? FindLayer<T>() where T : class
{ {
foreach (var layer in _layers) foreach (var layer in _layers)
if (layer is T match) if (layer is T match)
@ -89,7 +88,7 @@ namespace Avalonia.Controls.Primitives
return null; return null;
} }
void AddLayer(Control layer, int zindex) private void AddLayer(Control layer, int zindex)
{ {
_layers.Add(layer); _layers.Add(layer);
((ISetLogicalParent)layer).SetParent(this); ((ISetLogicalParent)layer).SetParent(this);
@ -101,6 +100,7 @@ namespace Avalonia.Controls.Primitives
InvalidateArrange(); InvalidateArrange();
} }
/// <inheritdoc />
protected override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) protected override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
{ {
foreach (var l in _layers) foreach (var l in _layers)
@ -109,6 +109,7 @@ namespace Avalonia.Controls.Primitives
base.NotifyChildResourcesChanged(e); base.NotifyChildResourcesChanged(e);
} }
/// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
base.OnAttachedToLogicalTree(e); base.OnAttachedToLogicalTree(e);
@ -118,6 +119,7 @@ namespace Avalonia.Controls.Primitives
((ILogical)l).NotifyAttachedToLogicalTree(e); ((ILogical)l).NotifyAttachedToLogicalTree(e);
} }
/// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
_logicalRoot = null; _logicalRoot = null;
@ -126,6 +128,7 @@ namespace Avalonia.Controls.Primitives
((ILogical)l).NotifyDetachedFromLogicalTree(e); ((ILogical)l).NotifyDetachedFromLogicalTree(e);
} }
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
foreach (var l in _layers) foreach (var l in _layers)
@ -133,6 +136,7 @@ namespace Avalonia.Controls.Primitives
return base.MeasureOverride(availableSize); return base.MeasureOverride(availableSize);
} }
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
foreach (var l in _layers) foreach (var l in _layers)

9
src/Avalonia.Controls/ProgressBar.cs

@ -1,4 +1,6 @@
using System; using System;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Automation.Peers;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
@ -228,10 +230,15 @@ namespace Avalonia.Controls
UpdateIndicator(); UpdateIndicator();
} }
protected override AutomationPeer OnCreateAutomationPeer()
{
return new ProgressBarAutomationPeer(this);
}
private void UpdateIndicator() private void UpdateIndicator()
{ {
// Gets the size of the parent indicator container // Gets the size of the parent indicator container
var barSize = _indicator?.Parent?.Bounds.Size ?? Bounds.Size; var barSize = _indicator?.VisualParent?.Bounds.Size ?? Bounds.Size;
if (_indicator != null) if (_indicator != null)
{ {

7
src/Avalonia.Controls/RelativePanel.cs

@ -33,13 +33,8 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
_childGraph.Clear(); _childGraph.Clear();
foreach (Layoutable child in Children) foreach (var child in Children)
{ {
if (child == null)
{
continue;
}
var node = _childGraph.AddNode(child); var node = _childGraph.AddNode(child);
node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child)); node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child));

44
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -14,8 +14,8 @@ using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
using Key = Avalonia.Input.Key; using Key = Avalonia.Input.Key;
using PixelFormat = Avalonia.Platform.PixelFormat;
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
using ProtocolMouseButton = Avalonia.Remote.Protocol.Input.MouseButton;
namespace Avalonia.Controls.Remote.Server namespace Avalonia.Controls.Remote.Server
{ {
@ -24,7 +24,7 @@ namespace Avalonia.Controls.Remote.Server
{ {
private readonly IAvaloniaRemoteTransportConnection _transport; private readonly IAvaloniaRemoteTransportConnection _transport;
private LockedFramebuffer? _framebuffer; private LockedFramebuffer? _framebuffer;
private object _lock = new object(); private readonly object _lock = new();
private long _lastSentFrame = -1; private long _lastSentFrame = -1;
private long _lastReceivedFrame = -1; private long _lastReceivedFrame = -1;
private long _nextFrameNumber = 1; private long _nextFrameNumber = 1;
@ -50,17 +50,17 @@ namespace Avalonia.Controls.Remote.Server
return r; return r;
} }
private static RawPointerEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed) private static RawPointerEventType GetAvaloniaEventType(ProtocolMouseButton button, bool pressed)
{ {
switch (button) switch (button)
{ {
case Avalonia.Remote.Protocol.Input.MouseButton.Left: case ProtocolMouseButton.Left:
return pressed ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp; return pressed ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp;
case Avalonia.Remote.Protocol.Input.MouseButton.Middle: case ProtocolMouseButton.Middle:
return pressed ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp; return pressed ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp;
case Avalonia.Remote.Protocol.Input.MouseButton.Right: case ProtocolMouseButton.Right:
return pressed ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp; return pressed ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp;
default: default:
@ -68,11 +68,7 @@ namespace Avalonia.Controls.Remote.Server
} }
} }
private static RawInputModifiers GetAvaloniaRawInputModifiers( private static RawInputModifiers GetAvaloniaRawInputModifiers(InputModifiers[]? modifiers)
Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
=> (RawInputModifiers)GetAvaloniaInputModifiers(modifiers);
private static RawInputModifiers GetAvaloniaInputModifiers (Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
{ {
var result = RawInputModifiers.None; var result = RawInputModifiers.None;
@ -85,31 +81,31 @@ namespace Avalonia.Controls.Remote.Server
{ {
switch (modifier) switch (modifier)
{ {
case Avalonia.Remote.Protocol.Input.InputModifiers.Control: case InputModifiers.Control:
result |= RawInputModifiers.Control; result |= RawInputModifiers.Control;
break; break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Alt: case InputModifiers.Alt:
result |= RawInputModifiers.Alt; result |= RawInputModifiers.Alt;
break; break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Shift: case InputModifiers.Shift:
result |= RawInputModifiers.Shift; result |= RawInputModifiers.Shift;
break; break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Windows: case InputModifiers.Windows:
result |= RawInputModifiers.Meta; result |= RawInputModifiers.Meta;
break; break;
case Avalonia.Remote.Protocol.Input.InputModifiers.LeftMouseButton: case InputModifiers.LeftMouseButton:
result |= RawInputModifiers.LeftMouseButton; result |= RawInputModifiers.LeftMouseButton;
break; break;
case Avalonia.Remote.Protocol.Input.InputModifiers.MiddleMouseButton: case InputModifiers.MiddleMouseButton:
result |= RawInputModifiers.MiddleMouseButton; result |= RawInputModifiers.MiddleMouseButton;
break; break;
case Avalonia.Remote.Protocol.Input.InputModifiers.RightMouseButton: case InputModifiers.RightMouseButton:
result |= RawInputModifiers.RightMouseButton; result |= RawInputModifiers.RightMouseButton;
break; break;
} }
@ -187,7 +183,7 @@ namespace Avalonia.Controls.Remote.Server
InputRoot!, InputRoot!,
RawPointerEventType.Move, RawPointerEventType.Move,
new Point(pointer.X, pointer.Y), new Point(pointer.X, pointer.Y),
GetAvaloniaInputModifiers(pointer.Modifiers))); GetAvaloniaRawInputModifiers(pointer.Modifiers)));
}, DispatcherPriority.Input); }, DispatcherPriority.Input);
} }
if(obj is PointerPressedEventMessage pressed) if(obj is PointerPressedEventMessage pressed)
@ -200,7 +196,7 @@ namespace Avalonia.Controls.Remote.Server
InputRoot!, InputRoot!,
GetAvaloniaEventType(pressed.Button, true), GetAvaloniaEventType(pressed.Button, true),
new Point(pressed.X, pressed.Y), new Point(pressed.X, pressed.Y),
GetAvaloniaInputModifiers(pressed.Modifiers))); GetAvaloniaRawInputModifiers(pressed.Modifiers)));
}, DispatcherPriority.Input); }, DispatcherPriority.Input);
} }
if (obj is PointerReleasedEventMessage released) if (obj is PointerReleasedEventMessage released)
@ -213,7 +209,7 @@ namespace Avalonia.Controls.Remote.Server
InputRoot!, InputRoot!,
GetAvaloniaEventType(released.Button, false), GetAvaloniaEventType(released.Button, false),
new Point(released.X, released.Y), new Point(released.X, released.Y),
GetAvaloniaInputModifiers(released.Modifiers))); GetAvaloniaRawInputModifiers(released.Modifiers)));
}, DispatcherPriority.Input); }, DispatcherPriority.Input);
} }
if(obj is ScrollEventMessage scroll) if(obj is ScrollEventMessage scroll)
@ -226,7 +222,7 @@ namespace Avalonia.Controls.Remote.Server
InputRoot!, InputRoot!,
new Point(scroll.X, scroll.Y), new Point(scroll.X, scroll.Y),
new Vector(scroll.DeltaX, scroll.DeltaY), new Vector(scroll.DeltaX, scroll.DeltaY),
GetAvaloniaInputModifiers(scroll.Modifiers))); GetAvaloniaRawInputModifiers(scroll.Modifiers)));
}, DispatcherPriority.Input); }, DispatcherPriority.Input);
} }
if(obj is KeyEventMessage key) if(obj is KeyEventMessage key)
@ -274,8 +270,8 @@ namespace Avalonia.Controls.Remote.Server
} }
public override IEnumerable<object> Surfaces => new[] { this }; public override IEnumerable<object> Surfaces => new[] { this };
FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format) private FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format)
{ {
var scalingX = _dpi.X / 96.0; var scalingX = _dpi.X / 96.0;
var scalingY = _dpi.Y / 96.0; var scalingY = _dpi.Y / 96.0;

13
src/Avalonia.Controls/Slider.cs

@ -81,11 +81,11 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="TicksProperty"/> property. /// Defines the <see cref="TicksProperty"/> 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 = false; private bool _isDragging;
private Track? _track; private Track? _track;
private Button? _decreaseButton; private Button? _decreaseButton;
private Button? _increaseButton; private Button? _increaseButton;
@ -124,7 +124,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the ticks to be drawn on the tick bar. /// Defines the ticks to be drawn on the tick bar.
/// </summary> /// </summary>
public AvaloniaList<double> Ticks public AvaloniaList<double>? Ticks
{ {
get => GetValue(TicksProperty); get => GetValue(TicksProperty);
set => SetValue(TicksProperty, value); set => SetValue(TicksProperty, value);
@ -215,6 +215,7 @@ namespace Avalonia.Controls
_pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel); _pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel);
} }
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
base.OnKeyDown(e); base.OnKeyDown(e);
@ -350,8 +351,8 @@ namespace Avalonia.Controls
var orient = Orientation == Orientation.Horizontal; var orient = Orientation == Orientation.Horizontal;
var thumbLength = (orient var thumbLength = (orient
? _track.Thumb.Bounds.Width ? _track.Thumb?.Bounds.Width ?? 0.0
: _track.Thumb.Bounds.Height) + double.Epsilon; : _track.Thumb?.Bounds.Height ?? 0.0) + double.Epsilon;
var trackLength = (orient var trackLength = (orient
? _track.Bounds.Width ? _track.Bounds.Width
: _track.Bounds.Height) - thumbLength; : _track.Bounds.Height) - thumbLength;
@ -367,6 +368,7 @@ namespace Avalonia.Controls
Value = IsSnapToTickEnabled ? SnapToTick(finalValue) : finalValue; Value = IsSnapToTickEnabled ? SnapToTick(finalValue) : finalValue;
} }
/// <inheritdoc />
protected override void UpdateDataValidation( protected override void UpdateDataValidation(
AvaloniaProperty property, AvaloniaProperty property,
BindingValueType state, BindingValueType state,
@ -378,6 +380,7 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);

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

Loading…
Cancel
Save