소스 검색

Merge branch 'master' into fixes/2862-grid-star-design-time

Jumar Macato 6 년 전
부모
커밋
82b616fd1e
27개의 변경된 파일503개의 추가작업 그리고 162개의 파일을 삭제
  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. 3 2
      src/Avalonia.Controls/TreeView.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. 2 2
      src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
  18. 2 1
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  19. 2 1
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  20. 2 2
      src/Avalonia.Visuals/Rendering/RenderLoop.cs
  21. 3 4
      src/Avalonia.Visuals/Visual.cs
  22. 1 1
      src/Avalonia.X11/Glx/GlxPlatformFeature.cs
  23. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  24. 1 1
      src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs
  25. 1 1
      src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs
  26. 41 0
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  27. 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?",

+ 3 - 2
src/Avalonia.Controls/TreeView.cs

@@ -1,3 +1,4 @@
+
 // 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.
 
@@ -470,7 +471,7 @@ namespace Avalonia.Controls
                     if (index > 0)
                     {
                         var previous = (TreeViewItem)parentGenerator.ContainerFromIndex(index - 1);
-                        result = previous.IsExpanded ?
+                        result = previous.IsExpanded && previous.ItemCount > 0 ?
                             (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1) :
                             previous;
                     }
@@ -482,7 +483,7 @@ namespace Avalonia.Controls
                     break;
 
                 case NavigationDirection.Down:
-                    if (from.IsExpanded && intoChildren)
+                    if (from.IsExpanded && intoChildren && from.ItemCount > 0)
                     {
                         result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0);
                     }

+ 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
             }

+ 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.");

+ 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
                 {

+ 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}",

+ 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}",

+ 41 - 0
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@@ -14,6 +14,7 @@ using Avalonia.Input;
 using Avalonia.Input.Platform;
 using Avalonia.Interactivity;
 using Avalonia.LogicalTree;
+using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Xunit;
 
@@ -892,6 +893,46 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(2, GetItem(target, 0, 1, 0).Level);
         }
 
+        [Fact]
+        public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection()
+        {
+            /// Issue #2980.
+            using (UnitTestApplication.Start(TestServices.RealStyler))
+            {
+                var target = new DerivedTreeView
+                {
+                    Template = CreateTreeViewTemplate(),
+                    SelectionMode = SelectionMode.Multiple,
+                    Items = new List<Node>
+                {
+                    new Node { Value = "Root1", },
+                    new Node { Value = "Root2", },
+                },
+                };
+
+                var visualRoot = new TestRoot
+                {
+                    Styles =
+                    {
+                        new Style(x => x.OfType<TreeViewItem>())
+                        {
+                            Setters =
+                            {
+                                new Setter(TreeViewItem.IsExpandedProperty, true),
+                            },
+                        },
+                    },
+                    Child = target,
+                };
+
+                CreateNodeDataTemplate(target);
+                ApplyTemplates(target);
+
+                _mouse.Click(GetItem(target, 0));
+                _mouse.Click(GetItem(target, 1), modifiers: InputModifiers.Shift);
+            }
+        }
+
         private void ApplyTemplates(TreeView tree)
         {
             tree.ApplyTemplate();

+ 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);
         }