diff --git a/.editorconfig b/.editorconfig
index e6ae266849..a144ec8843 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -55,16 +55,17 @@ dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
-# static fields should have s_ prefix
-dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
-dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
-dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
+# private static fields should have s_ prefix
+dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion
+dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_should_have_prefix.style = private_static_prefix_style
-dotnet_naming_symbols.static_fields.applicable_kinds = field
-dotnet_naming_symbols.static_fields.required_modifiers = static
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
-dotnet_naming_style.static_prefix_style.required_prefix = s_
-dotnet_naming_style.static_prefix_style.capitalization = camel_case
+dotnet_naming_style.private_static_prefix_style.required_prefix = s_
+dotnet_naming_style.private_static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
@@ -117,7 +118,7 @@ csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
-csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
@@ -211,5 +212,5 @@ indent_size = 2
# Shell scripts
[*.sh]
end_of_line = lf
-[*.{cmd, bat}]
+[*.{cmd,bat}]
end_of_line = crlf
diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props
index 620ec58ff3..75d317be1a 100644
--- a/build/HarfBuzzSharp.props
+++ b/build/HarfBuzzSharp.props
@@ -1,7 +1,7 @@
-
-
-
+
+
+
diff --git a/build/ImageSharp.props b/build/ImageSharp.props
index 178c274ac9..66e6580070 100644
--- a/build/ImageSharp.props
+++ b/build/ImageSharp.props
@@ -1,5 +1,5 @@
-
+
diff --git a/build/Moq.props b/build/Moq.props
index 9e2fd1db5d..357f0c9a5f 100644
--- a/build/Moq.props
+++ b/build/Moq.props
@@ -1,5 +1,5 @@
-
+
diff --git a/build/SharedVersion.props b/build/SharedVersion.props
index eca3ba37b0..2849262591 100644
--- a/build/SharedVersion.props
+++ b/build/SharedVersion.props
@@ -3,6 +3,7 @@
Avalonia
11.0.999
+ Avalonia Team
Copyright 2022 © The AvaloniaUI Project
https://avaloniaui.net
https://github.com/AvaloniaUI/Avalonia/
diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index 31619399f9..f45addaa2a 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,7 +1,7 @@
-
-
-
+
+
+
diff --git a/build/XUnit.props b/build/XUnit.props
index 17ead91aa3..3c89c8b52b 100644
--- a/build/XUnit.props
+++ b/build/XUnit.props
@@ -1,13 +1,12 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj b/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj
index d0fb614840..733a4b7194 100644
--- a/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj
+++ b/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj
@@ -9,8 +9,8 @@
-
-
+
+
diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
index e4c83dca49..e465e9caf3 100644
--- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
+++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
@@ -31,7 +31,6 @@
-
diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index 54c0cb0655..b116e4c789 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -120,30 +120,36 @@
-
-
-
- NonOwned
- Owned
- Modal
-
-
- Manual
- CenterScreen
- CenterOwner
-
-
- Normal
- Minimized
- Maximized
- FullScreen
-
-
-
-
-
-
-
+
+
+
+
+ NonOwned
+ Owned
+ Modal
+
+
+ Manual
+ CenterScreen
+ CenterOwner
+
+
+ Normal
+ Minimized
+ Maximized
+ FullScreen
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index 841947673a..3cd5350cce 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -7,9 +7,13 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Interactivity;
+using Avalonia.Media;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
using Microsoft.CodeAnalysis;
+using Avalonia.Controls.Primitives;
+using Avalonia.Threading;
+using Avalonia.Controls.Primitives.PopupPositioning;
namespace IntegrationTestApp
{
@@ -103,6 +107,89 @@ namespace IntegrationTestApp
}
}
+ private void ShowTransparentWindow()
+ {
+ // Show a background window to make sure the color behind the transparent window is
+ // a known color (green).
+ var backgroundWindow = new Window
+ {
+ Title = "Transparent Window Background",
+ Name = "TransparentWindowBackground",
+ Width = 300,
+ Height = 300,
+ Background = Brushes.Green,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ };
+
+ // This is the transparent window with a red circle.
+ var window = new Window
+ {
+ Title = "Transparent Window",
+ Name = "TransparentWindow",
+ SystemDecorations = SystemDecorations.None,
+ Background = Brushes.Transparent,
+ TransparencyLevelHint = WindowTransparencyLevel.Transparent,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ Width = 200,
+ Height = 200,
+ Content = new Border
+ {
+ Background = Brushes.Red,
+ CornerRadius = new CornerRadius(100),
+ }
+ };
+
+ window.PointerPressed += (_, _) =>
+ {
+ window.Close();
+ backgroundWindow.Close();
+ };
+
+ backgroundWindow.Show(this);
+ window.Show(backgroundWindow);
+ }
+
+ private void ShowTransparentPopup()
+ {
+ var popup = new Popup
+ {
+ WindowManagerAddShadowHint = false,
+ PlacementMode = PlacementMode.AnchorAndGravity,
+ PlacementAnchor = PopupAnchor.Top,
+ PlacementGravity = PopupGravity.Bottom,
+ Width= 200,
+ Height= 200,
+ Child = new Border
+ {
+ Background = Brushes.Red,
+ CornerRadius = new CornerRadius(100),
+ }
+ };
+
+ // Show a background window to make sure the color behind the transparent window is
+ // a known color (green).
+ var backgroundWindow = new Window
+ {
+ Title = "Transparent Popup Background",
+ Name = "TransparentPopupBackground",
+ Width = 200,
+ Height = 200,
+ Background = Brushes.Green,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ Content = new Border
+ {
+ Name = "PopupContainer",
+ Child = popup,
+ [AutomationProperties.AccessibilityViewProperty] = AccessibilityView.Content,
+ }
+ };
+
+ backgroundWindow.PointerPressed += (_, _) => backgroundWindow.Close();
+ backgroundWindow.Show(this);
+
+ popup.Open();
+ }
+
private void SendToBack()
{
var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
@@ -175,6 +262,10 @@ namespace IntegrationTestApp
this.Get("BasicListBox").SelectedIndex = -1;
if (source?.Name == "MenuClickedMenuItemReset")
this.Get("ClickedMenuItem").Text = "None";
+ if (source?.Name == "ShowTransparentWindow")
+ ShowTransparentWindow();
+ if (source?.Name == "ShowTransparentPopup")
+ ShowTransparentPopup();
if (source?.Name == "ShowWindow")
ShowWindow();
if (source?.Name == "SendToBack")
diff --git a/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj b/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj
index 1b83a3e567..a24e55de81 100644
--- a/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj
+++ b/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj
@@ -24,7 +24,6 @@
-
diff --git a/src/Avalonia.Base/Animation/Animatable.cs b/src/Avalonia.Base/Animation/Animatable.cs
index eddb89c3e8..5208c8b218 100644
--- a/src/Avalonia.Base/Animation/Animatable.cs
+++ b/src/Avalonia.Base/Animation/Animatable.cs
@@ -29,6 +29,9 @@ namespace Avalonia.Animation
private bool _transitionsEnabled = true;
private bool _isSubscribedToTransitionsCollection = false;
private Dictionary? _transitionState;
+ private NotifyCollectionChangedEventHandler? _collectionChanged;
+ private NotifyCollectionChangedEventHandler TransitionsCollectionChangedHandler =>
+ _collectionChanged ??= TransitionsCollectionChanged;
///
/// Gets or sets the clock which controls the animations on the control.
@@ -61,14 +64,14 @@ namespace Avalonia.Animation
{
_transitionsEnabled = true;
- if (Transitions is object)
+ if (Transitions is Transitions transitions)
{
if (!_isSubscribedToTransitionsCollection)
{
_isSubscribedToTransitionsCollection = true;
- Transitions.CollectionChanged += TransitionsCollectionChanged;
+ transitions.CollectionChanged += TransitionsCollectionChangedHandler;
}
- AddTransitions(Transitions);
+ AddTransitions(transitions);
}
}
}
@@ -86,14 +89,14 @@ namespace Avalonia.Animation
{
_transitionsEnabled = false;
- if (Transitions is object)
+ if (Transitions is Transitions transitions)
{
if (_isSubscribedToTransitionsCollection)
{
_isSubscribedToTransitionsCollection = false;
- Transitions.CollectionChanged -= TransitionsCollectionChanged;
+ transitions.CollectionChanged -= TransitionsCollectionChangedHandler;
}
- RemoveTransitions(Transitions);
+ RemoveTransitions(transitions);
}
}
}
@@ -120,7 +123,7 @@ namespace Avalonia.Animation
toAdd = newTransitions.Except(oldTransitions).ToList();
}
- newTransitions.CollectionChanged += TransitionsCollectionChanged;
+ newTransitions.CollectionChanged += TransitionsCollectionChangedHandler;
_isSubscribedToTransitionsCollection = true;
AddTransitions(toAdd);
}
@@ -134,19 +137,19 @@ namespace Avalonia.Animation
toRemove = oldTransitions.Except(newTransitions).ToList();
}
- oldTransitions.CollectionChanged -= TransitionsCollectionChanged;
+ oldTransitions.CollectionChanged -= TransitionsCollectionChangedHandler;
RemoveTransitions(toRemove);
}
}
else if (_transitionsEnabled &&
- Transitions is object &&
+ Transitions is Transitions transitions &&
_transitionState is object &&
!change.Property.IsDirect &&
change.Priority > BindingPriority.Animation)
{
- for (var i = Transitions.Count -1; i >= 0; --i)
+ for (var i = transitions.Count - 1; i >= 0; --i)
{
- var transition = Transitions[i];
+ var transition = transitions[i];
if (transition.Property == change.Property &&
_transitionState.TryGetValue(transition, out var state))
@@ -166,11 +169,11 @@ namespace Avalonia.Animation
{
oldValue = animatedValue;
}
-
+ var clock = Clock ?? AvaloniaLocator.Current.GetRequiredService();
state.Instance?.Dispose();
state.Instance = transition.Apply(
this,
- Clock ?? AvaloniaLocator.Current.GetRequiredService(),
+ clock,
oldValue,
newValue);
return;
diff --git a/src/Avalonia.Base/Animation/KeySpline.cs b/src/Avalonia.Base/Animation/KeySpline.cs
index 6ca5b2e759..ed6adb79b8 100644
--- a/src/Avalonia.Base/Animation/KeySpline.cs
+++ b/src/Avalonia.Base/Animation/KeySpline.cs
@@ -79,15 +79,12 @@ namespace Avalonia.Animation
/// culture of the string
/// Thrown if the string does not have 4 values
/// A with the appropriate values set
- 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}\"."))
- {
- return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
- }
+ using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".");
+ return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
}
///
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 1946d4ba5c..50a7a5c831 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -152,7 +152,7 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
- _values?.ClearLocalValue(property);
+ _values.ClearLocalValue(property);
}
///
@@ -242,7 +242,14 @@ namespace Avalonia
return registered.InvokeGetter(this);
}
- ///
+ ///
+ /// Gets an base value.
+ ///
+ /// The property.
+ ///
+ /// Gets the value of the property excluding animated values, otherwise .
+ /// Note that this method does not return property values that come from inherited or default values.
+ ///
public Optional GetBaseValue(StyledProperty property)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
@@ -261,7 +268,7 @@ namespace Avalonia
VerifyAccess();
- return _values?.IsAnimating(property) ?? false;
+ return _values.IsAnimating(property);
}
///
@@ -279,7 +286,7 @@ namespace Avalonia
VerifyAccess();
- return _values?.IsSet(property) ?? false;
+ return _values.IsSet(property);
}
///
@@ -515,14 +522,12 @@ namespace Avalonia
/// The property.
public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
- ///
internal void AddInheritanceChild(AvaloniaObject child)
{
_inheritanceChildren ??= new List();
_inheritanceChildren.Add(child);
}
-
- ///
+
internal void RemoveInheritanceChild(AvaloniaObject child)
{
_inheritanceChildren?.Remove(child);
@@ -541,24 +546,11 @@ namespace Avalonia
return new AvaloniaPropertyValue(
property,
GetValue(property),
- BindingPriority.Unset,
- "Local Value");
- }
- else if (_values != null)
- {
- var result = _values.GetDiagnostic(property);
-
- if (result != null)
- {
- return result;
- }
+ BindingPriority.LocalValue,
+ null);
}
- return new AvaloniaPropertyValue(
- property,
- GetValue(property),
- BindingPriority.Unset,
- "Unset");
+ return _values.GetDiagnostic(property);
}
internal ValueStore GetValueStore() => _values;
diff --git a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
index f5c135459d..aeb71d16ae 100644
--- a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
+++ b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
@@ -30,7 +30,7 @@ namespace Avalonia.Data.Converters
{
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)
diff --git a/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs
index d7b1f2e053..270cac95f2 100644
--- a/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs
+++ b/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs
@@ -1,6 +1,3 @@
-using System;
-using Avalonia.Data;
-
namespace Avalonia.Diagnostics
{
///
diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs
index 403dd6f23e..8d7cc2b9a1 100644
--- a/src/Avalonia.Base/Input/DragEventArgs.cs
+++ b/src/Avalonia.Base/Input/DragEventArgs.cs
@@ -1,36 +1,28 @@
using System;
using Avalonia.Interactivity;
using Avalonia.Metadata;
-using Avalonia.VisualTree;
namespace Avalonia.Input
{
public class DragEventArgs : RoutedEventArgs
{
- private Interactive _target;
- private Point _targetLocation;
+ private readonly Interactive _target;
+ private readonly Point _targetLocation;
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)
{
- var point = new Point(0, 0);
-
if (relativeTo == null)
{
throw new ArgumentNullException(nameof(relativeTo));
}
- if (_target != null)
- {
- point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point;
- }
-
- return point;
+ return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0);
}
[Unstable]
diff --git a/src/Avalonia.Base/Input/KeyGesture.cs b/src/Avalonia.Base/Input/KeyGesture.cs
index c6618fd550..9ee8ae9711 100644
--- a/src/Avalonia.Base/Input/KeyGesture.cs
+++ b/src/Avalonia.Base/Input/KeyGesture.cs
@@ -136,7 +136,7 @@ namespace Avalonia.Input
return StringBuilderCache.GetStringAndRelease(s);
}
- public bool Matches(KeyEventArgs keyEvent) =>
+ public bool Matches(KeyEventArgs? keyEvent) =>
keyEvent != null &&
keyEvent.KeyModifiers == KeyModifiers &&
ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key);
diff --git a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
index b05d8f30bb..ba909de60f 100644
--- a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
+++ b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
@@ -1,6 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using Avalonia.Input.Navigation;
using Avalonia.VisualTree;
@@ -51,7 +50,7 @@ namespace Avalonia.Input
// If there's a custom keyboard navigation handler as an ancestor, use that.
var custom = (element as Visual)?.FindAncestorOfType(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;
var result = direction switch
@@ -117,32 +116,27 @@ namespace Avalonia.Input
NavigationDirection direction,
[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)
- {
- result = next;
- return true;
- }
- else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
- {
- var r = direction switch
- {
- NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler),
- NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler),
- _ => throw new NotSupportedException(),
- };
-
- if (r is object)
- {
- result = r;
- return true;
- }
- }
+ NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler),
+ NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler),
+ _ => null
+ };
+
+ if (r is not null)
+ {
+ result = r;
+ return true;
}
}
diff --git a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs
index d218867cf2..c460ecf3b3 100644
--- a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs
+++ b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
using Avalonia.VisualTree;
namespace Avalonia.Input.Navigation
@@ -54,8 +52,7 @@ namespace Avalonia.Input.Navigation
// Avoid the endless loop here for Cycle groups
if (loopStartElement == nextTabElement)
break;
- if (loopStartElement == null)
- loopStartElement = nextTabElement;
+ loopStartElement ??= nextTabElement;
var firstTabElementInside = GetNextTab(null, nextTabElement, true);
if (firstTabElementInside != null)
@@ -80,12 +77,9 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e)
{
- if (e is IInputElement container)
+ if (e is IInputElement container && GetLastInTree(container) is { } last)
{
- var last = GetLastInTree(container);
-
- if (last is object)
- return GetNextTab(last, false);
+ return GetNextTab(last, false);
}
return null;
@@ -93,11 +87,8 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly)
{
- if (e is null && container is null)
- throw new InvalidOperationException("Either 'e' or 'container' must be non-null.");
-
- if (container is null)
- container = GetGroupParent(e!);
+ container ??=
+ GetGroupParent(e ?? throw new InvalidOperationException("Either 'e' or 'container' must be non-null."));
KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container);
@@ -163,8 +154,7 @@ namespace Avalonia.Input.Navigation
// Avoid the endless loop here
if (loopStartElement == nextTabElement)
break;
- if (loopStartElement == null)
- loopStartElement = nextTabElement;
+ loopStartElement ??= nextTabElement;
// At this point nextTabElement is TabGroup
var lastTabElementInside = GetPrevTab(null, nextTabElement, true);
@@ -189,22 +179,18 @@ namespace Avalonia.Input.Navigation
public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e)
{
- if (e is IInputElement container)
+ if (e is IInputElement container && GetFirstChild(container) is { } first)
{
- var first = GetFirstChild(container);
-
- if (first is object)
- return GetPrevTab(first, null, false);
+ return GetPrevTab(first, null, false);
}
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
- if (iie != null && !iie.IsKeyboardFocusWithin)
+ if (e != null && !e.IsKeyboardFocusWithin)
{
var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e);
if (focusedElement != null)
@@ -229,13 +215,11 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetFirstChild(IInputElement e)
{
// 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 the first visible element.
- var uiElement = e as InputElement;
-
- if (uiElement is null || IsVisibleAndEnabled(uiElement))
+ if (e is not InputElement uiElement || IsVisibleAndEnabled(uiElement))
{
if (e is Visual elementAsVisual)
{
@@ -265,7 +249,7 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetLastChild(IInputElement e)
{
// 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 the last visible element.
@@ -273,9 +257,7 @@ namespace Avalonia.Input.Navigation
if (uiElement == null || IsVisibleAndEnabled(uiElement))
{
- var elementAsVisual = e as Visual;
-
- if (elementAsVisual != null)
+ if (e is Visual elementAsVisual)
{
var children = elementAsVisual.VisualChildren;
var count = children.Count;
@@ -322,7 +304,7 @@ namespace Avalonia.Input.Navigation
return firstTabElement;
}
- private static IInputElement? GetLastInTree(IInputElement container)
+ private static IInputElement GetLastInTree(IInputElement container)
{
IInputElement? result;
IInputElement? c = container;
diff --git a/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs b/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs
index 74720c0a77..9ab7da3ff7 100644
--- a/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs
+++ b/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs
@@ -48,7 +48,7 @@ namespace Avalonia.LogicalTree
/// The logical.
/// If given logical should be included in search.
/// First ancestor of given type.
- public static T? FindLogicalAncestorOfType(this ILogical logical, bool includeSelf = false) where T : class
+ public static T? FindLogicalAncestorOfType(this ILogical? logical, bool includeSelf = false) where T : class
{
if (logical is null)
{
@@ -120,7 +120,7 @@ namespace Avalonia.LogicalTree
/// The logical.
/// If given logical should be included in search.
/// First descendant of given type.
- public static T? FindLogicalDescendantOfType(this ILogical logical, bool includeSelf = false) where T : class
+ public static T? FindLogicalDescendantOfType(this ILogical? logical, bool includeSelf = false) where T : class
{
if (logical is null)
{
@@ -185,7 +185,7 @@ namespace Avalonia.LogicalTree
/// True if is an ancestor of ;
/// otherwise false.
///
- public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target)
+ public static bool IsLogicalAncestorOf(this ILogical? logical, ILogical? target)
{
var current = target?.LogicalParent;
diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs
index 5470a735b3..ab89177295 100644
--- a/src/Avalonia.Base/Media/Color.cs
+++ b/src/Avalonia.Base/Media/Color.cs
@@ -147,16 +147,11 @@ namespace Avalonia.Media
/// The color string.
/// The parsed color
/// The status of the operation.
- public static bool TryParse(string s, out Color color)
+ public static bool TryParse(string? s, out Color color)
{
color = default;
- if (s is null)
- {
- return false;
- }
-
- if (s.Length == 0)
+ if (string.IsNullOrEmpty(s))
{
return false;
}
diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs
index d295111d72..622181dba0 100644
--- a/src/Avalonia.Base/Media/DrawingContext.cs
+++ b/src/Avalonia.Base/Media/DrawingContext.cs
@@ -240,7 +240,7 @@ namespace Avalonia.Media
///
/// The foreground brush.
/// The glyph run.
- public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
+ public void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun)
{
_ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun));
diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs
index 481329c20c..b7abda2c61 100644
--- a/src/Avalonia.Base/Media/DrawingGroup.cs
+++ b/src/Avalonia.Base/Media/DrawingGroup.cs
@@ -13,14 +13,14 @@ namespace Avalonia.Media
public static readonly StyledProperty OpacityProperty =
AvaloniaProperty.Register(nameof(Opacity), 1);
- public static readonly StyledProperty TransformProperty =
- AvaloniaProperty.Register(nameof(Transform));
+ public static readonly StyledProperty TransformProperty =
+ AvaloniaProperty.Register(nameof(Transform));
- public static readonly StyledProperty ClipGeometryProperty =
- AvaloniaProperty.Register(nameof(ClipGeometry));
+ public static readonly StyledProperty ClipGeometryProperty =
+ AvaloniaProperty.Register(nameof(ClipGeometry));
- public static readonly StyledProperty OpacityMaskProperty =
- AvaloniaProperty.Register(nameof(OpacityMask));
+ public static readonly StyledProperty OpacityMaskProperty =
+ AvaloniaProperty.Register(nameof(OpacityMask));
public static readonly DirectProperty ChildrenProperty =
AvaloniaProperty.RegisterDirect(
@@ -36,19 +36,19 @@ namespace Avalonia.Media
set => SetValue(OpacityProperty, value);
}
- public Transform Transform
+ public Transform? Transform
{
get => GetValue(TransformProperty);
set => SetValue(TransformProperty, value);
}
- public Geometry ClipGeometry
+ public Geometry? ClipGeometry
{
get => GetValue(ClipGeometryProperty);
set => SetValue(ClipGeometryProperty, value);
}
- public IBrush OpacityMask
+ public IBrush? OpacityMask
{
get => GetValue(OpacityMaskProperty);
set => SetValue(OpacityMaskProperty, value);
@@ -159,7 +159,7 @@ namespace Avalonia.Media
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
{
- if (((brush == null) && (pen == null)) || (geometry == null))
+ if ((brush == null) && (pen == null))
{
return;
}
@@ -167,9 +167,9 @@ namespace Avalonia.Media
AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
}
- public void DrawGlyphRun(IBrush foreground, IRef glyphRun)
+ public void DrawGlyphRun(IBrush? foreground, IRef glyphRun)
{
- if (foreground == null || glyphRun == null)
+ if (foreground == null)
{
return;
}
@@ -184,7 +184,7 @@ namespace Avalonia.Media
AddDrawing(glyphRunDrawing);
}
- public void DrawLine(IPen pen, Point p1, Point p2)
+ public void DrawLine(IPen? pen, Point p1, Point p2)
{
if (pen == null)
{
diff --git a/src/Avalonia.Base/Media/DrawingImage.cs b/src/Avalonia.Base/Media/DrawingImage.cs
index 38ddbdfaed..1b22a1ee69 100644
--- a/src/Avalonia.Base/Media/DrawingImage.cs
+++ b/src/Avalonia.Base/Media/DrawingImage.cs
@@ -20,8 +20,8 @@ namespace Avalonia.Media
///
/// Defines the property.
///
- public static readonly StyledProperty DrawingProperty =
- AvaloniaProperty.Register(nameof(Drawing));
+ public static readonly StyledProperty DrawingProperty =
+ AvaloniaProperty.Register(nameof(Drawing));
///
public event EventHandler? Invalidated;
@@ -30,7 +30,7 @@ namespace Avalonia.Media
/// Gets or sets the drawing content.
///
[Content]
- public Drawing Drawing
+ public Drawing? Drawing
{
get => GetValue(DrawingProperty);
set => SetValue(DrawingProperty, value);
diff --git a/src/Avalonia.Base/Media/FontFamily.cs b/src/Avalonia.Base/Media/FontFamily.cs
index da84861668..f4406bd010 100644
--- a/src/Avalonia.Base/Media/FontFamily.cs
+++ b/src/Avalonia.Base/Media/FontFamily.cs
@@ -119,7 +119,7 @@ namespace Avalonia.Media
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.RelativeOrAbsolute);
@@ -188,7 +188,7 @@ namespace Avalonia.Media
{
unchecked
{
- return ((FamilyNames != null ? FamilyNames.GetHashCode() : 0) * 397) ^ (Key != null ? Key.GetHashCode() : 0);
+ return (FamilyNames.GetHashCode() * 397) ^ (Key is not null ? Key.GetHashCode() : 0);
}
}
diff --git a/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs
index f607c67fed..12bb7e77e7 100644
--- a/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs
+++ b/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs
@@ -41,10 +41,7 @@ namespace Avalonia.Media.Fonts
{
var hash = (int)2166136261;
- if (Source != null)
- {
- hash = (hash * 16777619) ^ Source.GetHashCode();
- }
+ hash = (hash * 16777619) ^ Source.GetHashCode();
if (BaseUri != null)
{
diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs
index 28757b1a1d..3b63a98720 100644
--- a/src/Avalonia.Base/Media/FormattedText.cs
+++ b/src/Avalonia.Base/Media/FormattedText.cs
@@ -1354,7 +1354,7 @@ namespace Avalonia.Media
{
var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0);
- if (highlightBounds != null)
+ if (highlightBounds.Count > 0)
{
foreach (var bound in highlightBounds)
{
@@ -1365,7 +1365,7 @@ namespace Avalonia.Media
// Convert logical units (which extend leftward from the right edge
// 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,
// is the distance leftward from the right edge of the formatting rectangle
// whose width is the paragraph width passed to FormatLine.
@@ -1384,7 +1384,7 @@ namespace Avalonia.Media
else
{
accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union);
- }
+ }
}
}
}
diff --git a/src/Avalonia.Base/Media/GeometryDrawing.cs b/src/Avalonia.Base/Media/GeometryDrawing.cs
index 26cc2c3cab..ac2dce1e42 100644
--- a/src/Avalonia.Base/Media/GeometryDrawing.cs
+++ b/src/Avalonia.Base/Media/GeometryDrawing.cs
@@ -15,8 +15,8 @@ namespace Avalonia.Media
///
/// Defines the property.
///
- public static readonly StyledProperty GeometryProperty =
- AvaloniaProperty.Register(nameof(Geometry));
+ public static readonly StyledProperty GeometryProperty =
+ AvaloniaProperty.Register(nameof(Geometry));
///
/// Defines the property.
@@ -34,7 +34,7 @@ namespace Avalonia.Media
/// Gets or sets the that describes the shape of this .
///
[Content]
- public Geometry Geometry
+ public Geometry? Geometry
{
get => GetValue(GeometryProperty);
set => SetValue(GeometryProperty, value);
diff --git a/src/Avalonia.Base/Media/GlyphRunDrawing.cs b/src/Avalonia.Base/Media/GlyphRunDrawing.cs
index 242b9913fa..06d92fd81c 100644
--- a/src/Avalonia.Base/Media/GlyphRunDrawing.cs
+++ b/src/Avalonia.Base/Media/GlyphRunDrawing.cs
@@ -2,19 +2,19 @@
{
public class GlyphRunDrawing : Drawing
{
- public static readonly StyledProperty ForegroundProperty =
- AvaloniaProperty.Register(nameof(Foreground));
+ public static readonly StyledProperty ForegroundProperty =
+ AvaloniaProperty.Register(nameof(Foreground));
- public static readonly StyledProperty GlyphRunProperty =
- AvaloniaProperty.Register(nameof(GlyphRun));
+ public static readonly StyledProperty GlyphRunProperty =
+ AvaloniaProperty.Register(nameof(GlyphRun));
- public IBrush Foreground
+ public IBrush? Foreground
{
get => GetValue(ForegroundProperty);
set => SetValue(ForegroundProperty, value);
}
- public GlyphRun GlyphRun
+ public GlyphRun? GlyphRun
{
get => GetValue(GlyphRunProperty);
set => SetValue(GlyphRunProperty, value);
diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs
index 425a3138c3..b4bf6fd217 100644
--- a/src/Avalonia.Base/Media/HslColor.cs
+++ b/src/Avalonia.Base/Media/HslColor.cs
@@ -254,7 +254,7 @@ namespace Avalonia.Media
/// The HSL color string to parse.
/// The parsed .
/// True if parsing was successful; otherwise, false.
- public static bool TryParse(string s, out HslColor hslColor)
+ public static bool TryParse(string? s, out HslColor hslColor)
{
bool prefixMatched = false;
diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs
index 9f95b31518..f97457c54d 100644
--- a/src/Avalonia.Base/Media/HsvColor.cs
+++ b/src/Avalonia.Base/Media/HsvColor.cs
@@ -254,7 +254,7 @@ namespace Avalonia.Media
/// The HSV color string to parse.
/// The parsed .
/// True if parsing was successful; otherwise, false.
- public static bool TryParse(string s, out HsvColor hsvColor)
+ public static bool TryParse(string? s, out HsvColor hsvColor)
{
bool prefixMatched = false;
diff --git a/src/Avalonia.Base/Media/IVisualBrush.cs b/src/Avalonia.Base/Media/IVisualBrush.cs
index 6662613ff4..a7d3e4da10 100644
--- a/src/Avalonia.Base/Media/IVisualBrush.cs
+++ b/src/Avalonia.Base/Media/IVisualBrush.cs
@@ -1,5 +1,4 @@
using Avalonia.Metadata;
-using Avalonia.VisualTree;
namespace Avalonia.Media
{
@@ -12,6 +11,6 @@ namespace Avalonia.Media
///
/// Gets the visual to draw.
///
- Visual Visual { get; }
+ Visual? Visual { get; }
}
}
diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs b/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs
index 1f53f06955..6dff006045 100644
--- a/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs
+++ b/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs
@@ -39,17 +39,8 @@ namespace Avalonia.Media.Immutable
{
return true;
}
- else if (other is null)
- {
- return false;
- }
- if (Offset != other.Offset)
- {
- return false;
- }
-
- return SequenceEqual(Dashes, other.Dashes);
+ return other is not null && Offset == other.Offset && SequenceEqual(_dashes, other.Dashes);
}
///
@@ -58,30 +49,27 @@ namespace Avalonia.Media.Immutable
var hashCode = 717868523;
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;
}
- private static bool SequenceEqual(IReadOnlyList left, IReadOnlyList? right)
+ private static bool SequenceEqual(double[] left, IReadOnlyList? right)
{
if (ReferenceEquals(left, right))
{
return true;
}
- if (left == null || right == null || left.Count != right.Count)
+ if (right is null || left.Length != right.Count)
{
return false;
}
- for (var c = 0; c < left.Count; c++)
+ for (var c = 0; c < left.Length; c++)
{
if (left[c] != right[c])
{
diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
index 9b443391c5..0b625080e3 100644
--- a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
+++ b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
@@ -1,5 +1,4 @@
using Avalonia.Media.Imaging;
-using Avalonia.VisualTree;
namespace Avalonia.Media.Immutable
{
@@ -31,11 +30,11 @@ namespace Avalonia.Media.Immutable
RelativeRect? destinationRect = null,
double opacity = 1,
ImmutableTransform? transform = null,
- RelativePoint transformOrigin = new RelativePoint(),
+ RelativePoint transformOrigin = default,
RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None,
- Imaging.BitmapInterpolationMode bitmapInterpolationMode = Imaging.BitmapInterpolationMode.Default)
+ BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
: base(
alignmentX,
alignmentY,
@@ -62,6 +61,6 @@ namespace Avalonia.Media.Immutable
}
///
- public Visual Visual { get; }
+ public Visual? Visual { get; }
}
}
diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs
index dc9e5cb907..b74b7df9c5 100644
--- a/src/Avalonia.Base/Media/TextDecoration.cs
+++ b/src/Avalonia.Base/Media/TextDecoration.cs
@@ -22,8 +22,8 @@ namespace Avalonia.Media
///
/// Defines the property.
///
- public static readonly StyledProperty StrokeProperty =
- AvaloniaProperty.Register(nameof(Stroke));
+ public static readonly StyledProperty StrokeProperty =
+ AvaloniaProperty.Register(nameof(Stroke));
///
/// Defines the property.
@@ -34,8 +34,8 @@ namespace Avalonia.Media
///
/// Defines the property.
///
- public static readonly StyledProperty> StrokeDashArrayProperty =
- AvaloniaProperty.Register>(nameof(StrokeDashArray));
+ public static readonly StyledProperty?> StrokeDashArrayProperty =
+ AvaloniaProperty.Register?>(nameof(StrokeDashArray));
///
/// Defines the property.
@@ -82,7 +82,7 @@ namespace Avalonia.Media
///
/// Gets or sets the that specifies how the is painted.
///
- public IBrush Stroke
+ public IBrush? Stroke
{
get { return GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
@@ -101,7 +101,7 @@ namespace Avalonia.Media
/// Gets or sets a collection of values that indicate the pattern of dashes and gaps
/// that is used to draw the .
///
- public AvaloniaList StrokeDashArray
+ public AvaloniaList? StrokeDashArray
{
get { return GetValue(StrokeDashArrayProperty); }
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));
- if (intersections != null && intersections.Count > 0)
+ if (intersections.Count > 0)
{
var last = baselineOrigin.X;
var finalPos = last + glyphRun.Size.Width;
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
index c9dafaced7..b4734d702b 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
@@ -118,14 +118,17 @@ namespace Avalonia.Media.TextFormatting
fontManager.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
defaultTypeface.Stretch, defaultTypeface.FontFamily, defaultProperties.CultureInfo,
out var fallbackTypeface);
-
- var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface);
-
- if (matchFound && TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count))
+
+ if (matchFound)
{
- //Fallback found
- return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface),
- biDiLevel);
+ // Fallback found
+ var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface);
+
+ if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count))
+ {
+ return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface),
+ biDiLevel);
+ }
}
// no fallback found
diff --git a/src/Avalonia.Base/Media/VisualBrush.cs b/src/Avalonia.Base/Media/VisualBrush.cs
index 1261d233ac..2be3e9a94e 100644
--- a/src/Avalonia.Base/Media/VisualBrush.cs
+++ b/src/Avalonia.Base/Media/VisualBrush.cs
@@ -1,5 +1,4 @@
using Avalonia.Media.Immutable;
-using Avalonia.VisualTree;
namespace Avalonia.Media
{
@@ -11,8 +10,8 @@ namespace Avalonia.Media
///
/// Defines the property.
///
- public static readonly StyledProperty VisualProperty =
- AvaloniaProperty.Register(nameof(Visual));
+ public static readonly StyledProperty VisualProperty =
+ AvaloniaProperty.Register(nameof(Visual));
static VisualBrush()
{
@@ -38,7 +37,7 @@ namespace Avalonia.Media
///
/// Gets or sets the visual to draw.
///
- public Visual Visual
+ public Visual? Visual
{
get { return GetValue(VisualProperty); }
set { SetValue(VisualProperty, value); }
diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
index c05c04c22e..8509067cd0 100644
--- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
@@ -49,7 +49,7 @@ namespace Avalonia.Platform
/// The stroke pen.
/// The first point of the line.
/// The second point of the line.
- void DrawLine(IPen pen, Point p1, Point p2);
+ void DrawLine(IPen? pen, Point p1, Point p2);
///
/// Draws a geometry.
@@ -91,7 +91,7 @@ namespace Avalonia.Platform
///
/// The foreground.
/// The glyph run.
- void DrawGlyphRun(IBrush foreground, IRef glyphRun);
+ void DrawGlyphRun(IBrush? foreground, IRef glyphRun);
///
/// Creates a new that can be used as a render layer
diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
index 467cd530fc..d1a803fefb 100644
--- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
+++ b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
@@ -19,25 +19,20 @@ internal class AssemblyDescriptor : IAssemblyDescriptor
public AssemblyDescriptor(Assembly assembly)
{
Assembly = 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()
- .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);
+ Resources.Remove(Constants.AvaloniaResourceName);
- var indexLength = new BinaryReader(resources).ReadInt32();
- var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
- var baseOffset = indexLength + 4;
- AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor)
- new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
- }
- }
+ var indexLength = new BinaryReader(resources).ReadInt32();
+ var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
+ var baseOffset = indexLength + 4;
+ AvaloniaResources = index.ToDictionary(GetPathRooted, r => (IAssetDescriptor)
+ new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
}
}
@@ -45,6 +40,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor
public Dictionary? Resources { get; }
public Dictionary? AvaloniaResources { get; }
public string? Name { get; }
+
private static string GetPathRooted(AvaloniaResourcesIndexEntry r) =>
r.Path![0] == '/' ? r.Path : '/' + r.Path;
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
index a6db4330a3..455e9ebb5f 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations
public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase
{
private readonly CompositionPropertySet _propertySet;
- internal CompositionAnimation(Compositor compositor) : base(compositor, null!)
+ internal CompositionAnimation(Compositor compositor) : base(compositor, null)
{
_propertySet = new CompositionPropertySet(compositor);
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
index bad3991f43..1500e88abe 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
+++ b/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 RemoveAll() => Animations.Clear();
- public CompositionAnimationGroup(Compositor compositor) : base(compositor, null!)
+ public CompositionAnimationGroup(Compositor compositor) : base(compositor, null)
{
}
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
index 72be4edd07..d9adf261f8 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations
{
private Dictionary _inner = new Dictionary();
private IDictionary _innerface;
- internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null!)
+ internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null)
{
_innerface = _inner;
}
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
index 668d650ffd..7fa2d4955f 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
@@ -20,15 +20,17 @@ public class CompositingRenderer : IRendererWithCompositor
{
private readonly IRenderRoot _root;
private readonly Compositor _compositor;
- CompositionDrawingContext _recorder = new();
- DrawingContext _recordingContext;
- private HashSet _dirty = new();
- private HashSet _recalculateChildren = new();
+ private readonly CompositionDrawingContext _recorder = new();
+ private readonly DrawingContext _recordingContext;
+ private readonly HashSet _dirty = new();
+ private readonly HashSet _recalculateChildren = new();
+ private readonly Action _update;
+
private bool _queuedUpdate;
- private Action _update;
private bool _updating;
+ private bool _isDisposed;
- internal CompositionTarget CompositionTarget;
+ internal CompositionTarget CompositionTarget { get; }
///
/// 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
///
public RendererDiagnostics Diagnostics { get; }
+ ///
+ public Compositor Compositor => _compositor;
+
+ ///
+ /// Initializes a new instance of
+ ///
+ /// The render root using this renderer.
+ /// The associated compositors.
+ ///
+ /// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems.
+ ///
public CompositingRenderer(IRenderRoot root, Compositor compositor, Func> surfaces)
{
_root = root;
@@ -66,7 +79,7 @@ public class CompositingRenderer : IRendererWithCompositor
///
public event EventHandler? SceneInvalidated;
- void QueueUpdate()
+ private void QueueUpdate()
{
if(_queuedUpdate)
return;
@@ -77,9 +90,11 @@ public class CompositingRenderer : IRendererWithCompositor
///
public void AddDirty(Visual visual)
{
+ if (_isDisposed)
+ return;
if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass");
- _dirty.Add((Visual)visual);
+ _dirty.Add(visual);
QueueUpdate();
}
@@ -126,9 +141,11 @@ public class CompositingRenderer : IRendererWithCompositor
///
public void RecalculateChildren(Visual visual)
{
+ if (_isDisposed)
+ return;
if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass");
- _recalculateChildren.Add((Visual)visual);
+ _recalculateChildren.Add(visual);
QueueUpdate();
}
@@ -171,7 +188,7 @@ public class CompositingRenderer : IRendererWithCompositor
if (sortedChildren != null)
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;
break;
@@ -179,7 +196,7 @@ public class CompositingRenderer : IRendererWithCompositor
}
else
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;
break;
@@ -201,7 +218,7 @@ public class CompositingRenderer : IRendererWithCompositor
{
foreach (var ch in sortedChildren)
{
- var compositionChild = ((Visual)ch.visual).CompositionVisual;
+ var compositionChild = ch.visual.CompositionVisual;
if (compositionChild != null)
compositionChildren.Add(compositionChild);
}
@@ -210,7 +227,7 @@ public class CompositingRenderer : IRendererWithCompositor
else
foreach (var ch in v.GetVisualChildren())
{
- var compositionChild = ((Visual)ch).CompositionVisual;
+ var compositionChild = ch.CompositionVisual;
if (compositionChild != null)
compositionChildren.Add(compositionChild);
}
@@ -289,13 +306,18 @@ public class CompositingRenderer : IRendererWithCompositor
_updating = false;
}
}
-
+
+ ///
public void Resized(Size size)
{
}
+ ///
public void Paint(Rect rect)
{
+ if (_isDisposed)
+ return;
+
QueueUpdate();
CompositionTarget.RequestRedraw();
if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground)
@@ -304,17 +326,34 @@ public class CompositingRenderer : IRendererWithCompositor
CompositionTarget.ImmediateUIThreadRender();
}
- public void Start() => CompositionTarget.IsEnabled = true;
-
- public void Stop()
+ ///
+ public void Start()
{
- CompositionTarget.IsEnabled = false;
+ if (_isDisposed)
+ return;
+
+ CompositionTarget.IsEnabled = true;
}
- public ValueTask
public Compositor Compositor { get; }
- internal ServerObject Server { get; }
+ internal ServerObject? Server { get; }
public bool IsDisposed { get; private set; }
private bool _registeredForSerialization;
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
index 7d794af9a2..efd89951bb 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition
private readonly Dictionary _variants = new Dictionary();
private readonly Dictionary _objects = new Dictionary();
- internal CompositionPropertySet(Compositor compositor) : base(compositor, null!)
+ internal CompositionPropertySet(Compositor compositor) : base(compositor, null)
{
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
index 05488a558f..b75d080cfd 100644
--- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
@@ -88,8 +88,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
}
///
- 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();
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 void DrawGlyphRun(IBrush foreground, IRef glyphRun)
+ public void DrawGlyphRun(IBrush? foreground, IRef glyphRun)
{
+ if (foreground is null)
+ {
+ return;
+ }
+
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, foreground, glyphRun))
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
index 560ee05c10..b15da5d05d 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
@@ -165,8 +165,6 @@ namespace Avalonia.Rendering.Composition.Expressions
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
{
- if (context.ForeignFunctionInterface == null)
- return default;
var args = new List();
foreach (var expr in Parameters)
args.Add(expr.Evaluate(ref context));
diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
index 9086c59aad..f268364b54 100644
--- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using Avalonia.Rendering.Composition.Server;
// Special license applies License.md
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
index c58beebe7f..50df8bd32b 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
@@ -66,7 +66,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_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);
}
@@ -86,7 +86,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_impl.DrawEllipse(brush, pen, rect);
}
- public void DrawGlyphRun(IBrush foreground, IRef glyphRun)
+ public void DrawGlyphRun(IBrush? foreground, IRef glyphRun)
{
_impl.DrawGlyphRun(foreground, glyphRun);
}
diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs
index fa3260ffb4..09df7b7830 100644
--- a/src/Avalonia.Base/Rendering/IRenderRoot.cs
+++ b/src/Avalonia.Base/Rendering/IRenderRoot.cs
@@ -1,6 +1,4 @@
using Avalonia.Metadata;
-using Avalonia.Platform;
-using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs
index ba960ff5f3..7e32504e17 100644
--- a/src/Avalonia.Base/Rendering/IRenderer.cs
+++ b/src/Avalonia.Base/Rendering/IRenderer.cs
@@ -90,6 +90,9 @@ namespace Avalonia.Rendering
public interface IRendererWithCompositor : IRenderer
{
+ ///
+ /// The associated object
+ ///
Compositor Compositor { get; }
}
}
diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
index c67ac7057d..8e5dc38317 100644
--- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
@@ -48,8 +48,10 @@ namespace Avalonia.Rendering
///
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
{
- var visual = brush.Visual;
- Render(new DrawingContext(context), visual, visual.Bounds);
+ if (brush.Visual is { } visual)
+ {
+ Render(new DrawingContext(context), visual, visual.Bounds);
+ }
}
internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds)
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
index 12b67105e9..82f8fc2d56 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
@@ -80,11 +80,8 @@ namespace Avalonia.Rendering.SceneGraph
{
p *= Transform.Invert();
- if (Material != null)
- {
- var rect = Rect.Rect;
- return rect.ContainsExclusive(p);
- }
+ var rect = Rect.Rect;
+ return rect.ContainsExclusive(p);
}
return false;
diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs
index 3c44dd63ce..fafafabd82 100644
--- a/src/Avalonia.Base/Utilities/TypeUtilities.cs
+++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs
@@ -212,7 +212,7 @@ namespace Avalonia.Utilities
var toTypeConverter = TypeDescriptor.GetConverter(toUnderl);
- if (toTypeConverter.CanConvertFrom(from) == true)
+ if (toTypeConverter.CanConvertFrom(from))
{
result = toTypeConverter.ConvertFrom(null, culture, value);
return true;
@@ -220,7 +220,7 @@ namespace Avalonia.Utilities
var fromTypeConverter = TypeDescriptor.GetConverter(from);
- if (fromTypeConverter.CanConvertTo(toUnderl) == true)
+ if (fromTypeConverter.CanConvertTo(toUnderl))
{
result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl);
return true;
@@ -329,7 +329,7 @@ namespace Avalonia.Utilities
}
[RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
- public static T ConvertImplicit(object value)
+ public static T ConvertImplicit(object? value)
{
if (TryConvertImplicit(typeof(T), value, out var result))
{
@@ -369,11 +369,6 @@ namespace Avalonia.Utilities
///
public static bool IsNumeric(Type type)
{
- if (type == null)
- {
- return false;
- }
-
var underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null)
diff --git a/src/Avalonia.Base/Utilities/WeakEvent.cs b/src/Avalonia.Base/Utilities/WeakEvent.cs
index e72606bf70..237a491615 100644
--- a/src/Avalonia.Base/Utilities/WeakEvent.cs
+++ b/src/Avalonia.Base/Utilities/WeakEvent.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Threading;
@@ -15,7 +11,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event
{
private readonly Func, Action> _subscribe;
- readonly ConditionalWeakTable _subscriptions = new();
+ private readonly ConditionalWeakTable _subscriptions = new();
internal WeakEvent(
Action> subscribe,
@@ -51,56 +47,6 @@ public class WeakEvent : WeakEvent where TEventArgs : Event
private readonly WeakEvent _ev;
private readonly TSender _target;
private readonly Action _compact;
-
- struct Entry
- {
- WeakReference>? _reference;
- int _hashCode;
-
- public Entry(IWeakEventSubscriber r)
- {
- if (r == null)
- {
- _reference = null;
- _hashCode = 0;
- return;
- }
-
- _hashCode = r.GetHashCode();
- _reference = new WeakReference>(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 target)
- {
- if (_reference == null)
- {
- target = null!;
- return false;
- }
- return _reference.TryGetTarget(out target);
- }
-
- public bool Equals(IWeakEventSubscriber r)
- {
- if (_reference == null || r.GetHashCode() != _hashCode)
- return false;
- return _reference.TryGetTarget(out var target) && target == r;
- }
- }
-
private readonly Action _unsubscribe;
private readonly WeakHashList> _list = new();
private bool _compactScheduled;
@@ -114,7 +60,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event
_unsubscribe = ev._subscribe(target, OnEvent);
}
- void Destroy()
+ private void Destroy()
{
if(_destroyed)
return;
@@ -134,15 +80,15 @@ public class WeakEvent : WeakEvent where TEventArgs : Event
ScheduleCompact();
}
- void ScheduleCompact()
+ private void ScheduleCompact()
{
if(_compactScheduled || _destroyed)
return;
_compactScheduled = true;
Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background);
}
-
- void Compact()
+
+ private void Compact()
{
if(!_compactScheduled)
return;
@@ -152,7 +98,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event
Destroy();
}
- void OnEvent(object? sender, TEventArgs eventArgs)
+ private void OnEvent(object? sender, TEventArgs eventArgs)
{
var alive = _list.GetAlive();
if(alive == null)
@@ -196,4 +142,4 @@ public class WeakEvent
return () => unsubscribe(s, handler);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
index 020ba7a6d9..ef143144e6 100644
--- a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
+++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
@@ -60,8 +60,7 @@ namespace Avalonia.Utilities
private static class SubscriptionTypeStorage
where TArgs : EventArgs where TSubscriber : class
{
- public static readonly ConditionalWeakTable> Subscribers
- = new ConditionalWeakTable>();
+ public static readonly ConditionalWeakTable> Subscribers = new();
}
private class SubscriptionDic : Dictionary>
@@ -69,8 +68,7 @@ namespace Avalonia.Utilities
{
}
- private static readonly Dictionary> Accessors
- = new Dictionary>();
+ private static readonly Dictionary> s_accessors = new();
private class Subscription where T : EventArgs where TSubscriber : class
{
@@ -81,18 +79,17 @@ namespace Avalonia.Utilities
private readonly Delegate _delegate;
private Descriptor[] _data = new Descriptor[2];
- private int _count = 0;
+ private int _count;
- delegate void CallerDelegate(TSubscriber s, object sender, T args);
-
- struct Descriptor
+ private delegate void CallerDelegate(TSubscriber s, object? sender, T args);
+
+ private struct Descriptor
{
- public WeakReference Subscriber;
- public CallerDelegate Caller;
+ public WeakReference? Subscriber;
+ public CallerDelegate? Caller;
}
- private static Dictionary s_Callers =
- new Dictionary();
+ private static readonly Dictionary s_callers = new();
public Subscription(SubscriptionDic sdic,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] Type targetType,
@@ -101,8 +98,8 @@ namespace Avalonia.Utilities
_sdic = sdic;
_target = target;
_eventName = eventName;
- if (!Accessors.TryGetValue(targetType, out var evDic))
- Accessors[targetType] = evDic = new Dictionary();
+ if (!s_accessors.TryGetValue(targetType, out var evDic))
+ s_accessors[targetType] = evDic = new Dictionary();
if (evDic.TryGetValue(eventName, out var info))
{
@@ -123,12 +120,12 @@ namespace Avalonia.Utilities
var del = new Action(OnEvent);
_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);
}
@@ -146,8 +143,8 @@ namespace Avalonia.Utilities
MethodInfo method = s.Method;
var subscriber = (TSubscriber)s.Target!;
- if (!s_Callers.TryGetValue(method, out var caller))
- s_Callers[method] = caller =
+ if (!s_callers.TryGetValue(method, out var caller))
+ s_callers[method] = caller =
(CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, method);
_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;
for (int c = 0; c < _count; c++)
@@ -206,15 +203,15 @@ namespace Avalonia.Utilities
Destroy();
}
- void OnEvent(object sender, T eventArgs)
+ private void OnEvent(object? sender, T eventArgs)
{
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))
{
- _data[c].Caller(sub, sender, eventArgs);
+ _data[c].Caller!(sub, sender, eventArgs);
}
else
needCompact = true;
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index e6d7492c51..87bb1d3790 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -348,7 +348,7 @@ namespace Avalonia
///
public void InvalidateVisual()
{
- VisualRoot?.Renderer?.AddDirty(this);
+ VisualRoot?.Renderer.AddDirty(this);
}
///
@@ -449,7 +449,7 @@ namespace Avalonia
protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.LogicalChildrenCollectionChanged(sender, e);
- VisualRoot?.Renderer?.RecalculateChildren(this);
+ VisualRoot?.Renderer.RecalculateChildren(this);
}
///
@@ -477,23 +477,19 @@ namespace Avalonia
OnAttachedToVisualTree(e);
AttachedToVisualTree?.Invoke(this, e);
InvalidateVisual();
- _visualRoot.Renderer?.RecalculateChildren(_visualParent!);
+ _visualRoot.Renderer.RecalculateChildren(_visualParent!);
if (ZIndex != 0 && VisualParent is Visual parent)
parent.HasNonUniformZIndexChildren = true;
var visualChildren = VisualChildren;
+ var visualChildrenCount = visualChildren.Count;
- if (visualChildren != null)
+ for (var i = 0; i < visualChildrenCount; i++)
{
- var visualChildrenCount = visualChildren.Count;
-
- for (var i = 0; i < visualChildrenCount; i++)
+ if (visualChildren[i] is { } child)
{
- if (visualChildren[i] is Visual child)
- {
- child.OnAttachedToVisualTreeCore(e);
- }
+ child.OnAttachedToVisualTreeCore(e);
}
}
}
@@ -540,20 +536,16 @@ namespace Avalonia
}
DetachedFromVisualTree?.Invoke(this, e);
- e.Root?.Renderer?.AddDirty(this);
+ e.Root.Renderer.AddDirty(this);
var visualChildren = VisualChildren;
+ var visualChildrenCount = visualChildren.Count;
- if (visualChildren != null)
+ for (var i = 0; i < visualChildrenCount; i++)
{
- var visualChildrenCount = visualChildren.Count;
-
- for (var i = 0; i < visualChildrenCount; i++)
+ if (visualChildren[i] is { } child)
{
- if (visualChildren[i] is Visual child)
- {
- child.OnDetachedFromVisualTreeCore(e);
- }
+ child.OnDetachedFromVisualTreeCore(e);
}
}
}
@@ -659,7 +651,7 @@ namespace Avalonia
parentVisual.HasNonUniformZIndexChildren = true;
sender?.InvalidateVisual();
- parent?.VisualRoot?.Renderer?.RecalculateChildren(parent);
+ parent?.VisualRoot?.Renderer.RecalculateChildren(parent);
}
///
diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
index b58db3b276..9e38c6e7f2 100644
--- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs
+++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
@@ -46,7 +46,7 @@ namespace Avalonia.VisualTree
Visual? v = visual ?? throw new ArgumentNullException(nameof(visual));
var result = 0;
- v = v?.VisualParent;
+ v = v.VisualParent;
while (v != null)
{
@@ -64,17 +64,13 @@ namespace Avalonia.VisualTree
/// The first visual.
/// The second visual.
/// The common ancestor, or null if not found.
- 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 (target is null)
+ if (visual is null || target is null)
{
return null;
}
- Visual? t = target;
-
void GoUpwards(ref Visual? node, int count)
{
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.
// By doing that we can sometimes find out that other node is our lowest common ancestor.
var firstHeight = CalculateDistanceFromRoot(v);
@@ -144,7 +143,7 @@ namespace Avalonia.VisualTree
/// The visual.
/// If given visual should be included in search.
/// First ancestor of given type.
- public static T? FindAncestorOfType(this Visual visual, bool includeSelf = false) where T : class
+ public static T? FindAncestorOfType(this Visual? visual, bool includeSelf = false) where T : class
{
if (visual is null)
{
@@ -173,7 +172,7 @@ namespace Avalonia.VisualTree
/// The visual.
/// If given visual should be included in search.
/// First descendant of given type.
- public static T? FindDescendantOfType(this Visual visual, bool includeSelf = false) where T : class
+ public static T? FindDescendantOfType(this Visual? visual, bool includeSelf = false) where T : class
{
if (visual is null)
{
@@ -392,7 +391,7 @@ namespace Avalonia.VisualTree
/// True if is an ancestor of ;
/// otherwise false.
///
- public static bool IsVisualAncestorOf(this Visual visual, Visual target)
+ public static bool IsVisualAncestorOf(this Visual? visual, Visual? target)
{
Visual? current = target?.VisualParent;
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index f35124ee0a..91b65a1f72 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -3979,7 +3979,7 @@ namespace Avalonia.Controls
{
if (focusedObject is Control element)
{
- parent = element.Parent;
+ parent = element.VisualParent;
if (parent != null)
{
dataGridWillReceiveRoutedEvent = false;
diff --git a/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs b/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs
index f4ba644ae6..6aebf05d6b 100644
--- a/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs
+++ b/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs
@@ -36,7 +36,7 @@ namespace Avalonia.Controls.Utils
{
if (child is Control childElement)
{
- parent = childElement.Parent;
+ parent = childElement.VisualParent;
}
}
child = parent;
diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
index 98885e11ca..55649660f7 100644
--- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
@@ -792,7 +792,7 @@ namespace Avalonia.Controls
Control? element = focused as Control;
if (element != null)
{
- parent = element.Parent;
+ parent = element.VisualParent;
}
}
focused = parent;
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index 9627f200df..1ec6f8dabc 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -394,10 +394,10 @@ namespace Avalonia.Controls
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
IsPressed = true;
+ e.Handled = true;
if (ClickMode == ClickMode.Press)
{
- e.Handled = true;
OnClick();
}
}
@@ -411,11 +411,11 @@ namespace Avalonia.Controls
if (IsPressed && e.InitialPressMouseButton == MouseButton.Left)
{
IsPressed = false;
+ e.Handled = true;
if (ClickMode == ClickMode.Release &&
this.GetVisualsAt(e.GetPosition(this)).Any(c => this == c || this.IsVisualAncestorOf(c)))
{
- e.Handled = true;
OnClick();
}
}
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index ed24c3c7c2..ab7c9948c4 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -2,14 +2,12 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Automation.Peers;
-using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
-using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Threading;
@@ -211,8 +209,6 @@ namespace Avalonia.Controls
remove => RemoveHandler(SizeChangedEvent, value);
}
- public new Control? Parent => (Control?)base.Parent;
-
///
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
diff --git a/src/Avalonia.Controls/Documents/InlineUIContainer.cs b/src/Avalonia.Controls/Documents/InlineUIContainer.cs
index 58afb24b5c..f06c8515ee 100644
--- a/src/Avalonia.Controls/Documents/InlineUIContainer.cs
+++ b/src/Avalonia.Controls/Documents/InlineUIContainer.cs
@@ -64,5 +64,23 @@ namespace Avalonia.Controls.Documents
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);
+ }
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 59b5bf48a5..aa62ae3e71 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
-using System.Diagnostics.CodeAnalysis;
using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@@ -17,7 +16,6 @@ using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.Styling;
-using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -91,10 +89,11 @@ namespace Avalonia.Controls
/// Gets or sets the to use for binding to the display member of each item.
///
[AssignBinding]
+ [InheritDataTypeFromItems(nameof(Items))]
public IBinding? DisplayMemberBinding
{
- get { return GetValue(DisplayMemberBindingProperty); }
- set { SetValue(DisplayMemberBindingProperty, value); }
+ get => GetValue(DisplayMemberBindingProperty);
+ set => SetValue(DisplayMemberBindingProperty, value);
}
private IEnumerable? _items = new AvaloniaList();
@@ -134,8 +133,8 @@ namespace Avalonia.Controls
[Content]
public IEnumerable? Items
{
- get { return _items; }
- set { SetAndRaise(ItemsProperty, ref _items, value); }
+ get => _items;
+ set => SetAndRaise(ItemsProperty, ref _items, value);
}
///
@@ -143,8 +142,8 @@ namespace Avalonia.Controls
///
public ControlTheme? ItemContainerTheme
{
- get { return GetValue(ItemContainerThemeProperty); }
- set { SetValue(ItemContainerThemeProperty, value); }
+ get => GetValue(ItemContainerThemeProperty);
+ set => SetValue(ItemContainerThemeProperty, value);
}
///
@@ -161,8 +160,8 @@ namespace Avalonia.Controls
///
public ITemplate ItemsPanel
{
- get { return GetValue(ItemsPanelProperty); }
- set { SetValue(ItemsPanelProperty, value); }
+ get => GetValue(ItemsPanelProperty);
+ set => SetValue(ItemsPanelProperty, value);
}
///
@@ -171,8 +170,8 @@ namespace Avalonia.Controls
[InheritDataTypeFromItems(nameof(Items))]
public IDataTemplate? ItemTemplate
{
- get { return GetValue(ItemTemplateProperty); }
- set { SetValue(ItemTemplateProperty, value); }
+ get => GetValue(ItemTemplateProperty);
+ set => SetValue(ItemTemplateProperty, value);
}
///
@@ -264,8 +263,8 @@ namespace Avalonia.Controls
///
public bool AreHorizontalSnapPointsRegular
{
- get { return GetValue(AreHorizontalSnapPointsRegularProperty); }
- set { SetValue(AreHorizontalSnapPointsRegularProperty, value); }
+ get => GetValue(AreHorizontalSnapPointsRegularProperty);
+ set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
}
///
@@ -273,8 +272,8 @@ namespace Avalonia.Controls
///
public bool AreVerticalSnapPointsRegular
{
- get { return GetValue(AreVerticalSnapPointsRegularProperty); }
- set { SetValue(AreVerticalSnapPointsRegularProperty, value); }
+ get => GetValue(AreVerticalSnapPointsRegularProperty);
+ set => SetValue(AreVerticalSnapPointsRegularProperty, value);
}
///
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index 8b1a307182..80d1677c2f 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -104,6 +104,7 @@ namespace Avalonia.Controls
public void UnselectAll() => Selection.Clear();
protected internal override Control CreateContainerForItemOverride() => new ListBoxItem();
+ protected internal override bool IsItemItsOwnContainerOverride(Control item) => item is ListBoxItem;
///
protected override void OnGotFocus(GotFocusEventArgs e)
diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
index de3aca76d9..4dd868253e 100644
--- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
+++ b/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)
{
diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
index 8594b584fa..e8eaac7d17 100644
--- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
@@ -28,19 +28,19 @@ namespace Avalonia.Controls.Presenters
/// Defines the property.
///
public static readonly StyledProperty AreHorizontalSnapPointsRegularProperty =
- AvaloniaProperty.Register(nameof(AreHorizontalSnapPointsRegular));
+ AvaloniaProperty.Register(nameof(AreHorizontalSnapPointsRegular));
///
/// Defines the property.
///
public static readonly StyledProperty AreVerticalSnapPointsRegularProperty =
- AvaloniaProperty.Register(nameof(AreVerticalSnapPointsRegular));
+ AvaloniaProperty.Register(nameof(AreVerticalSnapPointsRegular));
///
/// Defines the event.
///
public static readonly RoutedEvent HorizontalSnapPointsChangedEvent =
- RoutedEvent.Register(
+ RoutedEvent.Register(
nameof(HorizontalSnapPointsChanged),
RoutingStrategies.Bubble);
@@ -48,7 +48,7 @@ namespace Avalonia.Controls.Presenters
/// Defines the event.
///
public static readonly RoutedEvent VerticalSnapPointsChangedEvent =
- RoutedEvent.Register(
+ RoutedEvent.Register(
nameof(VerticalSnapPointsChanged),
RoutingStrategies.Bubble);
@@ -139,7 +139,7 @@ namespace Avalonia.Controls.Presenters
Size IScrollable.Viewport => _logicalScrollable?.Viewport ?? default;
///
- /// Gets or sets whether the horizontal snap points for the are equidistant from each other.
+ /// Gets or sets whether the horizontal snap points for the are equidistant from each other.
///
public bool AreHorizontalSnapPointsRegular
{
@@ -148,7 +148,7 @@ namespace Avalonia.Controls.Presenters
}
///
- /// Gets or sets whether the vertical snap points for the are equidistant from each other.
+ /// Gets or sets whether the vertical snap points for the are equidistant from each other.
///
public bool AreVerticalSnapPointsRegular
{
diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
index e265f4eb6a..d466edeb33 100644
--- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
@@ -48,7 +48,7 @@ namespace Avalonia.Controls.Primitives
set { /* Not currently supported in overlay popups */ }
}
- protected internal override Interactive? InteractiveParent => Parent;
+ protected internal override Interactive? InteractiveParent => (Interactive?)VisualParent;
public void Dispose() => Hide();
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index c85199a665..3b68cd2ae8 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -723,7 +723,7 @@ namespace Avalonia.Controls.Primitives
while (e is object && (!e.Focusable || !e.IsEffectivelyEnabled || !e.IsVisible))
{
- e = e.Parent;
+ e = e.VisualParent as Control;
}
if (e is object)
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index 57ec864cad..b3436d4176 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -72,12 +72,12 @@ namespace Avalonia.Controls.Primitives
///
/// Popup events are passed to their parent window. This facilitates this.
///
- protected internal override Interactive? InteractiveParent => Parent;
+ protected internal override Interactive? InteractiveParent => (Interactive?)Parent;
///
/// Gets the control that is hosting the popup root.
///
- Visual? IHostedVisualTreeRoot.Host => Parent;
+ Visual? IHostedVisualTreeRoot.Host => VisualParent;
///
/// Gets the styling parent of the popup root.
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 5210362505..eb39e92cbe 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -4,15 +4,14 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
-using System.Xml.Linq;
-using Avalonia.Controls.Generators;
using Avalonia.Controls.Selection;
+using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
+using Avalonia.Metadata;
using Avalonia.Threading;
-using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
@@ -66,6 +65,19 @@ namespace Avalonia.Controls.Primitives
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty SelectedValueProperty =
+ AvaloniaProperty.Register(nameof(SelectedValue),
+ defaultBindingMode: BindingMode.TwoWay);
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty SelectedValueBindingProperty =
+ AvaloniaProperty.Register(nameof(SelectedValueBinding));
+
///
/// Defines the property.
///
@@ -129,6 +141,8 @@ namespace Avalonia.Controls.Primitives
private bool _ignoreContainerSelectionChanged;
private UpdateState? _updateState;
private bool _hasScrolledToSelectedItem;
+ private BindingHelper? _bindingHelper;
+ private bool _isSelectionChangeActive;
///
/// Initializes static members of the class.
@@ -143,8 +157,8 @@ namespace Avalonia.Controls.Primitives
///
public event EventHandler? SelectionChanged
{
- add { AddHandler(SelectionChangedEvent, value); }
- remove { RemoveHandler(SelectionChangedEvent, value); }
+ add => AddHandler(SelectionChangedEvent, value);
+ remove => RemoveHandler(SelectionChangedEvent, value);
}
///
@@ -152,8 +166,8 @@ namespace Avalonia.Controls.Primitives
///
public bool AutoScrollToSelectedItem
{
- get { return GetValue(AutoScrollToSelectedItemProperty); }
- set { SetValue(AutoScrollToSelectedItemProperty, value); }
+ get => GetValue(AutoScrollToSelectedItemProperty);
+ set => SetValue(AutoScrollToSelectedItemProperty, value);
}
///
@@ -209,6 +223,28 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
+ /// Gets the instance used to obtain the
+ /// property
+ ///
+ [AssignBinding]
+ [InheritDataTypeFromItems(nameof(Items))]
+ public IBinding? SelectedValueBinding
+ {
+ get => GetValue(SelectedValueBindingProperty);
+ set => SetValue(SelectedValueBindingProperty, value);
+ }
+
+ ///
+ /// Gets or sets the value of the selected item, obtained using
+ ///
+ ///
+ public object? SelectedValue
+ {
+ get => GetValue(SelectedValueProperty);
+ set => SetValue(SelectedValueProperty, value);
+ }
+
///
/// Gets or sets the selected items.
///
@@ -322,8 +358,8 @@ namespace Avalonia.Controls.Primitives
///
public bool IsTextSearchEnabled
{
- get { return GetValue(IsTextSearchEnabledProperty); }
- set { SetValue(IsTextSearchEnabledProperty, value); }
+ get => GetValue(IsTextSearchEnabledProperty);
+ set => SetValue(IsTextSearchEnabledProperty, value);
}
///
@@ -332,8 +368,8 @@ namespace Avalonia.Controls.Primitives
///
public bool WrapSelection
{
- get { return GetValue(WrapSelectionProperty); }
- set { SetValue(WrapSelectionProperty, value); }
+ get => GetValue(WrapSelectionProperty);
+ set => SetValue(WrapSelectionProperty, value);
}
///
@@ -345,8 +381,8 @@ namespace Avalonia.Controls.Primitives
///
protected SelectionMode SelectionMode
{
- get { return GetValue(SelectionModeProperty); }
- set { SetValue(SelectionModeProperty, value); }
+ get => GetValue(SelectionModeProperty);
+ set => SetValue(SelectionModeProperty, value);
}
///
@@ -609,6 +645,60 @@ namespace Avalonia.Controls.Primitives
{
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();
+ 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;
+ }
+ }
}
///
@@ -815,6 +905,10 @@ namespace Avalonia.Controls.Primitives
new BindingValue(SelectedItems));
_oldSelectedItems = SelectedItems;
}
+ else if (e.PropertyName == nameof(ISelectionModel.Source))
+ {
+ ClearValue(SelectedValueProperty);
+ }
}
///
@@ -845,6 +939,11 @@ namespace Avalonia.Controls.Primitives
Mark(i, false);
}
+ if (!_isSelectionChangeActive)
+ {
+ UpdateSelectedValueFromItem();
+ }
+
var route = BuildEventRoute(SelectionChangedEvent);
if (route.HasHandlers)
@@ -871,6 +970,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()
{
if (AutoScrollToSelectedItem &&
@@ -1037,6 +1239,13 @@ namespace Avalonia.Controls.Primitives
Selection.Clear();
}
+ if (state.SelectedValue.HasValue)
+ {
+ var item = FindItemWithValue(state.SelectedValue.Value);
+ if (item != AvaloniaProperty.UnsetValue)
+ state.SelectedItem = item;
+ }
+
if (state.SelectedIndex.HasValue)
{
SelectedIndex = state.SelectedIndex.Value;
@@ -1098,6 +1307,7 @@ namespace Avalonia.Controls.Primitives
{
private Optional _selectedIndex;
private Optional _selectedItem;
+ private Optional _selectedValue;
public int UpdateCount { get; set; }
public Optional Selection { get; set; }
@@ -1122,6 +1332,54 @@ namespace Avalonia.Controls.Primitives
_selectedIndex = default;
}
}
+
+ public Optional SelectedValue
+ {
+ get => _selectedValue;
+ set
+ {
+ _selectedValue = value;
+ }
+ }
+ }
+
+ ///
+ /// Helper class for evaluating a binding from an Item and IBinding instance
+ ///
+ private class BindingHelper : StyledElement
+ {
+ public BindingHelper(IBinding binding)
+ {
+ UpdateBinding(binding);
+ }
+
+ public static readonly StyledProperty ValueProperty =
+ AvaloniaProperty.Register("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;
}
}
}
diff --git a/src/Avalonia.Controls/Primitives/ToggleButton.cs b/src/Avalonia.Controls/Primitives/ToggleButton.cs
index dfb436a55e..158c5d875b 100644
--- a/src/Avalonia.Controls/Primitives/ToggleButton.cs
+++ b/src/Avalonia.Controls/Primitives/ToggleButton.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Controls.Primitives
nameof(IsChecked),
o => o.IsChecked,
(o, v) => o.IsChecked = v,
- unsetValue: null,
+ unsetValue: false,
defaultBindingMode: BindingMode.TwoWay);
///
diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs
index b6b7f74f83..98a9ec60bf 100644
--- a/src/Avalonia.Controls/ProgressBar.cs
+++ b/src/Avalonia.Controls/ProgressBar.cs
@@ -238,7 +238,7 @@ namespace Avalonia.Controls
private void UpdateIndicator()
{
// 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)
{
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index df9a3eb8f3..ec31470126 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -673,8 +673,6 @@ namespace Avalonia.Controls
controlRun.Control is Control control)
{
VisualChildren.Remove(control);
-
- LogicalChildren.Remove(control);
}
}
}
@@ -693,8 +691,6 @@ namespace Avalonia.Controls
{
VisualChildren.Add(control);
- LogicalChildren.Add(control);
-
control.Measure(Size.Infinity);
}
}
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 676fa1519a..4db71abfa8 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -94,7 +94,6 @@ namespace Avalonia.Controls
private readonly IInputManager? _inputManager;
private readonly IAccessKeyHandler? _accessKeyHandler;
private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler;
- private readonly IPlatformRenderInterface? _renderInterface;
private readonly IGlobalStyles? _globalStyles;
private readonly IGlobalThemeVariantProvider? _applicationThemeHost;
private readonly PointerOverPreProcessor? _pointerOverPreProcessor;
@@ -136,36 +135,21 @@ namespace Avalonia.Controls
///
public TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyResolver)
{
- if (impl == null)
- {
- throw new InvalidOperationException(
- "Could not create window implementation: maybe no windowing subsystem was initialized?");
- }
-
- PlatformImpl = impl;
+ PlatformImpl = impl ?? throw new InvalidOperationException(
+ "Could not create window implementation: maybe no windowing subsystem was initialized?");
_actualTransparencyLevel = PlatformImpl.TransparencyLevel;
- dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current;
+ dependencyResolver ??= AvaloniaLocator.Current;
_accessKeyHandler = TryGetService(dependencyResolver);
_inputManager = TryGetService(dependencyResolver);
_keyboardNavigationHandler = TryGetService(dependencyResolver);
- _renderInterface = TryGetService(dependencyResolver);
_globalStyles = TryGetService(dependencyResolver);
_applicationThemeHost = TryGetService(dependencyResolver);
Renderer = impl.CreateRenderer(this);
-
- if (Renderer != null)
- {
- Renderer.SceneInvalidated += SceneInvalidated;
- }
- else
- {
- // Prevent nullable error.
- Renderer = null!;
- }
+ Renderer.SceneInvalidated += SceneInvalidated;
impl.SetInputRoot(this);
@@ -216,7 +200,7 @@ namespace Avalonia.Controls
if(impl.TryGetFeature() is {} systemNavigationManager)
{
- systemNavigationManager.BackRequested += (s, e) =>
+ systemNavigationManager.BackRequested += (_, e) =>
{
e.RoutedEvent = BackRequestedEvent;
RaiseEvent(e);
@@ -337,7 +321,7 @@ namespace Avalonia.Controls
{
_layoutManager = CreateLayoutManager();
- if (_layoutManager is LayoutManager typedLayoutManager && Renderer is not null)
+ if (_layoutManager is LayoutManager typedLayoutManager)
{
_layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, typedLayoutManager);
_layoutDiagnosticBridge.SetupBridge();
@@ -356,7 +340,7 @@ namespace Avalonia.Controls
///
/// Gets the renderer for the window.
///
- public IRenderer Renderer { get; private set; }
+ public IRenderer Renderer { get; }
internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition;
@@ -450,7 +434,7 @@ namespace Avalonia.Controls
/// The dirty area.
protected virtual void HandlePaint(Rect rect)
{
- Renderer?.Paint(rect);
+ Renderer.Paint(rect);
}
///
@@ -468,8 +452,8 @@ namespace Avalonia.Controls
_applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged;
}
- Renderer?.Dispose();
- Renderer = null!;
+ Renderer.SceneInvalidated -= SceneInvalidated;
+ Renderer.Dispose();
_layoutDiagnosticBridge?.Dispose();
_layoutDiagnosticBridge = null;
@@ -488,7 +472,7 @@ namespace Avalonia.Controls
OnClosed(EventArgs.Empty);
- LayoutManager?.Dispose();
+ LayoutManager.Dispose();
}
///
@@ -503,7 +487,7 @@ namespace Avalonia.Controls
Width = clientSize.Width;
Height = clientSize.Height;
LayoutManager.ExecuteLayoutPass();
- Renderer?.Resized(clientSize);
+ Renderer.Resized(clientSize);
}
///
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index a20b4eee58..ba1b599421 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -450,7 +450,7 @@ namespace Avalonia.Controls
/// resulting task will produce the value when the window
/// is closed.
///
- public void Close(object dialogResult)
+ public void Close(object? dialogResult)
{
_dialogResult = dialogResult;
CloseCore(WindowCloseReason.WindowClosing, true);
@@ -573,7 +573,7 @@ namespace Avalonia.Controls
return;
}
- Renderer?.Stop();
+ Renderer.Stop();
if (Owner is Window owner)
{
@@ -721,7 +721,7 @@ namespace Avalonia.Controls
SetWindowStartupLocation(owner?.PlatformImpl);
PlatformImpl?.Show(ShowActivated, false);
- Renderer?.Start();
+ Renderer.Start();
OnOpened(EventArgs.Empty);
}
}
@@ -798,7 +798,7 @@ namespace Avalonia.Controls
PlatformImpl?.Show(ShowActivated, true);
- Renderer?.Start();
+ Renderer.Start();
Observable.FromEventPattern(
x => Closed += x,
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index aad0482b50..0c9a91148b 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -129,7 +129,7 @@ namespace Avalonia.Controls
{
using (FreezeVisibilityChangeHandling())
{
- Renderer?.Stop();
+ Renderer.Stop();
PlatformImpl?.Hide();
IsVisible = false;
}
@@ -153,7 +153,7 @@ namespace Avalonia.Controls
}
PlatformImpl?.Show(true, false);
- Renderer?.Start();
+ Renderer.Start();
OnOpened(EventArgs.Empty);
}
}
@@ -219,7 +219,7 @@ namespace Avalonia.Controls
{
ClientSize = clientSize;
LayoutManager.ExecuteLayoutPass();
- Renderer?.Resized(clientSize);
+ Renderer.Resized(clientSize);
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
index 7426c4e2ed..a0ff3a714f 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
@@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.Controls
RendererRoot = application.ApplicationLifetime switch
{
Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow?.Renderer,
- Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as Visual)?.VisualRoot?.Renderer,
+ Lifetimes.ISingleViewApplicationLifetime single => single.MainView?.VisualRoot?.Renderer,
_ => null
};
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
index 0140281d50..785fd49983 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
@@ -115,7 +115,7 @@ namespace Avalonia.Diagnostics.ViewModels
var link = _currentEvent.EventChain[linkIndex];
link.Handled = true;
- _currentEvent.HandledBy = link;
+ _currentEvent.HandledBy ??= link;
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
index cd2e92914a..f62d8a0b79 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
@@ -29,6 +29,7 @@
diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
index 225e846390..68466fe381 100644
--- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
@@ -141,9 +141,7 @@ namespace Avalonia.Headless
}
public IReadOnlyList GetIntersections(float lowerBound, float upperBound)
- {
- return null;
- }
+ => Array.Empty();
}
class HeadlessGeometryStub : IGeometryImpl
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index f27d94b61a..64c1d0da10 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/src/Avalonia.Native/WindowImpl.cs
@@ -119,7 +119,8 @@ namespace Avalonia.Native
{
if(e.Type == RawPointerEventType.LeftButtonDown)
{
- var visual = (_inputRoot as Window).Renderer.HitTestFirst(e.Position, _inputRoot as Window, x =>
+ var window = _inputRoot as Window;
+ var visual = window?.Renderer.HitTestFirst(e.Position, window, x =>
{
if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible))
{
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 1f290acd86..50bee0d395 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -501,7 +501,7 @@ namespace Avalonia.Native
}
}
- public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.Transparent;
+ public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
diff --git a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj
index a9cad0538f..9017ce1546 100644
--- a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj
+++ b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index dcb20d2a44..ba646c64ee 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -208,6 +208,12 @@ namespace Avalonia.Skia
public void DrawLine(IPen pen, Point p1, Point p2)
{
CheckLease();
+
+ if (pen is null)
+ {
+ return;
+ }
+
using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
{
if (paint.Paint is object)
@@ -495,6 +501,12 @@ namespace Avalonia.Skia
public void DrawGlyphRun(IBrush foreground, IRef glyphRun)
{
CheckLease();
+
+ if (foreground is null)
+ {
+ return;
+ }
+
using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
index 24b8fc04b3..446db47d92 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using Avalonia.Platform;
using SharpDX.DirectWrite;
@@ -25,8 +26,6 @@ namespace Avalonia.Direct2D1.Media
}
public IReadOnlyList GetIntersections(float lowerBound, float upperBound)
- {
- return null;
- }
+ => Array.Empty();
}
}
diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
index b7dca78845..a24fe31df8 100644
--- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
+++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
index 0c0fe5b921..f3af312d1a 100644
--- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
+++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs
index 181883656c..690926a193 100644
--- a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs
+++ b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs
@@ -1,16 +1,79 @@
using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Reflection;
+using System.Text.RegularExpressions;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.XamlIl;
-namespace Avalonia.Designer.HostApp
+namespace Avalonia.Designer.HostApp;
+
+class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader
{
- class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader
+ public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
+ {
+ PreloadDepsAssemblies(configuration.LocalAssembly ?? Assembly.GetEntryAssembly());
+
+ return AvaloniaXamlIlRuntimeCompiler.Load(document, configuration);
+ }
+
+ private void PreloadDepsAssemblies(Assembly targetAssembly)
{
- public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
+ // Assemblies loaded in memory (e.g. single file) return empty string from Location.
+ // In these cases, don't try probing next to the assembly.
+ var assemblyLocation = targetAssembly.Location;
+ if (string.IsNullOrEmpty(assemblyLocation))
+ {
+ return;
+ }
+
+ var depsJsonFile = Path.ChangeExtension(assemblyLocation, ".deps.json");
+ if (!File.Exists(depsJsonFile))
+ {
+ return;
+ }
+
+ using var stream = File.OpenRead(depsJsonFile);
+
+ /*
+ We can't use any references in the Avalonia.Designer.HostApp. Including even json.
+ Ideally we would prefer Microsoft.Extensions.DependencyModel package, but can't use it here.
+ So, instead we need to fallback to some JSON parsing using pretty easy regex.
+
+ Json part example:
+"Avalonia.Xaml.Interactions/11.0.0-preview5": {
+ "dependencies": {
+ "Avalonia": "11.0.999",
+ "Avalonia.Xaml.Interactivity": "11.0.0-preview5"
+ },
+ "runtime": {
+ "lib/net6.0/Avalonia.Xaml.Interactions.dll": {
+ "assemblyVersion": "11.0.0.0",
+ "fileVersion": "11.0.0.0"
+ }
+ }
+},
+ We want to extract "lib/net6.0/Avalonia.Xaml.Interactions.dll" from here.
+ No need to resolve real path of ref assemblies.
+ No need to handle special cases with .NET Framework and GAC.
+ */
+ var text = new StreamReader(stream).ReadToEnd();
+ var matches = Regex.Matches( text, """runtime"\s*:\s*{\s*"([^"]+)""");
+
+ foreach (Match match in matches)
{
- return AvaloniaXamlIlRuntimeCompiler.Load(document, configuration);
+ if (match.Groups[1] is { Success: true } g)
+ {
+ var assemblyName = Path.GetFileNameWithoutExtension(g.Value);
+ try
+ {
+ _ = Assembly.Load(new AssemblyName(assemblyName));
+ }
+ catch
+ {
+ }
+ }
}
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
index 339cf8a334..924e844ec5 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
@@ -78,18 +78,6 @@ namespace Avalonia.Base.UnitTests.Data.Core
GC.KeepAlive(data);
}
- [Fact]
- public async Task Should_Coerce_Get_Null_Double_String_To_UnsetValue()
- {
- var data = new Class1 { StringValue = null };
- var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(double));
- var result = await target.Take(1);
-
- Assert.Equal(AvaloniaProperty.UnsetValue, result);
-
- GC.KeepAlive(data);
- }
-
[Fact]
public void Should_Convert_Set_String_To_Double()
{
@@ -249,19 +237,6 @@ namespace Avalonia.Base.UnitTests.Data.Core
GC.KeepAlive(data);
}
- [Fact]
- public void Should_Coerce_Setting_Null_Double_To_Default_Value()
- {
- var data = new Class1 { DoubleValue = 5.6 };
- var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
-
- target.OnNext(null);
-
- Assert.Equal(0, data.DoubleValue);
-
- GC.KeepAlive(data);
- }
-
[Fact]
public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value()
{
diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
index 07d2d672ae..c1468a28e4 100644
--- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
@@ -29,7 +29,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
double height,
double scaleX,
double scaleY,
- double? penThickness,
+ double penThickness,
double expectedX,
double expectedY,
double expectedWidth,
@@ -38,7 +38,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
var target = new TestRectangleDrawOperation(
new Rect(x, y, width, height),
Matrix.CreateScale(scaleX, scaleY),
- penThickness.HasValue ? new Pen(Brushes.Black, penThickness.Value) : null);
+ new Pen(Brushes.Black, penThickness));
Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds);
}
diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
index 0ddee2ad7a..9ea0482abc 100644
--- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
+++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
index 4ff98bdedd..2679d4ce06 100644
--- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
@@ -140,10 +140,9 @@ namespace Avalonia.Controls.UnitTests
.Returns>((p, r, f) =>
r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]);
- var target = new TestButton()
+ var target = new TestButton(renderer.Object)
{
- Bounds = new Rect(0, 0, 100, 100),
- Renderer = renderer.Object
+ Bounds = new Rect(0, 0, 100, 100)
};
bool clicked = false;
@@ -172,10 +171,9 @@ namespace Avalonia.Controls.UnitTests
.Returns>((p, r, f) =>
r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]);
- var target = new TestButton()
+ var target = new TestButton(renderer.Object)
{
- Bounds = new Rect(0, 0, 100, 100),
- Renderer = renderer.Object
+ Bounds = new Rect(0, 0, 100, 100)
};
bool clicked = false;
@@ -206,11 +204,10 @@ namespace Avalonia.Controls.UnitTests
r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
new Visual[] { r } : new Visual[0]);
- var target = new TestButton()
+ var target = new TestButton(renderer.Object)
{
Bounds = new Rect(0, 0, 100, 100),
- RenderTransform = new TranslateTransform { X = 100, Y = 0 },
- Renderer = renderer.Object
+ RenderTransform = new TranslateTransform { X = 100, Y = 0 }
};
//actual bounds of button should be 100,0,100,100 x -> translated 100 pixels
@@ -386,9 +383,10 @@ namespace Avalonia.Controls.UnitTests
private class TestButton : Button, IRenderRoot
{
- public TestButton()
+ public TestButton(IRenderer renderer)
{
IsVisible = true;
+ Renderer = renderer;
}
public new Rect Bounds
@@ -399,7 +397,7 @@ namespace Avalonia.Controls.UnitTests
public Size ClientSize => throw new NotImplementedException();
- public IRenderer Renderer { get; set; }
+ public IRenderer Renderer { get; }
public double RenderScaling => throw new NotImplementedException();
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs
new file mode 100644
index 0000000000..df81b1faae
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs
@@ -0,0 +1,330 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+using Avalonia.Styling;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests.Primitives
+{
+ public class SelectingItemsControlTests_SelectedValue
+ {
+ [Fact]
+ public void Setting_SelectedItem_Sets_SelectedValue()
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ SelectedValueBinding = new Binding("Name"),
+ Template = Template()
+ };
+
+ sic.SelectedItem = items[0];
+
+ Assert.Equal(items[0].Name, sic.SelectedValue);
+ }
+
+ [Fact]
+ public void Setting_SelectedIndex_Sets_SelectedValue()
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ SelectedValueBinding = new Binding("Name"),
+ Template = Template()
+ };
+
+ sic.SelectedIndex = 0;
+
+ Assert.Equal(items[0].Name, sic.SelectedValue);
+ }
+
+ [Fact]
+ public void Setting_SelectedItems_Sets_SelectedValue()
+ {
+ var items = TestClass.GetItems();
+ var sic = new ListBox
+ {
+ Items = items,
+ SelectedValueBinding = new Binding("Name"),
+ Template = Template()
+ };
+
+ sic.SelectedItems = new List
+ {
+ items[1],
+ items[3],
+ items[4]
+ };
+
+ // When interacting, SelectedItem is the first item in the SelectedItems collection
+ // But when set here, it's the last
+ Assert.Equal(items[4].Name, sic.SelectedValue);
+ }
+
+ [Fact]
+ public void Setting_SelectedValue_Sets_SelectedIndex()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ SelectedValueBinding = new Binding("Name"),
+ Template = Template()
+ };
+
+ Prepare(sic);
+
+ sic.SelectedValue = items[1].Name;
+
+ Assert.Equal(1, sic.SelectedIndex);
+ }
+ }
+
+ [Fact]
+ public void Setting_SelectedValue_Sets_SelectedItem()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ SelectedValueBinding = new Binding("Name"),
+ Template = Template()
+ };
+
+ Prepare(sic);
+
+ sic.SelectedValue = "Item2";
+
+ Assert.Equal(items[1], sic.SelectedItem);
+ }
+ }
+
+ [Fact]
+ public void Changing_SelectedValueBinding_Updates_SelectedValue()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ SelectedValueBinding = new Binding("Name"),
+ Template = Template()
+ };
+
+ sic.SelectedValue = "Item2";
+
+ sic.SelectedValueBinding = new Binding("AltProperty");
+
+ // Ensure SelectedItem didn't change
+ Assert.Equal(items[1], sic.SelectedItem);
+
+
+ Assert.Equal("Alt2", sic.SelectedValue);
+ }
+ }
+
+ [Fact]
+ public void SelectedValue_With_Null_SelectedValueBinding_Is_Item()
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ Template = Template()
+ };
+
+ sic.SelectedIndex = 0;
+
+ Assert.Equal(items[0], sic.SelectedValue);
+ }
+
+ [Fact]
+ public void Setting_SelectedValue_Before_Initialize_Should_Retain_Selection()
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ Template = Template(),
+ SelectedValueBinding = new Binding("Name"),
+ SelectedValue = "Item2"
+ };
+
+ sic.BeginInit();
+ sic.EndInit();
+
+ Assert.Equal(items[1].Name, sic.SelectedValue);
+ }
+
+ [Fact]
+ public void Setting_SelectedValue_During_Initialize_Should_Take_Priority_Over_Previous_Value()
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ Template = Template(),
+ SelectedValueBinding = new Binding("Name"),
+ SelectedValue = "Item2"
+ };
+
+ sic.BeginInit();
+ sic.SelectedValue = "Item1";
+ sic.EndInit();
+
+ Assert.Equal(items[0].Name, sic.SelectedValue);
+ }
+
+ [Fact]
+ public void Changing_Items_Should_Clear_SelectedValue()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ Template = Template(),
+ SelectedValueBinding = new Binding("Name"),
+ SelectedValue = "Item2"
+ };
+
+ Prepare(sic);
+
+ sic.Items = new List
+ {
+ new TestClass("NewItem", string.Empty)
+ };
+
+ Assert.Equal(null, sic.SelectedValue);
+ }
+ }
+
+ [Fact]
+ public void Setting_SelectedValue_Should_Raise_SelectionChanged_Event()
+ {
+ // Unlike SelectedIndex/SelectedItem tests, we need the ItemsControl to
+ // initialize so that SelectedValue can actually be looked up
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ Template = Template(),
+ SelectedValueBinding = new Binding("Name"),
+ };
+
+ Prepare(sic);
+
+ var called = false;
+ sic.SelectionChanged += (s, e) =>
+ {
+ Assert.Same(items[1], e.AddedItems.Cast().Single());
+ Assert.Empty(e.RemovedItems);
+ called = true;
+ };
+
+ sic.SelectedValue = "Item2";
+ Assert.True(called);
+ }
+ }
+
+ [Fact]
+ public void Clearing_SelectedValue_Should_Raise_SelectionChanged_Event()
+ {
+ var items = TestClass.GetItems();
+ var sic = new SelectingItemsControl
+ {
+ Items = items,
+ Template = Template(),
+ SelectedValueBinding = new Binding("Name"),
+ SelectedValue = "Item2"
+ };
+
+ var called = false;
+ sic.SelectionChanged += (s, e) =>
+ {
+ Assert.Same(items[1], e.RemovedItems.Cast().Single());
+ Assert.Empty(e.AddedItems);
+ called = true;
+ };
+
+ sic.SelectedValue = null;
+ Assert.True(called);
+ }
+
+ private static FuncControlTemplate Template()
+ {
+ return new FuncControlTemplate((control, scope) =>
+ new ItemsPresenter
+ {
+ Name = "itemsPresenter",
+ [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
+ }.RegisterInNameScope(scope));
+ }
+
+ private static void Prepare(SelectingItemsControl target)
+ {
+ var root = new TestRoot
+ {
+ Child = target,
+ Width = 100,
+ Height = 100,
+ Styles =
+ {
+ new Style(x => x.Is())
+ {
+ Setters =
+ {
+ new Setter(ListBox.TemplateProperty, Template()),
+ },
+ },
+ },
+ };
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+ }
+ }
+
+ internal class TestClass
+ {
+ public TestClass(string name, string alt)
+ {
+ Name = name;
+ AltProperty = alt;
+ }
+
+ public string Name { get; set; }
+
+ public string AltProperty { get; set; }
+
+ public static List GetItems()
+ {
+ return new List
+ {
+ new TestClass("Item1", "Alt1"),
+ new TestClass("Item2", "Alt2"),
+ new TestClass("Item3", "Alt3"),
+ new TestClass("Item4", "Alt4"),
+ new TestClass("Item5", "Alt5"),
+ };
+ }
+ }
+}
+
+
diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
index d63251c1f5..2644e7184a 100644
--- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
@@ -6,6 +6,7 @@ using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Platform;
+using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
@@ -20,7 +21,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
var target = new TestTopLevel(impl.Object);
Assert.True(((ILogical)target).IsAttachedToLogicalTree);
@@ -32,7 +33,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object);
@@ -46,7 +47,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object);
@@ -60,7 +61,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object);
@@ -76,7 +77,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(services))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
var target = new TestTopLevel(impl.Object, Mock.Of());
@@ -91,7 +92,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.SetupProperty(x => x.Resized);
impl.SetupGet(x => x.RenderScaling).Returns(1);
@@ -117,7 +118,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
var target = new TestTopLevel(impl.Object);
@@ -133,7 +134,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
impl.Setup(x => x.ClientSize).Returns(new Size(123, 456));
@@ -151,7 +152,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
bool raised = false;
@@ -169,7 +170,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
@@ -200,7 +201,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(services))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
@@ -222,7 +223,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
var child = new TestTopLevel(impl.Object);
@@ -240,7 +241,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
var raised = false;
@@ -257,7 +258,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.SetupAllProperties();
var layoutManager = new Mock();
@@ -274,7 +275,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockTopLevelImpl();
impl.SetupGet(x => x.RenderScaling).Returns(1);
var child = new Border { Classes = { "foo" } };
@@ -317,6 +318,14 @@ namespace Avalonia.Controls.UnitTests
}.RegisterInNameScope(scope));
}
+ private static Mock CreateMockTopLevelImpl()
+ {
+ var renderer = new Mock();
+ renderer.Setup(r => r.CreateRenderer(It.IsAny()))
+ .Returns(RendererMocks.CreateRenderer().Object);
+ return renderer;
+ }
+
private class TestTopLevel : TopLevel
{
private readonly ILayoutManager _layoutManager;
diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
index f367112cc0..336aad79da 100644
--- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
@@ -4,9 +4,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
-using Avalonia.Styling;
using Avalonia.UnitTests;
-using Moq;
using Xunit;
using Factory = System.Func, Avalonia.Controls.Window, Avalonia.AvaloniaObject>;
@@ -20,7 +18,7 @@ namespace Avalonia.Controls.UnitTests.Utils
using (AvaloniaLocator.EnterScope())
{
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(new WindowingPlatformMock());
+ .Bind().ToConstant(new MockWindowingPlatform());
var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
var gesture2 = new KeyGesture(Key.B, KeyModifiers.Control);
@@ -64,7 +62,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var commandResult = 0;
var expectedParameter = 1;
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(new WindowingPlatformMock());
+ .Bind().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
@@ -106,7 +104,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var target = new KeyboardDevice();
var isExecuted = false;
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(new WindowingPlatformMock());
+ .Bind().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
@@ -146,7 +144,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var target = new KeyboardDevice();
var clickExecutedCount = 0;
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(new WindowingPlatformMock());
+ .Bind().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
@@ -199,7 +197,7 @@ namespace Avalonia.Controls.UnitTests.Utils
var clickExecutedCount = 0;
var commandExecutedCount = 0;
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(new WindowingPlatformMock());
+ .Bind().ToConstant(new MockWindowingPlatform());
var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
index a10b1324d6..d65fa06183 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockWindowBaseImpl();
var target = new TestWindowBase(impl.Object);
target.Activate();
@@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockWindowBaseImpl();
impl.SetupAllProperties();
bool raised = false;
@@ -55,7 +55,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var impl = new Mock();
+ var impl = CreateMockWindowBaseImpl();
impl.SetupAllProperties();
bool raised = false;
@@ -241,6 +241,14 @@ namespace Avalonia.Controls.UnitTests
}.RegisterInNameScope(scope));
}
+ private static Mock CreateMockWindowBaseImpl()
+ {
+ var renderer = new Mock();
+ renderer.Setup(r => r.CreateRenderer(It.IsAny()))
+ .Returns(RendererMocks.CreateRenderer().Object);
+ return renderer;
+ }
+
private class TestWindowBase : WindowBase
{
public bool IsClosed { get; private set; }
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index 014174990e..cada2bfa6f 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -15,6 +15,8 @@ namespace Avalonia.Controls.UnitTests
public void Setting_Title_Should_Set_Impl_Title()
{
var windowImpl = new Mock();
+ windowImpl.Setup(r => r.CreateRenderer(It.IsAny()))
+ .Returns(RendererMocks.CreateRenderer().Object);
var windowingPlatform = new MockWindowingPlatform(() => windowImpl.Object);
using (UnitTestApplication.Start(new TestServices(windowingPlatform: windowingPlatform)))
diff --git a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs b/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs
deleted file mode 100644
index e8471d41fb..0000000000
--- a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using Moq;
-using Avalonia.Platform;
-
-namespace Avalonia.Controls.UnitTests
-{
- public class WindowingPlatformMock : IWindowingPlatform
- {
- private readonly Func _windowImpl;
- private readonly Func _popupImpl;
-
- public WindowingPlatformMock(Func windowImpl = null, Func popupImpl = null )
- {
- _windowImpl = windowImpl;
- _popupImpl = popupImpl;
- }
-
- public IWindowImpl CreateWindow()
- {
- return _windowImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1);
- }
-
- public IWindowImpl CreateEmbeddableWindow()
- {
- throw new NotImplementedException();
- }
-
- public ITrayIconImpl CreateTrayIcon()
- {
- return null;
- }
-
- public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1);
- }
-}
diff --git a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj
index 57338a1e08..5de2b85569 100644
--- a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj
+++ b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj
@@ -10,10 +10,11 @@
-
+
+
diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
index 4d833cdb1f..7bb991aae6 100644
--- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
@@ -1,11 +1,14 @@
using System;
+using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Controls;
+using Avalonia.Media.Imaging;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Interactions;
+using SixLabors.ImageSharp.PixelFormats;
using Xunit;
using Xunit.Sdk;
@@ -141,7 +144,6 @@ namespace Avalonia.IntegrationTests.Appium
}
}
-
[Theory]
[InlineData(ShowWindowMode.NonOwned)]
[InlineData(ShowWindowMode.Owned)]
@@ -187,6 +189,47 @@ namespace Avalonia.IntegrationTests.Appium
}
}
+ [Fact]
+ public void TransparentWindow()
+ {
+ var showTransparentWindow = _session.FindElementByAccessibilityId("ShowTransparentWindow");
+ showTransparentWindow.Click();
+ Thread.Sleep(1000);
+
+ var window = _session.FindElementByAccessibilityId("TransparentWindow");
+ var screenshot = window.GetScreenshot();
+
+ window.Click();
+
+ var img = SixLabors.ImageSharp.Image.Load(screenshot.AsByteArray);
+ var topLeftColor = img[10, 10];
+ var centerColor = img[img.Width / 2, img.Height / 2];
+
+ Assert.Equal(new Rgba32(0, 128, 0), topLeftColor);
+ Assert.Equal(new Rgba32(255, 0, 0), centerColor);
+ }
+
+ [Fact]
+ public void TransparentPopup()
+ {
+ var showTransparentWindow = _session.FindElementByAccessibilityId("ShowTransparentPopup");
+ showTransparentWindow.Click();
+ Thread.Sleep(1000);
+
+ var window = _session.FindElementByAccessibilityId("TransparentPopupBackground");
+ var container = window.FindElementByAccessibilityId("PopupContainer");
+ var screenshot = container.GetScreenshot();
+
+ window.Click();
+
+ var img = SixLabors.ImageSharp.Image.Load(screenshot.AsByteArray);
+ var topLeftColor = img[10, 10];
+ var centerColor = img[img.Width / 2, img.Height / 2];
+
+ Assert.Equal(new Rgba32(0, 128, 0), topLeftColor);
+ Assert.Equal(new Rgba32(255, 0, 0), centerColor);
+ }
+
public static TheoryData StartupLocationData()
{
var sizes = new Size?[] { null, new Size(400, 300) };
diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj
index 15815c81b6..c3d9aa0622 100644
--- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj
+++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj
@@ -1,6 +1,6 @@
- net461
+ net462
diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
index 3ba8e8354d..c312a71d44 100644
--- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
+++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
@@ -648,16 +648,69 @@ namespace Avalonia.Markup.UnitTests.Data
};
}
+ [Fact]
+ public void Binding_Producing_Default_Value_Should_Result_In_Correct_Priority()
+ {
+ var defaultValue = StyledPropertyClass.NullableDoubleProperty.GetDefaultValue(typeof(StyledPropertyClass));
+
+ var vm = new NullableValuesViewModel() { NullableDouble = defaultValue };
+ var target = new StyledPropertyClass();
+
+ target.Bind(StyledPropertyClass.NullableDoubleProperty, new Binding(nameof(NullableValuesViewModel.NullableDouble)) { Source = vm });
+
+ Assert.Equal(BindingPriority.LocalValue, target.GetDiagnosticInternal(StyledPropertyClass.NullableDoubleProperty).Priority);
+ Assert.Equal(defaultValue, target.GetValue(StyledPropertyClass.NullableDoubleProperty));
+ }
+
+ [Fact]
+ public void Binding_Non_Nullable_ValueType_To_Null_Reverts_To_Default_Value()
+ {
+ var source = new NullableValuesViewModel { NullableDouble = 42 };
+ var target = new StyledPropertyClass();
+ var binding = new Binding(nameof(source.NullableDouble)) { Source = source };
+
+ target.Bind(StyledPropertyClass.DoubleValueProperty, binding);
+ Assert.Equal(42, target.DoubleValue);
+
+ source.NullableDouble = null;
+
+ Assert.Equal(12.3, target.DoubleValue);
+ }
+
+ [Fact]
+ public void Binding_Nullable_ValueType_To_Null_Sets_Value_To_Null()
+ {
+ var source = new NullableValuesViewModel { NullableDouble = 42 };
+ var target = new StyledPropertyClass();
+ var binding = new Binding(nameof(source.NullableDouble)) { Source = source };
+
+ target.Bind(StyledPropertyClass.NullableDoubleProperty, binding);
+ Assert.Equal(42, target.NullableDouble);
+
+ source.NullableDouble = null;
+
+ Assert.Null(target.NullableDouble);
+ }
+
private class StyledPropertyClass : AvaloniaObject
{
public static readonly StyledProperty DoubleValueProperty =
- AvaloniaProperty.Register(nameof(DoubleValue));
+ AvaloniaProperty.Register(nameof(DoubleValue), 12.3);
public double DoubleValue
{
get { return GetValue(DoubleValueProperty); }
set { SetValue(DoubleValueProperty, value); }
}
+
+ public static StyledProperty NullableDoubleProperty =
+ AvaloniaProperty.Register(nameof(NullableDoubleProperty), -1);
+
+ public double? NullableDouble
+ {
+ get => GetValue(NullableDoubleProperty);
+ set => SetValue(NullableDoubleProperty, value);
+ }
}
private class DirectPropertyClass : AvaloniaObject
@@ -676,6 +729,21 @@ namespace Avalonia.Markup.UnitTests.Data
}
}
+ private class NullableValuesViewModel : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private double? _nullableDouble;
+ public double? NullableDouble
+ {
+ get => _nullableDouble; set
+ {
+ _nullableDouble = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NullableDouble)));
+ }
+ }
+ }
+
private class TestStackOverflowViewModel : INotifyPropertyChanged
{
public int SetterInvokedCount { get; private set; }
diff --git a/tests/Avalonia.UnitTests/MockGlyphRun.cs b/tests/Avalonia.UnitTests/MockGlyphRun.cs
index 477f34565f..0319803a5e 100644
--- a/tests/Avalonia.UnitTests/MockGlyphRun.cs
+++ b/tests/Avalonia.UnitTests/MockGlyphRun.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
@@ -24,12 +25,9 @@ namespace Avalonia.UnitTests
public void Dispose()
{
-
}
public IReadOnlyList GetIntersections(float lowerBound, float upperBound)
- {
- return null;
- }
+ => Array.Empty();
}
}
diff --git a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs
deleted file mode 100644
index 38ab3c3c5d..0000000000
--- a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using System;
-using Avalonia.Controls;
-using Avalonia.Controls.Presenters;
-using Avalonia.Controls.Templates;
-using Avalonia.Layout;
-using Avalonia.LogicalTree;
-using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.Styling;
-
-namespace Avalonia.UnitTests
-{
- public class TestTemplatedRoot : ContentControl, ILayoutRoot, IRenderRoot, ILogicalRoot
- {
- private readonly NameScope _nameScope = new NameScope();
-
- public TestTemplatedRoot()
- {
- LayoutManager = new LayoutManager(this);
- Template = new FuncControlTemplate((x, scope) => new ContentPresenter
- {
- Name = "PART_ContentPresenter",
- }.RegisterInNameScope(scope));
- }
-
- public Size ClientSize => new Size(100, 100);
-
- public Size MaxClientSize => Size.Infinity;
-
- public double LayoutScaling => 1;
-
- public ILayoutManager LayoutManager { get; set; }
-
- public double RenderScaling => 1;
-
- public IRenderTarget RenderTarget => null;
-
- public IRenderer Renderer => null;
-
- public IRenderTarget CreateRenderTarget()
- {
- throw new NotImplementedException();
- }
-
- public void Invalidate(Rect rect)
- {
- throw new NotImplementedException();
- }
-
- public Point PointToClient(PixelPoint p) => p.ToPoint(1);
-
- public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
- }
-}