diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 8cc512d132..2450f1a3a1 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/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 /// The binding error. 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 /// The priority. 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}", diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index 7f8396cdfa..986e2cf012 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/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}", diff --git a/src/Avalonia.Base/Logging/ILogSink.cs b/src/Avalonia.Base/Logging/ILogSink.cs index 0ed4eede8f..8b5751b0af 100644 --- a/src/Avalonia.Base/Logging/ILogSink.cs +++ b/src/Avalonia.Base/Logging/ILogSink.cs @@ -8,6 +8,77 @@ namespace Avalonia.Logging /// public interface ILogSink { + /// + /// Checks if given log level is enabled. + /// + /// The log event level. + /// if given log level is enabled. + bool IsEnabled(LogEventLevel level); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2); + /// /// Logs a new event. /// diff --git a/src/Avalonia.Base/Logging/Logger.cs b/src/Avalonia.Base/Logging/Logger.cs index b1132ff4a9..c895c70094 100644 --- a/src/Avalonia.Base/Logging/Logger.cs +++ b/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 { /// @@ -16,124 +14,43 @@ namespace Avalonia.Logging public static ILogSink Sink { get; set; } /// - /// Logs an event. + /// Checks if given log level is enabled. /// /// The log event level. - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [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); - } - - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Verbose( - string area, - object source, - string messageTemplate, - params object[] propertyValues) + /// if given log level is enabled. + public static bool IsEnabled(LogEventLevel level) { - Log(LogEventLevel.Verbose, area, source, messageTemplate, propertyValues); + return Sink?.IsEnabled(level) == true; } /// - /// Logs an event with the level. + /// Returns parametrized logging sink if given log level is enabled. /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Debug( - string area, - object source, - string messageTemplate, - params object[] propertyValues) + /// The log event level. + /// Log sink or if log level is not enabled. + public static ParametrizedLogger? TryGet(LogEventLevel level) { - Log(LogEventLevel.Debug, area, source, messageTemplate, propertyValues); - } + if (!IsEnabled(level)) + { + return null; + } - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [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); } /// - /// Logs an event with the level. + /// Returns parametrized logging sink if given log level is enabled. /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Warning( - string area, - object source, - string messageTemplate, - params object[] propertyValues) + /// The log event level. + /// Log sink that is valid only if method returns . + /// if logger was obtained successfully. + public static bool TryGet(LogEventLevel level, out ParametrizedLogger outLogger) { - Log(LogEventLevel.Warning, area, source, messageTemplate, propertyValues); - } + ParametrizedLogger? logger = TryGet(level); - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [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(); - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [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; } } } diff --git a/src/Avalonia.Base/Logging/ParametrizedLogger.cs b/src/Avalonia.Base/Logging/ParametrizedLogger.cs new file mode 100644 index 0000000000..1550cc1b40 --- /dev/null +++ b/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 +{ + /// + /// Logger sink parametrized for given logging level. + /// + public readonly struct ParametrizedLogger + { + private readonly ILogSink _sink; + private readonly LogEventLevel _level; + + public ParametrizedLogger(ILogSink sink, LogEventLevel level) + { + _sink = sink; + _level = level; + } + + /// + /// Checks if this logger can be used. + /// + public bool IsValid => _sink != null; + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate) + { + _sink.Log(_level, area, source, messageTemplate); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2, + T3 propertyValue3) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + 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); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + 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); + } + } +} diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 2871271062..61184ef7b1 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/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})", diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index b67d9ef89a..9da803d16d 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/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); diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index b752b3f7a8..d2d82f5ca1 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/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}", diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index ba4c6830d1..0ace387185 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/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); diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index e0acab1133..c54ebd5360 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/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?", diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index a7d4b96974..f3bb0edce5 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/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); diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 45efccc1fa..855f123748 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/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; diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index bd248d6d44..b0757a823d 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/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); diff --git a/src/Avalonia.Logging.Serilog/SerilogLogger.cs b/src/Avalonia.Logging.Serilog/SerilogLogger.cs index 0534fe3012..895ee268d2 100644 --- a/src/Avalonia.Logging.Serilog/SerilogLogger.cs +++ b/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(area != null); + Contract.Requires(messageTemplate != null); + + using (PushLogContextProperties(area, source)) + { + _output.Write((SerilogLogEventLevel)level, messageTemplate); + } + } + + public void Log( + LogEventLevel level, + string area, object source, + string messageTemplate, + T0 propertyValue0) + { + Contract.Requires(area != null); + Contract.Requires(messageTemplate != null); + + using (PushLogContextProperties(area, source)) + { + _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0); + } + } + + public void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1) + { + Contract.Requires(area != null); + Contract.Requires(messageTemplate != null); + + using (PushLogContextProperties(area, source)) + { + _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0, propertyValue1); + } + } + + public void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2) + { + Contract.Requires(area != null); + Contract.Requires(messageTemplate != null); + + using (PushLogContextProperties(area, source)) + { + _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + } + } + /// public void Log( AvaloniaLogEventLevel level, @@ -45,12 +115,40 @@ namespace Avalonia.Logging.Serilog Contract.Requires(area != null); Contract.Requires(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(); + } + } } } diff --git a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs index 86411b89da..5f5064fba5 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs +++ b/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; } } diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 910846ae63..de8093c048 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/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 } diff --git a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs index 362ac86e50..f1fd2f6c7f 100644 --- a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs @@ -18,8 +18,9 @@ namespace Avalonia.Styling internal class TypeNameAndClassSelector : Selector { private readonly Selector _previous; + private readonly Lazy> _classes = new Lazy>(() => new List()); private Type _targetType; - private Lazy> _classes = new Lazy>(() => new List()); + 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 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 + private static bool AreClassesMatching(IReadOnlyList classes, IList toMatch) { - readonly IList _match; - IAvaloniaReadOnlyList _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 + { + private readonly IList _match; + private readonly IAvaloniaReadOnlyList _classes; + private bool _hasMatch; public ClassObserver(IAvaloniaReadOnlyList classes, IList match) { @@ -215,42 +237,32 @@ namespace Avalonia.Styling protected override void Initialize() { - _value = GetResult(); + _hasMatch = IsMatching(); _classes.CollectionChanged += ClassesChanged; } protected override void Subscribed(IObserver 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); } } } diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index e7be272f13..1f1590bdcd 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/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."); diff --git a/src/Avalonia.Visuals/CornerRadius.cs b/src/Avalonia.Visuals/CornerRadius.cs index 09163a2dac..ecb9e75d82 100644 --- a/src/Avalonia.Visuals/CornerRadius.cs +++ b/src/Avalonia.Visuals/CornerRadius.cs @@ -8,7 +8,10 @@ using Avalonia.Utilities; namespace Avalonia { - public struct CornerRadius + /// + /// Represents the radii of a rectangle's corners. + /// + public readonly struct CornerRadius : IEquatable { static CornerRadius() { @@ -33,22 +36,59 @@ namespace Avalonia BottomLeft = bottomLeft; } + /// + /// Radius of the top left corner. + /// public double TopLeft { get; } + + /// + /// Radius of the top right corner. + /// public double TopRight { get; } + + /// + /// Radius of the bottom right corner. + /// public double BottomRight { get; } + + /// + /// Radius of the bottom left corner. + /// public double BottomLeft { get; } + + /// + /// Gets a value indicating whether all corner radii are set to 0. + /// public bool IsEmpty => TopLeft.Equals(0) && IsUniform; + + /// + /// Gets a value indicating whether all corner radii are equal. + /// public bool IsUniform => TopLeft.Equals(TopRight) && BottomLeft.Equals(BottomRight) && TopRight.Equals(BottomRight); - public override bool Equals(object obj) + /// + /// Returns a boolean indicating whether the corner radius is equal to the other given corner radius. + /// + /// The other corner radius to test equality against. + /// True if this corner radius is equal to other; False otherwise. + 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 } + /// + /// Returns a boolean indicating whether the given Object is equal to this corner radius instance. + /// + /// The Object to compare against. + /// True if the Object is equal to this corner radius; False otherwise. + 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); } } } diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index d083a2aaf8..92b7dae904 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -10,7 +10,7 @@ namespace Avalonia /// /// A 2x3 matrix. /// - public readonly struct Matrix + public readonly struct Matrix : IEquatable { private readonly double _m11; private readonly double _m12; @@ -235,12 +235,14 @@ namespace Avalonia /// True if this matrix is equal to other; False otherwise. 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 } /// @@ -248,15 +250,7 @@ namespace Avalonia /// /// The Object to compare against. /// True if the Object is equal to this matrix; False otherwise. - 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); /// /// Returns the hash code for this instance. @@ -316,7 +310,7 @@ namespace Avalonia /// The . 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(), diff --git a/src/Avalonia.Visuals/Media/PixelPoint.cs b/src/Avalonia.Visuals/Media/PixelPoint.cs index d62c2a2e55..5384a40be9 100644 --- a/src/Avalonia.Visuals/Media/PixelPoint.cs +++ b/src/Avalonia.Visuals/Media/PixelPoint.cs @@ -10,7 +10,7 @@ namespace Avalonia /// /// Represents a point in device pixels. /// - public readonly struct PixelPoint + public readonly struct PixelPoint : IEquatable { /// /// A point representing 0,0. @@ -46,7 +46,7 @@ namespace Avalonia /// True if the points are equal; otherwise false. public static bool operator ==(PixelPoint left, PixelPoint right) { - return left.X == right.X && left.Y == right.Y; + return left.Equals(right); } /// @@ -120,7 +120,7 @@ namespace Avalonia /// The . 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 } } + /// + /// Returns a boolean indicating whether the point is equal to the other given point. + /// + /// The other point to test equality against. + /// True if this point is equal to other; False otherwise. + public bool Equals(PixelPoint other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return X == other.X && Y == other.Y; + // ReSharper restore CompareOfFloatsByEqualityOperator + } + /// /// Checks for equality between a point and an object. /// @@ -135,15 +147,7 @@ namespace Avalonia /// /// True if is a point that equals the current point. /// - 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); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Media/PixelRect.cs b/src/Avalonia.Visuals/Media/PixelRect.cs index 0e2094da07..a024e1af11 100644 --- a/src/Avalonia.Visuals/Media/PixelRect.cs +++ b/src/Avalonia.Visuals/Media/PixelRect.cs @@ -10,7 +10,7 @@ namespace Avalonia /// /// Represents a rectangle in device pixels. /// - public readonly struct PixelRect + public readonly struct PixelRect : IEquatable { /// /// An empty rectangle. @@ -148,7 +148,7 @@ namespace Avalonia /// True if the rects are equal; otherwise false. public static bool operator ==(PixelRect left, PixelRect right) { - return left.Position == right.Position && left.Size == right.Size; + return left.Equals(right); } /// @@ -196,20 +196,22 @@ namespace Avalonia rect.Height); } + /// + /// Returns a boolean indicating whether the rect is equal to the other given rect. + /// + /// The other rect to test equality against. + /// True if this rect is equal to other; False otherwise. + public bool Equals(PixelRect other) + { + return Position == other.Position && Size == other.Size; + } + /// /// Returns a boolean indicating whether the given object is equal to this rectangle. /// /// The object to compare against. /// True if the object is equal to this rectangle; false otherwise. - 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); /// /// Returns the hash code for this instance. @@ -432,7 +434,7 @@ namespace Avalonia /// The parsed . 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(), diff --git a/src/Avalonia.Visuals/Media/PixelSize.cs b/src/Avalonia.Visuals/Media/PixelSize.cs index b903b804f9..a49aa82c0d 100644 --- a/src/Avalonia.Visuals/Media/PixelSize.cs +++ b/src/Avalonia.Visuals/Media/PixelSize.cs @@ -10,7 +10,7 @@ namespace Avalonia /// /// Represents a size in device pixels. /// - public readonly struct PixelSize + public readonly struct PixelSize : IEquatable { /// /// A size representing zero @@ -51,7 +51,7 @@ namespace Avalonia /// True if the sizes are equal; otherwise false. public static bool operator ==(PixelSize left, PixelSize right) { - return left.Width == right.Width && left.Height == right.Height; + return left.Equals(right); } /// @@ -72,7 +72,7 @@ namespace Avalonia /// The . 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 } } + /// + /// Returns a boolean indicating whether the size is equal to the other given size. + /// + /// The other size to test equality against. + /// True if this size is equal to other; False otherwise. + public bool Equals(PixelSize other) + { + return Width == other.Width && Height == other.Height; + } + /// /// Checks for equality between a size and an object. /// @@ -87,15 +97,7 @@ namespace Avalonia /// /// True if is a size that equals the current size. /// - 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); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 0d3e354615..d92f8b0fc4 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/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 /// /// Defines a point. /// - public readonly struct Point + public readonly struct Point : IEquatable { static Point() { @@ -75,7 +76,7 @@ namespace Avalonia /// True if the points are equal; otherwise false. public static bool operator ==(Point left, Point right) { - return left.X == right.X && left.Y == right.Y; + return left.Equals(right); } /// @@ -177,7 +178,7 @@ namespace Avalonia /// The . 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 } } + /// + /// Returns a boolean indicating whether the point is equal to the other given point. + /// + /// The other point to test equality against. + /// True if this point is equal to other; False otherwise. + public bool Equals(Point other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return _x == other._x && + _y == other._y; + // ReSharper enable CompareOfFloatsByEqualityOperator + } + /// /// Checks for equality between a point and an object. /// @@ -193,16 +207,7 @@ namespace Avalonia /// /// True if is a point that equals the current point. /// - 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); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 8f08f7f51f..4dfd641525 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -11,7 +11,7 @@ namespace Avalonia /// /// Defines a rectangle. /// - public readonly struct Rect + public readonly struct Rect : IEquatable { static Rect() { @@ -164,7 +164,9 @@ namespace Avalonia /// /// Gets a value that indicates whether the rectangle is empty. /// + // ReSharper disable CompareOfFloatsByEqualityOperator public bool IsEmpty => _width == 0 && _height == 0; + // ReSharper restore CompareOfFloatsByEqualityOperator /// /// Checks for equality between two s. @@ -174,7 +176,7 @@ namespace Avalonia /// True if the rects are equal; otherwise false. public static bool operator ==(Rect left, Rect right) { - return left.Position == right.Position && left.Size == right.Size; + return left.Equals(right); } /// @@ -297,21 +299,27 @@ namespace Avalonia Size.Deflate(thickness)); } + /// + /// Returns a boolean indicating whether the rect is equal to the other given rect. + /// + /// The other rect to test equality against. + /// True if this rect is equal to other; False otherwise. + public bool Equals(Rect other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return _x == other._x && + _y == other._y && + _width == other._width && + _height == other._height; + // ReSharper enable CompareOfFloatsByEqualityOperator + } + /// /// Returns a boolean indicating whether the given object is equal to this rectangle. /// /// The object to compare against. /// True if the object is equal to this rectangle; false otherwise. - 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); /// /// 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 /// The parsed . 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(), diff --git a/src/Avalonia.Visuals/RelativePoint.cs b/src/Avalonia.Visuals/RelativePoint.cs index d38bb1d496..2e8fb16bc1 100644 --- a/src/Avalonia.Visuals/RelativePoint.cs +++ b/src/Avalonia.Visuals/RelativePoint.cs @@ -107,10 +107,7 @@ namespace Avalonia /// /// The other object. /// True if the objects are equal, otherwise false. - 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); /// /// Checks if the 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 /// The parsed . 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(); diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs index 927ec3ef75..d2e4b2dc26 100644 --- a/src/Avalonia.Visuals/RelativeRect.cs +++ b/src/Avalonia.Visuals/RelativeRect.cs @@ -116,10 +116,7 @@ namespace Avalonia /// /// The other object. /// True if the objects are equal, otherwise false. - 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); /// /// Checks if the 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); } - + /// /// Parses a string. /// @@ -169,7 +163,7 @@ namespace Avalonia /// The parsed . 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(); diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index efcc555159..d9a68b236a 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/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; } diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index b2d242d4af..68d56eeedd 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/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; } diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index 140688f8bc..c2594658b9 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/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 { diff --git a/src/Avalonia.Visuals/Size.cs b/src/Avalonia.Visuals/Size.cs index 782c5ea67b..aba2ed8d62 100644 --- a/src/Avalonia.Visuals/Size.cs +++ b/src/Avalonia.Visuals/Size.cs @@ -11,7 +11,7 @@ namespace Avalonia /// /// Defines a size. /// - public readonly struct Size + public readonly struct Size : IEquatable { static Size() { @@ -72,7 +72,7 @@ namespace Avalonia /// True if the sizes are equal; otherwise false. public static bool operator ==(Size left, Size right) { - return left._width == right._width && left._height == right._height; + return left.Equals(right); } /// @@ -158,7 +158,7 @@ namespace Avalonia /// The . 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)); } + /// + /// Returns a boolean indicating whether the size is equal to the other given size. + /// + /// The other size to test equality against. + /// True if this size is equal to other; False otherwise. + public bool Equals(Size other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return _width == other._width && + _height == other._height; + // ReSharper enable CompareOfFloatsByEqualityOperator + } + /// /// Checks for equality between a size and an object. /// @@ -198,16 +211,7 @@ namespace Avalonia /// /// True if is a size that equals the current size. /// - 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); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Thickness.cs b/src/Avalonia.Visuals/Thickness.cs index 830ee4666e..44ff66069f 100644 --- a/src/Avalonia.Visuals/Thickness.cs +++ b/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 /// /// Describes the thickness of a frame around a rectangle. /// - public readonly struct Thickness + public readonly struct Thickness : IEquatable { static Thickness() { @@ -204,7 +203,9 @@ namespace Avalonia /// The . 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); } } + /// + /// Returns a boolean indicating whether the thickness is equal to the other given point. + /// + /// The other thickness to test equality against. + /// True if this thickness is equal to other; False otherwise. + public bool Equals(Thickness other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return _left == other._left && + _top == other._top && + _right == other._right && + _bottom == other._bottom; + // ReSharper restore CompareOfFloatsByEqualityOperator + } + /// /// Checks for equality between a thickness and an object. /// @@ -232,19 +248,7 @@ namespace Avalonia /// /// True if is a size that equals the current size. /// - 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); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index 11bda8b00e..576d2daaaa 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -11,7 +11,7 @@ namespace Avalonia /// /// Defines a vector. /// - public readonly struct Vector + public readonly struct Vector : IEquatable { static Vector() { @@ -138,7 +138,6 @@ namespace Avalonia /// /// The other vector. /// True if vectors are nearly equal. - [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() { diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 1f2d67b69e..f4306d3929 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -359,7 +359,7 @@ namespace Avalonia /// The event args. 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 /// The event args. 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}", diff --git a/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs b/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs index 39b328adc2..b2121aa8da 100644 --- a/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs +++ b/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 { /// /// Holds information about the bounds of a control, together with a transform and a clip. /// - public readonly struct TransformedBounds + public readonly struct TransformedBounds : IEquatable { /// /// Initializes a new instance of the 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() { diff --git a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs index d15b5fe4b8..3dc2e8e41f 100644 --- a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs +++ b/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; } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs index b42bd53619..6000b71f9d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs +++ b/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}'.", diff --git a/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs b/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs index e03427c161..f7d228609d 100644 --- a/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs +++ b/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}", diff --git a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs index 10b89d79b8..fd2f14fc58 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs +++ b/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}", diff --git a/tests/Avalonia.UnitTests/TestLogSink.cs b/tests/Avalonia.UnitTests/TestLogSink.cs index 8e4dd7164f..a2b188c273 100644 --- a/tests/Avalonia.UnitTests/TestLogSink.cs +++ b/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(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0) + { + _callback(level, area, source, messageTemplate, propertyValue0); + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, + T0 propertyValue0, T1 propertyValue1) + { + _callback(level, area, source, messageTemplate, propertyValue0, propertyValue1); + } + + public void Log(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); }