Ver código fonte

Merge branch 'master' into alloc-classhandlers

Dariusz Komosiński 6 anos atrás
pai
commit
a61f310553
40 arquivos alterados com 718 adições e 361 exclusões
  1. 10 13
      src/Avalonia.Base/AvaloniaObject.cs
  2. 1 1
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  3. 71 0
      src/Avalonia.Base/Logging/ILogSink.cs
  4. 21 104
      src/Avalonia.Base/Logging/Logger.cs
  5. 174 0
      src/Avalonia.Base/Logging/ParametrizedLogger.cs
  6. 1 1
      src/Avalonia.Base/PriorityValue.cs
  7. 2 2
      src/Avalonia.Controls/DropDown.cs
  8. 1 1
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  9. 1 1
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  10. 1 1
      src/Avalonia.Controls/TopLevel.cs
  11. 11 1
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  12. 23 10
      src/Avalonia.Layout/LayoutManager.cs
  13. 4 4
      src/Avalonia.Layout/Layoutable.cs
  14. 101 3
      src/Avalonia.Logging.Serilog/SerilogLogger.cs
  15. 1 1
      src/Avalonia.OpenGL/EglGlPlatformFeature.cs
  16. 2 2
      src/Avalonia.Styling/StyledElement.cs
  17. 51 39
      src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
  18. 2 2
      src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
  19. 55 16
      src/Avalonia.Visuals/CornerRadius.cs
  20. 5 11
      src/Avalonia.Visuals/Matrix.cs
  21. 16 12
      src/Avalonia.Visuals/Media/PixelPoint.cs
  22. 14 12
      src/Avalonia.Visuals/Media/PixelRect.cs
  23. 14 12
      src/Avalonia.Visuals/Media/PixelSize.cs
  24. 18 13
      src/Avalonia.Visuals/Point.cs
  25. 25 17
      src/Avalonia.Visuals/Rect.cs
  26. 3 9
      src/Avalonia.Visuals/RelativePoint.cs
  27. 4 10
      src/Avalonia.Visuals/RelativeRect.cs
  28. 2 1
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  29. 2 1
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  30. 2 2
      src/Avalonia.Visuals/Rendering/RenderLoop.cs
  31. 17 13
      src/Avalonia.Visuals/Size.cs
  32. 21 17
      src/Avalonia.Visuals/Thickness.cs
  33. 2 9
      src/Avalonia.Visuals/Vector.cs
  34. 3 4
      src/Avalonia.Visuals/Visual.cs
  35. 3 10
      src/Avalonia.Visuals/VisualTree/TransformedBounds.cs
  36. 1 1
      src/Avalonia.X11/Glx/GlxPlatformFeature.cs
  37. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  38. 1 1
      src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs
  39. 1 1
      src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs
  40. 30 2
      tests/Avalonia.UnitTests/TestLogSink.cs

+ 10 - 13
src/Avalonia.Base/AvaloniaObject.cs

@@ -326,8 +326,6 @@ namespace Avalonia
 
             VerifyAccess();
 
-            var description = GetDescription(source);
-
             if (property.IsDirect)
             {
                 if (property.IsReadOnly)
@@ -335,12 +333,12 @@ namespace Avalonia
                     throw new ArgumentException($"The property {property.Name} is readonly.");
                 }
 
-                Logger.Verbose(
-                    LogArea.Property, 
+                Logger.TryGet(LogEventLevel.Verbose)?.Log(
+                    LogArea.Property,
                     this,
-                    "Bound {Property} to {Binding} with priority LocalValue", 
-                    property, 
-                    description);
+                    "Bound {Property} to {Binding} with priority LocalValue",
+                    property,
+                    GetDescription(source));
 
                 if (_directBindings == null)
                 {
@@ -351,12 +349,12 @@ namespace Avalonia
             }
             else
             {
-                Logger.Verbose(
+                Logger.TryGet(LogEventLevel.Verbose)?.Log(
                     LogArea.Property,
                     this,
                     "Bound {Property} to {Binding} with priority {Priority}",
                     property,
-                    description,
+                    GetDescription(source),
                     priority);
 
                 return Values.AddBinding(property, source, priority);
@@ -406,7 +404,7 @@ namespace Avalonia
             {
                 RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority);
 
-                Logger.Verbose(
+                Logger.TryGet(LogEventLevel.Verbose)?.Log(
                     LogArea.Property,
                     this,
                     "{Property} changed from {$Old} to {$Value} with priority {Priority}",
@@ -458,8 +456,7 @@ namespace Avalonia
         /// <param name="e">The binding error.</param>
         protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e)
         {
-            Logger.Log(
-                LogEventLevel.Warning,
+            Logger.TryGet(LogEventLevel.Warning)?.Log(
                 LogArea.Binding,
                 this,
                 "Error in binding to {Target}.{Property}: {Message}",
@@ -812,7 +809,7 @@ namespace Avalonia
         /// <param name="priority">The priority.</param>
         private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority)
         {
-            Logger.Verbose(
+            Logger.TryGet(LogEventLevel.Verbose)?.Log(
                 LogArea.Property,
                 this,
                 "Set {Property} to {$Value} with priority {Priority}",

+ 1 - 1
src/Avalonia.Base/Data/Core/BindingExpression.cs

@@ -165,7 +165,7 @@ namespace Avalonia.Data.Core
                             }
                             else
                             {
-                                Logger.Error(
+                                Logger.TryGet(LogEventLevel.Error)?.Log(
                                     LogArea.Binding,
                                     this,
                                     "Could not convert FallbackValue {FallbackValue} to {Type}",

+ 71 - 0
src/Avalonia.Base/Logging/ILogSink.cs

@@ -8,6 +8,77 @@ namespace Avalonia.Logging
     /// </summary>
     public interface ILogSink
     {
+        /// <summary>
+        /// Checks if given log level is enabled.
+        /// </summary>
+        /// <param name="level">The log event level.</param>
+        /// <returns><see langword="true"/> if given log level is enabled.</returns>
+        bool IsEnabled(LogEventLevel level);
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="level">The log event level.</param>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        void Log(
+            LogEventLevel level,
+            string area,
+            object source,
+            string messageTemplate);
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="level">The log event level.</param>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        /// <param name="propertyValue0">Message property value.</param>
+        void Log<T0>(
+            LogEventLevel level,
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0);
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="level">The log event level.</param>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        /// <param name="propertyValue0">Message property value.</param>
+        /// <param name="propertyValue1">Message property value.</param>
+        void Log<T0, T1>(
+            LogEventLevel level,
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1);
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="level">The log event level.</param>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        /// <param name="propertyValue0">Message property value.</param>
+        /// <param name="propertyValue1">Message property value.</param>
+        /// <param name="propertyValue2">Message property value.</param>
+        void Log<T0, T1, T2>(
+            LogEventLevel level,
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1,
+            T2 propertyValue2);
+
         /// <summary>
         /// Logs a new event.
         /// </summary>

+ 21 - 104
src/Avalonia.Base/Logging/Logger.cs

@@ -1,8 +1,6 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
-using System.Runtime.CompilerServices;
-
 namespace Avalonia.Logging
 {
     /// <summary>
@@ -16,124 +14,43 @@ namespace Avalonia.Logging
         public static ILogSink Sink { get; set; }
 
         /// <summary>
-        /// Logs an event.
+        /// Checks if given log level is enabled.
         /// </summary>
         /// <param name="level">The log event level.</param>
-        /// <param name="area">The area that the event originates.</param>
-        /// <param name="source">The object from which the event originates.</param>
-        /// <param name="messageTemplate">The message template.</param>
-        /// <param name="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Log(
-            LogEventLevel level, 
-            string area,
-            object source,
-            string messageTemplate, 
-            params object[] propertyValues)
-        {
-            Sink?.Log(level, area, source, messageTemplate, propertyValues);
-        }
-
-        /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Verbose"/> level.
-        /// </summary>
-        /// <param name="area">The area that the event originates.</param>
-        /// <param name="source">The object from which the event originates.</param>
-        /// <param name="messageTemplate">The message template.</param>
-        /// <param name="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Verbose(
-            string area,
-            object source,
-            string messageTemplate, 
-            params object[] propertyValues)
+        /// <returns><see langword="true"/> if given log level is enabled.</returns>
+        public static bool IsEnabled(LogEventLevel level)
         {
-            Log(LogEventLevel.Verbose, area, source, messageTemplate, propertyValues);
+            return Sink?.IsEnabled(level) == true;
         }
 
         /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Debug"/> level.
+        /// Returns parametrized logging sink if given log level is enabled.
         /// </summary>
-        /// <param name="area">The area that the event originates.</param>
-        /// <param name="source">The object from which the event originates.</param>
-        /// <param name="messageTemplate">The message template.</param>
-        /// <param name="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Debug(
-            string area,
-            object source,
-            string messageTemplate,
-            params object[] propertyValues)
+        /// <param name="level">The log event level.</param>
+        /// <returns>Log sink or <see langword="null"/> if log level is not enabled.</returns>
+        public static ParametrizedLogger? TryGet(LogEventLevel level)
         {
-            Log(LogEventLevel.Debug, area, source, messageTemplate, propertyValues);
-        }
+            if (!IsEnabled(level))
+            {
+                return null;
+            }
 
-        /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Information"/> level.
-        /// </summary>
-        /// <param name="area">The area that the event originates.</param>
-        /// <param name="source">The object from which the event originates.</param>
-        /// <param name="messageTemplate">The message template.</param>
-        /// <param name="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Information(
-            string area,
-            object source,
-            string messageTemplate,
-            params object[] propertyValues)
-        {
-            Log(LogEventLevel.Information, area, source, messageTemplate, propertyValues);
+            return new ParametrizedLogger(Sink, level);
         }
 
         /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Warning"/> level.
+        /// Returns parametrized logging sink if given log level is enabled.
         /// </summary>
-        /// <param name="area">The area that the event originates.</param>
-        /// <param name="source">The object from which the event originates.</param>
-        /// <param name="messageTemplate">The message template.</param>
-        /// <param name="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Warning(
-            string area,
-            object source,
-            string messageTemplate,
-            params object[] propertyValues)
+        /// <param name="level">The log event level.</param>
+        /// <param name="outLogger">Log sink that is valid only if method returns <see langword="true"/>.</param>
+        /// <returns><see langword="true"/> if logger was obtained successfully.</returns>
+        public static bool TryGet(LogEventLevel level, out ParametrizedLogger outLogger)
         {
-            Log(LogEventLevel.Warning, area, source, messageTemplate, propertyValues);
-        }
+            ParametrizedLogger? logger = TryGet(level);
 
-        /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Error"/> level.
-        /// </summary>
-        /// <param name="area">The area that the event originates.</param>
-        /// <param name="source">The object from which the event originates.</param>
-        /// <param name="messageTemplate">The message template.</param>
-        /// <param name="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Error(
-            string area,
-            object source,
-            string messageTemplate, 
-            params object[] propertyValues)
-        {
-            Log(LogEventLevel.Error, area, source, messageTemplate, propertyValues);
-        }
+            outLogger = logger.GetValueOrDefault();
 
-        /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Fatal"/> level.
-        /// </summary>
-        /// <param name="area">The area that the event originates.</param>
-        /// <param name="source">The object from which the event originates.</param>
-        /// <param name="messageTemplate">The message template.</param>
-        /// <param name="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Fatal(
-            string area,
-            object source,
-            string messageTemplate,
-            params object[] propertyValues)
-        {
-            Log(LogEventLevel.Fatal, area, source, messageTemplate, propertyValues);
+            return logger.HasValue;
         }
     }
 }

+ 174 - 0
src/Avalonia.Base/Logging/ParametrizedLogger.cs

@@ -0,0 +1,174 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+namespace Avalonia.Logging
+{
+    /// <summary>
+    /// Logger sink parametrized for given logging level.
+    /// </summary>
+    public readonly struct ParametrizedLogger
+    {
+        private readonly ILogSink _sink;
+        private readonly LogEventLevel _level;
+
+        public ParametrizedLogger(ILogSink sink, LogEventLevel level)
+        {
+            _sink = sink;
+            _level = level;
+        }
+
+        /// <summary>
+        /// Checks if this logger can be used.
+        /// </summary>
+        public bool IsValid => _sink != null;
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log(
+            string area,
+            object source,
+            string messageTemplate)
+        {
+            _sink.Log(_level, area, source, messageTemplate);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        /// <param name="propertyValue0">Message property value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        /// <param name="propertyValue0">Message property value.</param>
+        /// <param name="propertyValue1">Message property value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0, T1>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        /// <param name="propertyValue0">Message property value.</param>
+        /// <param name="propertyValue1">Message property value.</param>
+        /// <param name="propertyValue2">Message property value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0, T1, T2>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1,
+            T2 propertyValue2)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        /// <param name="propertyValue0">Message property value.</param>
+        /// <param name="propertyValue1">Message property value.</param>
+        /// <param name="propertyValue2">Message property value.</param>
+        /// <param name="propertyValue3">Message property value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0, T1, T2, T3>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1,
+            T2 propertyValue2,
+            T3 propertyValue3)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        /// <param name="propertyValue0">Message property value.</param>
+        /// <param name="propertyValue1">Message property value.</param>
+        /// <param name="propertyValue2">Message property value.</param>
+        /// <param name="propertyValue3">Message property value.</param>
+        /// <param name="propertyValue4">Message property value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0, T1, T2, T3, T4>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1,
+            T2 propertyValue2,
+            T3 propertyValue3,
+            T4 propertyValue4)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <param name="area">The area that the event originates.</param>
+        /// <param name="source">The object from which the event originates.</param>
+        /// <param name="messageTemplate">The message template.</param>
+        /// <param name="propertyValue0">Message property value.</param>
+        /// <param name="propertyValue1">Message property value.</param>
+        /// <param name="propertyValue2">Message property value.</param>
+        /// <param name="propertyValue3">Message property value.</param>
+        /// <param name="propertyValue4">Message property value.</param>
+        /// <param name="propertyValue5">Message property value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0, T1, T2, T3, T4, T5>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1,
+            T2 propertyValue2,
+            T3 propertyValue3,
+            T4 propertyValue4,
+            T5 propertyValue5)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4, propertyValue5);
+        }
+    }
+}

+ 1 - 1
src/Avalonia.Base/PriorityValue.cs

@@ -301,7 +301,7 @@ namespace Avalonia
             }
             else
             {
-                Logger.Error(
+                Logger.TryGet(LogEventLevel.Error)?.Log(
                     LogArea.Binding,
                     Owner,
                     "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",

+ 2 - 2
src/Avalonia.Controls/DropDown.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Controls
     {
         public DropDown()
         {
-            Logger.Warning(LogArea.Control, this, "DropDown is deprecated: Use ComboBox");
+            Logger.TryGet(LogEventLevel.Warning)?.Log(LogArea.Control, this, "DropDown is deprecated: Use ComboBox");
         }
 
         Type IStyleable.StyleKey => typeof(ComboBox);
@@ -20,7 +20,7 @@ namespace Avalonia.Controls
     {
         public DropDownItem()
         {
-            Logger.Warning(LogArea.Control, this, "DropDownItem is deprecated: Use ComboBoxItem");
+            Logger.TryGet(LogEventLevel.Warning)?.Log(LogArea.Control, this, "DropDownItem is deprecated: Use ComboBoxItem");
         }
 
         Type IStyleable.StyleKey => typeof(ComboBoxItem);

+ 1 - 1
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -1062,7 +1062,7 @@ namespace Avalonia.Controls.Primitives
             }
             catch (Exception ex)
             {
-                Logger.Error(
+                Logger.TryGet(LogEventLevel.Error)?.Log(
                     LogArea.Property,
                     this,
                     "Error thrown updating SelectedItems: {Error}",

+ 1 - 1
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -255,7 +255,7 @@ namespace Avalonia.Controls.Primitives
 
                 if (template != null)
                 {
-                    Logger.Verbose(LogArea.Control, this, "Creating control template");
+                    Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Control, this, "Creating control template");
 
                     var (child, nameScope) = template.Build(this);
                     ApplyTemplatedParent(child);

+ 1 - 1
src/Avalonia.Controls/TopLevel.cs

@@ -330,7 +330,7 @@ namespace Avalonia.Controls
 
             if (result == null)
             {
-                Logger.Warning(
+                Logger.TryGet(LogEventLevel.Warning)?.Log(
                     LogArea.Control,
                     this,
                     "Could not create {Service} : maybe Application.RegisterServices() wasn't called?",

+ 11 - 1
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@@ -69,7 +69,17 @@ namespace Avalonia.DesignerSupport
                 }
 
                 if (!window.IsSet(Window.SizeToContentProperty))
-                    window.SizeToContent = SizeToContent.WidthAndHeight;
+                {
+                    if (double.IsNaN(window.Width))
+                    {
+                        window.SizeToContent |= SizeToContent.Width;
+                    }
+
+                    if (double.IsNaN(window.Height))
+                    {
+                        window.SizeToContent |= SizeToContent.Height;
+                    }
+                }
             }
             window.Show();
             Design.ApplyDesignModeProperties(window, control);

+ 23 - 10
src/Avalonia.Layout/LayoutManager.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Diagnostics;
 using Avalonia.Logging;
 using Avalonia.Threading;
 
@@ -69,15 +70,23 @@ namespace Avalonia.Layout
             {
                 _running = true;
 
-                Logger.Information(
-                    LogArea.Layout,
-                    this,
-                    "Started layout pass. To measure: {Measure} To arrange: {Arrange}",
-                    _toMeasure.Count,
-                    _toArrange.Count);
+                Stopwatch stopwatch = null;
 
-                var stopwatch = new System.Diagnostics.Stopwatch();
-                stopwatch.Start();
+                const LogEventLevel timingLogLevel = LogEventLevel.Information;
+                bool captureTiming = Logger.IsEnabled(timingLogLevel);
+
+                if (captureTiming)
+                {
+                    Logger.TryGet(timingLogLevel)?.Log(
+                        LogArea.Layout,
+                        this,
+                        "Started layout pass. To measure: {Measure} To arrange: {Arrange}",
+                        _toMeasure.Count,
+                        _toArrange.Count);
+
+                    stopwatch = new Stopwatch();
+                    stopwatch.Start();
+                }
 
                 _toMeasure.BeginLoop(MaxPasses);
                 _toArrange.BeginLoop(MaxPasses);
@@ -103,8 +112,12 @@ namespace Avalonia.Layout
                 _toMeasure.EndLoop();
                 _toArrange.EndLoop();
 
-                stopwatch.Stop();
-                Logger.Information(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed);
+                if (captureTiming)
+                {
+                    stopwatch.Stop();
+
+                    Logger.TryGet(timingLogLevel)?.Log(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed);
+                }
             }
 
             _queued = false;

+ 4 - 4
src/Avalonia.Layout/Layoutable.cs

@@ -329,7 +329,7 @@ namespace Avalonia.Layout
                 DesiredSize = desiredSize;
                 _previousMeasure = availableSize;
 
-                Logger.Verbose(LogArea.Layout, this, "Measure requested {DesiredSize}", DesiredSize);
+                Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Measure requested {DesiredSize}", DesiredSize);
 
                 if (DesiredSize != previousDesiredSize)
                 {
@@ -356,7 +356,7 @@ namespace Avalonia.Layout
 
             if (!IsArrangeValid || _previousArrange != rect)
             {
-                Logger.Verbose(LogArea.Layout, this, "Arrange to {Rect} ", rect);
+                Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Arrange to {Rect} ", rect);
 
                 IsArrangeValid = true;
                 ArrangeCore(rect);
@@ -381,7 +381,7 @@ namespace Avalonia.Layout
         {
             if (IsMeasureValid)
             {
-                Logger.Verbose(LogArea.Layout, this, "Invalidated measure");
+                Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Invalidated measure");
 
                 IsMeasureValid = false;
                 IsArrangeValid = false;
@@ -402,7 +402,7 @@ namespace Avalonia.Layout
         {
             if (IsArrangeValid)
             {
-                Logger.Verbose(LogArea.Layout, this, "Invalidated arrange");
+                Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Invalidated arrange");
 
                 IsArrangeValid = false;
                 (VisualRoot as ILayoutRoot)?.LayoutManager?.InvalidateArrange(this);

+ 101 - 3
src/Avalonia.Logging.Serilog/SerilogLogger.cs

@@ -34,6 +34,76 @@ namespace Avalonia.Logging.Serilog
             Logger.Sink = new SerilogLogger(output);
         }
 
+        public bool IsEnabled(LogEventLevel level)
+        {
+            return _output.IsEnabled((SerilogLogEventLevel)level);
+        }
+
+        public void Log(
+            LogEventLevel level,
+            string area,
+            object source,
+            string messageTemplate)
+        {
+            Contract.Requires<ArgumentNullException>(area != null);
+            Contract.Requires<ArgumentNullException>(messageTemplate != null);
+
+            using (PushLogContextProperties(area, source))
+            {
+                _output.Write((SerilogLogEventLevel)level, messageTemplate);
+            }
+        }
+
+        public void Log<T0>(
+            LogEventLevel level, 
+            string area, object source,
+            string messageTemplate, 
+            T0 propertyValue0)
+        {
+            Contract.Requires<ArgumentNullException>(area != null);
+            Contract.Requires<ArgumentNullException>(messageTemplate != null);
+
+            using (PushLogContextProperties(area, source))
+            {
+                _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0);
+            }
+        }
+
+        public void Log<T0, T1>(
+            LogEventLevel level, 
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1)
+        {
+            Contract.Requires<ArgumentNullException>(area != null);
+            Contract.Requires<ArgumentNullException>(messageTemplate != null);
+
+            using (PushLogContextProperties(area, source))
+            {
+                _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0, propertyValue1);
+            }
+        }
+
+        public void Log<T0, T1, T2>(
+            LogEventLevel level, 
+            string area, 
+            object source, 
+            string messageTemplate, 
+            T0 propertyValue0,
+            T1 propertyValue1, 
+            T2 propertyValue2)
+        {
+            Contract.Requires<ArgumentNullException>(area != null);
+            Contract.Requires<ArgumentNullException>(messageTemplate != null);
+
+            using (PushLogContextProperties(area, source))
+            {
+                _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0, propertyValue1, propertyValue2);
+            }
+        }
+
         /// <inheritdoc/>
         public void Log(
             AvaloniaLogEventLevel level,
@@ -45,12 +115,40 @@ namespace Avalonia.Logging.Serilog
             Contract.Requires<ArgumentNullException>(area != null);
             Contract.Requires<ArgumentNullException>(messageTemplate != null);
 
-            using (LogContext.PushProperty("Area", area))
-            using (LogContext.PushProperty("SourceType", source?.GetType()))
-            using (LogContext.PushProperty("SourceHash", source?.GetHashCode()))
+            using (PushLogContextProperties(area, source))
             {
                 _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValues);
             }
         }
+
+        private static LogContextDisposable PushLogContextProperties(string area, object source)
+        {
+            return new LogContextDisposable(
+                LogContext.PushProperty("Area", area),
+                LogContext.PushProperty("SourceType", source?.GetType()),
+                LogContext.PushProperty("SourceHash", source?.GetHashCode())
+                );
+        }
+        
+        private readonly struct LogContextDisposable : IDisposable
+        {
+            private readonly IDisposable _areaDisposable;
+            private readonly IDisposable _sourceTypeDisposable;
+            private readonly IDisposable _sourceHashDisposable;
+
+            public LogContextDisposable(IDisposable areaDisposable, IDisposable sourceTypeDisposable, IDisposable sourceHashDisposable)
+            {
+                _areaDisposable = areaDisposable;
+                _sourceTypeDisposable = sourceTypeDisposable;
+                _sourceHashDisposable = sourceHashDisposable;
+            }
+
+            public void Dispose()
+            {
+                _areaDisposable.Dispose();
+                _sourceTypeDisposable.Dispose();
+                _sourceHashDisposable.Dispose();
+            }
+        }
     }
 }

+ 1 - 1
src/Avalonia.OpenGL/EglGlPlatformFeature.cs

@@ -31,7 +31,7 @@ namespace Avalonia.OpenGL
             }
             catch(Exception e)
             {
-                Logger.Error("OpenGL", null, "Unable to initialize EGL-based rendering: {0}", e);
+                Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", null, "Unable to initialize EGL-based rendering: {0}", e);
                 return null;
             }
         }

+ 2 - 2
src/Avalonia.Styling/StyledElement.cs

@@ -743,11 +743,11 @@ namespace Avalonia
 #if DEBUG
                 if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0)
                 {
-                    Logger.Warning(
+                    Logger.TryGet(LogEventLevel.Warning)?.Log(
                         LogArea.Control,
                         this,
                         "{Type} detached from logical tree but still has class listeners",
-                        this.GetType());
+                        GetType());
                 }
 #endif
             }

+ 51 - 39
src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs

@@ -18,8 +18,9 @@ namespace Avalonia.Styling
     internal class TypeNameAndClassSelector : Selector
     {
         private readonly Selector _previous;
+        private readonly Lazy<List<string>> _classes = new Lazy<List<string>>(() => new List<string>());
         private Type _targetType;
-        private Lazy<List<string>> _classes = new Lazy<List<string>>(() => new List<string>());
+        
         private string _selectorString;
 
         public static TypeNameAndClassSelector OfType(Selector previous, Type targetType)
@@ -27,6 +28,7 @@ namespace Avalonia.Styling
             var result = new TypeNameAndClassSelector(previous);
             result._targetType = targetType;
             result.IsConcreteType = true;
+
             return result;
         }
 
@@ -35,6 +37,7 @@ namespace Avalonia.Styling
             var result = new TypeNameAndClassSelector(previous);
             result._targetType = targetType;
             result.IsConcreteType = false;
+
             return result;
         }
 
@@ -42,6 +45,7 @@ namespace Avalonia.Styling
         {
             var result = new TypeNameAndClassSelector(previous);
             result.Name = name;
+
             return result;
         }
 
@@ -49,6 +53,7 @@ namespace Avalonia.Styling
         {
             var result = new TypeNameAndClassSelector(previous);
             result.Classes.Add(className);
+
             return result;
         }
 
@@ -126,9 +131,11 @@ namespace Avalonia.Styling
                 if (subscribe)
                 {
                     var observable = new ClassObserver(control.Classes, _classes.Value);
+
                     return new SelectorMatch(observable);
                 }
-                else if (!Matches(control.Classes))
+
+                if (!AreClassesMatching(control.Classes, Classes))
                 {
                     return SelectorMatch.NeverThisInstance;
                 }
@@ -139,21 +146,6 @@ namespace Avalonia.Styling
 
         protected override Selector MovePrevious() => _previous;
 
-        private bool Matches(IEnumerable<string> classes)
-        {
-            int remaining = Classes.Count;
-
-            foreach (var c in classes)
-            {
-                if (Classes.Contains(c))
-                {
-                    --remaining;
-                }
-            }
-
-            return remaining == 0;
-        }
-
         private string BuildSelectorString()
         {
             var builder = new StringBuilder();
@@ -199,11 +191,41 @@ namespace Avalonia.Styling
             return builder.ToString();
         }
 
-        private class ClassObserver : LightweightObservableBase<bool>
+        private static bool AreClassesMatching(IReadOnlyList<string> classes, IList<string> toMatch)
         {
-            readonly IList<string> _match;
-            IAvaloniaReadOnlyList<string> _classes;
-            bool _value;
+            int remainingMatches = toMatch.Count;
+            int classesCount = classes.Count;
+
+            // Early bail out - we can't match if control does not have enough classes.
+            if (classesCount < remainingMatches)
+            {
+                return false;
+            }
+
+            for (var i = 0; i < classesCount; i++)
+            {
+                var c = classes[i];
+
+                if (toMatch.Contains(c))
+                {
+                    --remainingMatches;
+
+                    // Already matched so we can skip checking other classes.
+                    if (remainingMatches == 0)
+                    {
+                        break;
+                    }
+                }
+            }
+
+            return remainingMatches == 0;
+        }
+
+        private sealed class ClassObserver : LightweightObservableBase<bool>
+        {
+            private readonly IList<string> _match;
+            private readonly IAvaloniaReadOnlyList<string> _classes;
+            private bool _hasMatch;
 
             public ClassObserver(IAvaloniaReadOnlyList<string> classes, IList<string> match)
             {
@@ -215,42 +237,32 @@ namespace Avalonia.Styling
 
             protected override void Initialize()
             {
-                _value = GetResult();
+                _hasMatch = IsMatching();
                 _classes.CollectionChanged += ClassesChanged;
             }
 
             protected override void Subscribed(IObserver<bool> observer, bool first)
             {
-                observer.OnNext(_value);
+                observer.OnNext(_hasMatch);
             }
 
             private void ClassesChanged(object sender, NotifyCollectionChangedEventArgs e)
             {
                 if (e.Action != NotifyCollectionChangedAction.Move)
                 {
-                    var value = GetResult();
+                    var hasMatch = IsMatching();
 
-                    if (value != _value)
+                    if (hasMatch != _hasMatch)
                     {
-                        PublishNext(GetResult());
-                        _value = value;
+                        PublishNext(hasMatch);
+                        _hasMatch = hasMatch;
                     }
                 }
             }
 
-            private bool GetResult()
+            private bool IsMatching()
             {
-                int remaining = _match.Count;
-
-                foreach (var c in _classes)
-                {
-                    if (_match.Contains(c))
-                    {
-                        --remaining;
-                    }
-                }
-
-                return remaining == 0;
+                return AreClassesMatching(_classes, _match);
             }
         }
     }

+ 2 - 2
src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs

@@ -65,14 +65,14 @@ namespace Avalonia.Animation.Animators
                     }
                 }
 
-                Logger.Warning(
+                Logger.TryGet(LogEventLevel.Warning)?.Log(
                     LogArea.Animations,
                     control,
                     $"Cannot find the appropriate transform: \"{Property.OwnerType}\" in {control}.");
             }
             else
             {
-                Logger.Error(
+                Logger.TryGet(LogEventLevel.Error)?.Log(
                     LogArea.Animations,
                     control,
                     $"Cannot apply animation: Target property owner {Property.OwnerType} is not a Transform object.");

+ 55 - 16
src/Avalonia.Visuals/CornerRadius.cs

@@ -8,7 +8,10 @@ using Avalonia.Utilities;
 
 namespace Avalonia
 {
-    public struct CornerRadius
+    /// <summary>
+    /// Represents the radii of a rectangle's corners.
+    /// </summary>
+    public readonly struct CornerRadius : IEquatable<CornerRadius>
     {
         static CornerRadius()
         {
@@ -33,22 +36,59 @@ namespace Avalonia
             BottomLeft = bottomLeft;
         }
 
+        /// <summary>
+        /// Radius of the top left corner.
+        /// </summary>
         public double TopLeft { get; }
+
+        /// <summary>
+        /// Radius of the top right corner.
+        /// </summary>
         public double TopRight { get; }
+
+        /// <summary>
+        /// Radius of the bottom right corner.
+        /// </summary>
         public double BottomRight { get; }
+
+        /// <summary>
+        /// Radius of the bottom left corner.
+        /// </summary>
         public double BottomLeft { get; }
+
+        /// <summary>
+        /// Gets a value indicating whether all corner radii are set to 0.
+        /// </summary>
         public bool IsEmpty => TopLeft.Equals(0) && IsUniform;
+
+        /// <summary>
+        /// Gets a value indicating whether all corner radii are equal.
+        /// </summary>
         public bool IsUniform => TopLeft.Equals(TopRight) && BottomLeft.Equals(BottomRight) && TopRight.Equals(BottomRight);
 
-        public override bool Equals(object obj)
+        /// <summary>
+        /// Returns a boolean indicating whether the corner radius is equal to the other given corner radius.
+        /// </summary>
+        /// <param name="other">The other corner radius to test equality against.</param>
+        /// <returns>True if this corner radius is equal to other; False otherwise.</returns>
+        public bool Equals(CornerRadius other)
         {
-            if (obj is CornerRadius)
-            {
-                return this == (CornerRadius)obj;
-            }
-            return false;
+            // ReSharper disable CompareOfFloatsByEqualityOperator
+            return TopLeft == other.TopLeft &&
+                   
+                   TopRight == other.TopRight &&
+                   BottomRight == other.BottomRight &&
+                   BottomLeft == other.BottomLeft;
+            // ReSharper restore CompareOfFloatsByEqualityOperator
         }
 
+        /// <summary>
+        /// Returns a boolean indicating whether the given Object is equal to this corner radius instance.
+        /// </summary>
+        /// <param name="obj">The Object to compare against.</param>
+        /// <returns>True if the Object is equal to this corner radius; False otherwise.</returns>
+        public override bool Equals(object obj) => obj is CornerRadius other && Equals(other);
+
         public override int GetHashCode()
         {
             return TopLeft.GetHashCode() ^ TopRight.GetHashCode() ^ BottomLeft.GetHashCode() ^ BottomRight.GetHashCode();
@@ -61,7 +101,9 @@ namespace Avalonia
 
         public static CornerRadius Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness"))
+            const string exceptionMessage = "Invalid CornerRadius.";
+
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage))
             {
                 if (tokenizer.TryReadDouble(out var a))
                 {
@@ -78,21 +120,18 @@ namespace Avalonia
                     return new CornerRadius(a);
                 }
 
-                throw new FormatException("Invalid CornerRadius.");
+                throw new FormatException(exceptionMessage);
             }
         }
 
-        public static bool operator ==(CornerRadius cr1, CornerRadius cr2)
+        public static bool operator ==(CornerRadius left, CornerRadius right)
         {
-            return cr1.TopLeft.Equals(cr2.TopLeft)
-                   && cr1.TopRight.Equals(cr2.TopRight)
-                   && cr1.BottomRight.Equals(cr2.BottomRight)
-                   && cr1.BottomLeft.Equals(cr2.BottomLeft);
+            return left.Equals(right);
         }
 
-        public static bool operator !=(CornerRadius cr1, CornerRadius cr2)
+        public static bool operator !=(CornerRadius left, CornerRadius right)
         {
-            return !(cr1 == cr2);
+            return !(left == right);
         }
     }
 }

+ 5 - 11
src/Avalonia.Visuals/Matrix.cs

@@ -10,7 +10,7 @@ namespace Avalonia
     /// <summary>
     /// A 2x3 matrix.
     /// </summary>
-    public readonly struct Matrix
+    public readonly struct Matrix : IEquatable<Matrix>
     {
         private readonly double _m11;
         private readonly double _m12;
@@ -235,12 +235,14 @@ namespace Avalonia
         /// <returns>True if this matrix is equal to other; False otherwise.</returns>
         public bool Equals(Matrix other)
         {
+            // ReSharper disable CompareOfFloatsByEqualityOperator
             return _m11 == other.M11 &&
                    _m12 == other.M12 &&
                    _m21 == other.M21 &&
                    _m22 == other.M22 &&
                    _m31 == other.M31 &&
                    _m32 == other.M32;
+            // ReSharper restore CompareOfFloatsByEqualityOperator
         }
 
         /// <summary>
@@ -248,15 +250,7 @@ namespace Avalonia
         /// </summary>
         /// <param name="obj">The Object to compare against.</param>
         /// <returns>True if the Object is equal to this matrix; False otherwise.</returns>
-        public override bool Equals(object obj)
-        {
-            if (!(obj is Matrix))
-            {
-                return false;
-            }
-
-            return Equals((Matrix)obj);
-        }
+        public override bool Equals(object obj) => obj is Matrix other && Equals(other);
 
         /// <summary>
         /// Returns the hash code for this instance.
@@ -316,7 +310,7 @@ namespace Avalonia
         /// <returns>The <see cref="Matrix"/>.</returns>
         public static Matrix Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix."))
             {
                 return new Matrix(
                     tokenizer.ReadDouble(),

+ 16 - 12
src/Avalonia.Visuals/Media/PixelPoint.cs

@@ -10,7 +10,7 @@ namespace Avalonia
     /// <summary>
     /// Represents a point in device pixels.
     /// </summary>
-    public readonly struct PixelPoint
+    public readonly struct PixelPoint : IEquatable<PixelPoint>
     {
         /// <summary>
         /// A point representing 0,0.
@@ -46,7 +46,7 @@ namespace Avalonia
         /// <returns>True if the points are equal; otherwise false.</returns>
         public static bool operator ==(PixelPoint left, PixelPoint right)
         {
-            return left.X == right.X && left.Y == right.Y;
+            return left.Equals(right);
         }
 
         /// <summary>
@@ -120,7 +120,7 @@ namespace Avalonia
         /// <returns>The <see cref="PixelPoint"/>.</returns>
         public static PixelPoint Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint."))
             {
                 return new PixelPoint(
                     tokenizer.ReadInt32(),
@@ -128,6 +128,18 @@ namespace Avalonia
             }
         }
 
+        /// <summary>
+        /// Returns a boolean indicating whether the point is equal to the other given point.
+        /// </summary>
+        /// <param name="other">The other point to test equality against.</param>
+        /// <returns>True if this point is equal to other; False otherwise.</returns>
+        public bool Equals(PixelPoint other)
+        {
+            // ReSharper disable CompareOfFloatsByEqualityOperator
+            return X == other.X && Y == other.Y;
+            // ReSharper restore CompareOfFloatsByEqualityOperator
+        }
+
         /// <summary>
         /// Checks for equality between a point and an object.
         /// </summary>
@@ -135,15 +147,7 @@ namespace Avalonia
         /// <returns>
         /// True if <paramref name="obj"/> is a point that equals the current point.
         /// </returns>
-        public override bool Equals(object obj)
-        {
-            if (obj is PixelPoint other)
-            {
-                return this == other;
-            }
-
-            return false;
-        }
+        public override bool Equals(object obj) => obj is PixelPoint other && Equals(other);
 
         /// <summary>
         /// Returns a hash code for a <see cref="PixelPoint"/>.

+ 14 - 12
src/Avalonia.Visuals/Media/PixelRect.cs

@@ -10,7 +10,7 @@ namespace Avalonia
     /// <summary>
     /// Represents a rectangle in device pixels.
     /// </summary>
-    public readonly struct PixelRect
+    public readonly struct PixelRect : IEquatable<PixelRect>
     {
         /// <summary>
         /// An empty rectangle.
@@ -148,7 +148,7 @@ namespace Avalonia
         /// <returns>True if the rects are equal; otherwise false.</returns>
         public static bool operator ==(PixelRect left, PixelRect right)
         {
-            return left.Position == right.Position && left.Size == right.Size;
+            return left.Equals(right);
         }
 
         /// <summary>
@@ -196,20 +196,22 @@ namespace Avalonia
                 rect.Height);
         }
 
+        /// <summary>
+        /// Returns a boolean indicating whether the rect is equal to the other given rect.
+        /// </summary>
+        /// <param name="other">The other rect to test equality against.</param>
+        /// <returns>True if this rect is equal to other; False otherwise.</returns>
+        public bool Equals(PixelRect other)
+        {
+            return Position == other.Position && Size == other.Size;
+        }
+
         /// <summary>
         /// Returns a boolean indicating whether the given object is equal to this rectangle.
         /// </summary>
         /// <param name="obj">The object to compare against.</param>
         /// <returns>True if the object is equal to this rectangle; false otherwise.</returns>
-        public override bool Equals(object obj)
-        {
-            if (obj is PixelRect other)
-            {
-                return this == other;
-            }
-
-            return false;
-        }
+        public override bool Equals(object obj) => obj is PixelRect other && Equals(other);
 
         /// <summary>
         /// Returns the hash code for this instance.
@@ -432,7 +434,7 @@ namespace Avalonia
         /// <returns>The parsed <see cref="PixelRect"/>.</returns>
         public static PixelRect Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect."))
             {
                 return new PixelRect(
                     tokenizer.ReadInt32(),

+ 14 - 12
src/Avalonia.Visuals/Media/PixelSize.cs

@@ -10,7 +10,7 @@ namespace Avalonia
     /// <summary>
     /// Represents a size in device pixels.
     /// </summary>
-    public readonly struct PixelSize
+    public readonly struct PixelSize : IEquatable<PixelSize>
     {
         /// <summary>
         /// A size representing zero
@@ -51,7 +51,7 @@ namespace Avalonia
         /// <returns>True if the sizes are equal; otherwise false.</returns>
         public static bool operator ==(PixelSize left, PixelSize right)
         {
-            return left.Width == right.Width && left.Height == right.Height;
+            return left.Equals(right);
         }
 
         /// <summary>
@@ -72,7 +72,7 @@ namespace Avalonia
         /// <returns>The <see cref="PixelSize"/>.</returns>
         public static PixelSize Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelSize"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelSize."))
             {
                 return new PixelSize(
                     tokenizer.ReadInt32(),
@@ -80,6 +80,16 @@ namespace Avalonia
             }
         }
 
+        /// <summary>
+        /// Returns a boolean indicating whether the size is equal to the other given size.
+        /// </summary>
+        /// <param name="other">The other size to test equality against.</param>
+        /// <returns>True if this size is equal to other; False otherwise.</returns>
+        public bool Equals(PixelSize other)
+        {
+            return Width == other.Width && Height == other.Height;
+        }
+
         /// <summary>
         /// Checks for equality between a size and an object.
         /// </summary>
@@ -87,15 +97,7 @@ namespace Avalonia
         /// <returns>
         /// True if <paramref name="obj"/> is a size that equals the current size.
         /// </returns>
-        public override bool Equals(object obj)
-        {
-            if (obj is PixelSize other)
-            {
-                return this == other;
-            }
-
-            return false;
-        }
+        public override bool Equals(object obj) => obj is PixelSize other && Equals(other);
 
         /// <summary>
         /// Returns a hash code for a <see cref="PixelSize"/>.

+ 18 - 13
src/Avalonia.Visuals/Point.cs

@@ -1,6 +1,7 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
 using System.Globalization;
 using Avalonia.Animation.Animators;
 using Avalonia.Utilities;
@@ -10,7 +11,7 @@ namespace Avalonia
     /// <summary>
     /// Defines a point.
     /// </summary>
-    public readonly struct Point
+    public readonly struct Point : IEquatable<Point>
     {
         static Point()
         {
@@ -75,7 +76,7 @@ namespace Avalonia
         /// <returns>True if the points are equal; otherwise false.</returns>
         public static bool operator ==(Point left, Point right)
         {
-            return left.X == right.X && left.Y == right.Y;
+            return left.Equals(right);
         }
 
         /// <summary>
@@ -177,7 +178,7 @@ namespace Avalonia
         /// <returns>The <see cref="Thickness"/>.</returns>
         public static Point Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Point"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Point."))
             {
                 return new Point(
                     tokenizer.ReadDouble(),
@@ -186,6 +187,19 @@ namespace Avalonia
             }
         }
 
+        /// <summary>
+        /// Returns a boolean indicating whether the point is equal to the other given point.
+        /// </summary>
+        /// <param name="other">The other point to test equality against.</param>
+        /// <returns>True if this point is equal to other; False otherwise.</returns>
+        public bool Equals(Point other)
+        {
+            // ReSharper disable CompareOfFloatsByEqualityOperator
+            return _x == other._x &&
+                   _y == other._y;
+            // ReSharper enable CompareOfFloatsByEqualityOperator
+        }
+
         /// <summary>
         /// Checks for equality between a point and an object.
         /// </summary>
@@ -193,16 +207,7 @@ namespace Avalonia
         /// <returns>
         /// True if <paramref name="obj"/> is a point that equals the current point.
         /// </returns>
-        public override bool Equals(object obj)
-        {
-            if (obj is Point)
-            {
-                var other = (Point)obj;
-                return X == other.X && Y == other.Y;
-            }
-
-            return false;
-        }
+        public override bool Equals(object obj) => obj is Point other && Equals(other);
 
         /// <summary>
         /// Returns a hash code for a <see cref="Point"/>.

+ 25 - 17
src/Avalonia.Visuals/Rect.cs

@@ -11,7 +11,7 @@ namespace Avalonia
     /// <summary>
     /// Defines a rectangle.
     /// </summary>
-    public readonly struct Rect
+    public readonly struct Rect : IEquatable<Rect>
     {
         static Rect()
         {
@@ -164,7 +164,9 @@ namespace Avalonia
         /// <summary>
         /// Gets a value that indicates whether the rectangle is empty.
         /// </summary>
+        // ReSharper disable CompareOfFloatsByEqualityOperator
         public bool IsEmpty => _width == 0 && _height == 0;
+        // ReSharper restore CompareOfFloatsByEqualityOperator
 
         /// <summary>
         /// Checks for equality between two <see cref="Rect"/>s.
@@ -174,7 +176,7 @@ namespace Avalonia
         /// <returns>True if the rects are equal; otherwise false.</returns>
         public static bool operator ==(Rect left, Rect right)
         {
-            return left.Position == right.Position && left.Size == right.Size;
+            return left.Equals(right);
         }
 
         /// <summary>
@@ -297,21 +299,27 @@ namespace Avalonia
                 Size.Deflate(thickness));
         }
 
+        /// <summary>
+        /// Returns a boolean indicating whether the rect is equal to the other given rect.
+        /// </summary>
+        /// <param name="other">The other rect to test equality against.</param>
+        /// <returns>True if this rect is equal to other; False otherwise.</returns>
+        public bool Equals(Rect other)
+        {
+            // ReSharper disable CompareOfFloatsByEqualityOperator
+            return _x == other._x &&
+                   _y == other._y &&
+                   _width == other._width &&
+                   _height == other._height;
+            // ReSharper enable CompareOfFloatsByEqualityOperator
+        }
+
         /// <summary>
         /// Returns a boolean indicating whether the given object is equal to this rectangle.
         /// </summary>
         /// <param name="obj">The object to compare against.</param>
         /// <returns>True if the object is equal to this rectangle; false otherwise.</returns>
-        public override bool Equals(object obj)
-        {
-            if (obj is Rect)
-            {
-                var other = (Rect)obj;
-                return Position == other.Position && Size == other.Size;
-            }
-
-            return false;
-        }
+        public override bool Equals(object obj) => obj is Rect other && Equals(other);
 
         /// <summary>
         /// Returns the hash code for this instance.
@@ -422,10 +430,10 @@ namespace Avalonia
             }
             else
             {
-                var x1 = Math.Min(this.X, rect.X);
-                var x2 = Math.Max(this.Right, rect.Right);
-                var y1 = Math.Min(this.Y, rect.Y);
-                var y2 = Math.Max(this.Bottom, rect.Bottom);
+                var x1 = Math.Min(X, rect.X);
+                var x2 = Math.Max(Right, rect.Right);
+                var y1 = Math.Min(Y, rect.Y);
+                var y2 = Math.Max(Bottom, rect.Bottom);
 
                 return new Rect(new Point(x1, y1), new Point(x2, y2));
             }
@@ -493,7 +501,7 @@ namespace Avalonia
         /// <returns>The parsed <see cref="Rect"/>.</returns>
         public static Rect Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Rect"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Rect."))
             {
                 return new Rect(
                     tokenizer.ReadDouble(),

+ 3 - 9
src/Avalonia.Visuals/RelativePoint.cs

@@ -107,10 +107,7 @@ namespace Avalonia
         /// </summary>
         /// <param name="obj">The other object.</param>
         /// <returns>True if the objects are equal, otherwise false.</returns>
-        public override bool Equals(object obj)
-        {
-            return (obj is RelativePoint) && Equals((RelativePoint)obj);
-        }
+        public override bool Equals(object obj) => obj is RelativePoint other && Equals(other);
 
         /// <summary>
         /// Checks if the <see cref="RelativePoint"/> equals another point.
@@ -130,10 +127,7 @@ namespace Avalonia
         {
             unchecked
             {
-                int hash = 17;
-                hash = (hash * 23) + Unit.GetHashCode();
-                hash = (hash * 23) + Point.GetHashCode();
-                return hash;
+                return (_point.GetHashCode() * 397) ^ (int)_unit;
             }
         }
 
@@ -156,7 +150,7 @@ namespace Avalonia
         /// <returns>The parsed <see cref="RelativePoint"/>.</returns>
         public static RelativePoint Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid RelativePoint"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid RelativePoint."))
             {
                 var x = tokenizer.ReadString();
                 var y = tokenizer.ReadString();

+ 4 - 10
src/Avalonia.Visuals/RelativeRect.cs

@@ -116,10 +116,7 @@ namespace Avalonia
         /// </summary>
         /// <param name="obj">The other object.</param>
         /// <returns>True if the objects are equal, otherwise false.</returns>
-        public override bool Equals(object obj)
-        {
-            return (obj is RelativeRect) && Equals((RelativeRect)obj);
-        }
+        public override bool Equals(object obj) => obj is RelativeRect other && Equals(other);
 
         /// <summary>
         /// Checks if the <see cref="RelativeRect"/> equals another rectangle.
@@ -139,10 +136,7 @@ namespace Avalonia
         {
             unchecked
             {
-                int hash = 17;
-                hash = (hash * 23) + Unit.GetHashCode();
-                hash = (hash * 23) + Rect.GetHashCode();
-                return hash;
+                return ((int)Unit * 397) ^ Rect.GetHashCode();
             }
         }
 
@@ -161,7 +155,7 @@ namespace Avalonia
                     Rect.Width * size.Width,
                     Rect.Height * size.Height);
         }
-        
+
         /// <summary>
         /// Parses a <see cref="RelativeRect"/> string.
         /// </summary>
@@ -169,7 +163,7 @@ namespace Avalonia
         /// <returns>The parsed <see cref="RelativeRect"/>.</returns>
         public static RelativeRect Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect"))
+            using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect."))
             {
                 var x = tokenizer.ReadString();
                 var y = tokenizer.ReadString();

+ 2 - 1
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using Avalonia.Logging;
 using Avalonia.Media;
 using Avalonia.Media.Immutable;
 using Avalonia.Platform;
@@ -269,7 +270,7 @@ namespace Avalonia.Rendering
                 }
                 catch (RenderTargetCorruptedException ex)
                 {
-                    Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
+                    Logger.TryGet(LogEventLevel.Information)?.Log("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
                     RenderTarget?.Dispose();
                     RenderTarget = null;
                 }

+ 2 - 1
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Avalonia.Logging;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.VisualTree;
@@ -80,7 +81,7 @@ namespace Avalonia.Rendering
             }
             catch (RenderTargetCorruptedException ex)
             {
-                Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
+                Logger.TryGet(LogEventLevel.Information)?.Log("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
                 _renderTarget.Dispose();
                 _renderTarget = null;
             }

+ 2 - 2
src/Avalonia.Visuals/Rendering/RenderLoop.cs

@@ -120,7 +120,7 @@ namespace Avalonia.Rendering
                                     }
                                     catch (Exception ex)
                                     {
-                                        Logger.Error(LogArea.Visual, this, "Exception in render update: {Error}", ex);
+                                        Logger.TryGet(LogEventLevel.Error)?.Log(LogArea.Visual, this, "Exception in render update: {Error}", ex);
                                     }
                                 }
                             }
@@ -136,7 +136,7 @@ namespace Avalonia.Rendering
                 }
                 catch (Exception ex)
                 {
-                    Logger.Error(LogArea.Visual, this, "Exception in render loop: {Error}", ex);
+                    Logger.TryGet(LogEventLevel.Error)?.Log(LogArea.Visual, this, "Exception in render loop: {Error}", ex);
                 }
                 finally
                 {

+ 17 - 13
src/Avalonia.Visuals/Size.cs

@@ -11,7 +11,7 @@ namespace Avalonia
     /// <summary>
     /// Defines a size.
     /// </summary>
-    public readonly struct Size
+    public readonly struct Size : IEquatable<Size>
     {
         static Size()
         {
@@ -72,7 +72,7 @@ namespace Avalonia
         /// <returns>True if the sizes are equal; otherwise false.</returns>
         public static bool operator ==(Size left, Size right)
         {
-            return left._width == right._width && left._height == right._height;
+            return left.Equals(right);
         }
 
         /// <summary>
@@ -158,7 +158,7 @@ namespace Avalonia
         /// <returns>The <see cref="Size"/>.</returns>
         public static Size Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size."))
             {
                 return new Size(
                     tokenizer.ReadDouble(),
@@ -191,6 +191,19 @@ namespace Avalonia
                 Math.Max(0, _height - thickness.Top - thickness.Bottom));
         }
 
+        /// <summary>
+        /// Returns a boolean indicating whether the size is equal to the other given size.
+        /// </summary>
+        /// <param name="other">The other size to test equality against.</param>
+        /// <returns>True if this size is equal to other; False otherwise.</returns>
+        public bool Equals(Size other)
+        {
+            // ReSharper disable CompareOfFloatsByEqualityOperator
+            return _width == other._width &&
+                   _height == other._height;
+            // ReSharper enable CompareOfFloatsByEqualityOperator
+        }
+
         /// <summary>
         /// Checks for equality between a size and an object.
         /// </summary>
@@ -198,16 +211,7 @@ namespace Avalonia
         /// <returns>
         /// True if <paramref name="obj"/> is a size that equals the current size.
         /// </returns>
-        public override bool Equals(object obj)
-        {
-            if (obj is Size)
-            {
-                var other = (Size)obj;
-                return Width == other.Width && Height == other.Height;
-            }
-
-            return false;
-        }
+        public override bool Equals(object obj) => obj is Size other && Equals(other);
 
         /// <summary>
         /// Returns a hash code for a <see cref="Size"/>.

+ 21 - 17
src/Avalonia.Visuals/Thickness.cs

@@ -3,7 +3,6 @@
 
 using System;
 using System.Globalization;
-using Avalonia.Animation;
 using Avalonia.Animation.Animators;
 using Avalonia.Utilities;
 
@@ -12,7 +11,7 @@ namespace Avalonia
     /// <summary>
     /// Describes the thickness of a frame around a rectangle.
     /// </summary>
-    public readonly struct Thickness
+    public readonly struct Thickness : IEquatable<Thickness>
     {
         static Thickness()
         {
@@ -204,7 +203,9 @@ namespace Avalonia
         /// <returns>The <see cref="Thickness"/>.</returns>
         public static Thickness Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness"))
+            const string exceptionMessage = "Invalid Thickness.";
+
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage))
             {
                 if (tokenizer.TryReadDouble(out var a))
                 {
@@ -221,10 +222,25 @@ namespace Avalonia
                     return new Thickness(a);
                 }
 
-                throw new FormatException("Invalid Thickness.");
+                throw new FormatException(exceptionMessage);
             }
         }
 
+        /// <summary>
+        /// Returns a boolean indicating whether the thickness is equal to the other given point.
+        /// </summary>
+        /// <param name="other">The other thickness to test equality against.</param>
+        /// <returns>True if this thickness is equal to other; False otherwise.</returns>
+        public bool Equals(Thickness other)
+        {
+            // ReSharper disable CompareOfFloatsByEqualityOperator
+            return _left == other._left &&
+                   _top == other._top &&
+                   _right == other._right &&
+                   _bottom == other._bottom;
+            // ReSharper restore CompareOfFloatsByEqualityOperator
+        }
+
         /// <summary>
         /// Checks for equality between a thickness and an object.
         /// </summary>
@@ -232,19 +248,7 @@ namespace Avalonia
         /// <returns>
         /// True if <paramref name="obj"/> is a size that equals the current size.
         /// </returns>
-        public override bool Equals(object obj)
-        {
-            if (obj is Thickness)
-            {
-                Thickness other = (Thickness)obj;
-                return Left == other.Left &&
-                       Top == other.Top &&
-                       Right == other.Right &&
-                       Bottom == other.Bottom;
-            }
-
-            return false;
-        }
+        public override bool Equals(object obj) => obj is Thickness other && Equals(other);
 
         /// <summary>
         /// Returns a hash code for a <see cref="Thickness"/>.

+ 2 - 9
src/Avalonia.Visuals/Vector.cs

@@ -11,7 +11,7 @@ namespace Avalonia
     /// <summary>
     /// Defines a vector.
     /// </summary>
-    public readonly struct Vector
+    public readonly struct Vector : IEquatable<Vector>
     {
         static Vector()
         {
@@ -138,7 +138,6 @@ namespace Avalonia
         /// </summary>
         /// <param name="other">The other vector.</param>
         /// <returns>True if vectors are nearly equal.</returns>
-        [Pure]
         public bool NearlyEquals(Vector other)
         {
             const float tolerance = float.Epsilon;
@@ -146,13 +145,7 @@ namespace Avalonia
             return Math.Abs(_x - other._x) < tolerance && Math.Abs(_y - other._y) < tolerance;
         }
 
-        public override bool Equals(object obj)
-        {
-            if (ReferenceEquals(null, obj))
-                return false;
-
-            return obj is Vector vector && Equals(vector);
-        }
+        public override bool Equals(object obj) => obj is Vector other && Equals(other);
 
         public override int GetHashCode()
         {

+ 3 - 4
src/Avalonia.Visuals/Visual.cs

@@ -359,7 +359,7 @@ namespace Avalonia
         /// <param name="e">The event args.</param>
         protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
         {
-            Logger.Verbose(LogArea.Visual, this, "Attached to visual tree");
+            Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Visual, this, "Attached to visual tree");
 
             _visualRoot = e.Root;
 
@@ -388,7 +388,7 @@ namespace Avalonia
         /// <param name="e">The event args.</param>
         protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
         {
-            Logger.Verbose(LogArea.Visual, this, "Detached from visual tree");
+            Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Visual, this, "Detached from visual tree");
 
             _visualRoot = null;
 
@@ -453,8 +453,7 @@ namespace Avalonia
                     return;
                 }
 
-                Logger.Log(
-                    LogEventLevel.Warning,
+                Logger.TryGet(LogEventLevel.Warning)?.Log(
                     LogArea.Binding,
                     this,
                     "Error in binding to {Target}.{Property}: {Message}",

+ 3 - 10
src/Avalonia.Visuals/VisualTree/TransformedBounds.cs

@@ -1,13 +1,14 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
 
 namespace Avalonia.VisualTree
 {
     /// <summary>
     /// Holds information about the bounds of a control, together with a transform and a clip.
     /// </summary>
-    public readonly struct TransformedBounds
+    public readonly struct TransformedBounds : IEquatable<TransformedBounds>
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="TransformedBounds"/> struct.
@@ -56,15 +57,7 @@ namespace Avalonia.VisualTree
             return Bounds == other.Bounds && Clip == other.Clip && Transform == other.Transform;
         }
 
-        public override bool Equals(object obj)
-        {
-            if (obj is null)
-            {
-                return false;
-            }
-
-            return obj is TransformedBounds other && Equals(other);
-        }
+        public override bool Equals(object obj) => obj is TransformedBounds other && Equals(other);
 
         public override int GetHashCode()
         {

+ 1 - 1
src/Avalonia.X11/Glx/GlxPlatformFeature.cs

@@ -36,7 +36,7 @@ namespace Avalonia.X11.Glx
             }
             catch(Exception e)
             {
-                Logger.Error("OpenGL", null, "Unable to initialize GLX-based rendering: {0}", e);
+                Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", null, "Unable to initialize GLX-based rendering: {0}", e);
                 return null;
             }
         }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs

@@ -42,7 +42,7 @@ namespace Avalonia.Markup.Xaml.Converters
                 !property.IsAttached &&
                 !registry.IsRegistered(targetType, property))
             {
-                Logger.Warning(
+                Logger.TryGet(LogEventLevel.Warning)?.Log(
                     LogArea.Property,
                     this,
                     "Property '{Owner}.{Name}' is not registered on '{Type}'.",

+ 1 - 1
src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs

@@ -150,7 +150,7 @@ namespace Avalonia.Markup.Data
                 }
                 catch (Exception e)
                 {
-                    Logger.Error(
+                    Logger.TryGet(LogEventLevel.Error)?.Log(
                         LogArea.Property,
                         control,
                         "Error setting {Property} on {Target}: {Exception}",

+ 1 - 1
src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs

@@ -85,7 +85,7 @@ namespace Avalonia.Direct2D1.Media
             }
             catch (Exception ex)
             {
-                Logger.Error(
+                Logger.TryGet(LogEventLevel.Error)?.Log(
                     LogArea.Visual,
                     this,
                     "GeometrySink.Close exception: {Exception}",

+ 30 - 2
tests/Avalonia.UnitTests/TestLogSink.cs

@@ -16,7 +16,7 @@ namespace Avalonia.UnitTests
 
     public class TestLogSink : ILogSink
     {
-        private LogCallback _callback;
+        private readonly LogCallback _callback;
 
         public TestLogSink(LogCallback callback)
         {
@@ -30,7 +30,35 @@ namespace Avalonia.UnitTests
             return Disposable.Create(() => Logger.Sink = null);
         }
 
-        public void Log(LogEventLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
+        public bool IsEnabled(LogEventLevel level)
+        {
+            return true;
+        }
+
+        public void Log(LogEventLevel level, string area, object source, string messageTemplate)
+        {
+            _callback(level, area, source, messageTemplate);
+        }
+
+        public void Log<T0>(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0)
+        {
+            _callback(level, area, source, messageTemplate, propertyValue0);
+        }
+
+        public void Log<T0, T1>(LogEventLevel level, string area, object source, string messageTemplate,
+            T0 propertyValue0, T1 propertyValue1)
+        {
+            _callback(level, area, source, messageTemplate, propertyValue0, propertyValue1);
+        }
+
+        public void Log<T0, T1, T2>(LogEventLevel level, string area, object source, string messageTemplate,
+            T0 propertyValue0, T1 propertyValue1, T2 propertyValue2)
+        {
+            _callback(level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2);
+        }
+
+        public void Log(LogEventLevel level, string area, object source, string messageTemplate,
+            params object[] propertyValues)
         {
             _callback(level, area, source, messageTemplate, propertyValues);
         }