From b96df46054014da28b6ec7f3526b9aa2238882c5 Mon Sep 17 00:00:00 2001 From: Il Harper Date: Sat, 1 May 2021 23:56:50 +0800 Subject: [PATCH 01/84] fix: Fix ArgumentNullException in TreeView.ExpandSubTree() close #4288 --- src/Avalonia.Controls/TreeView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 3afbbd944c..e890e4346f 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -168,7 +168,7 @@ namespace Avalonia.Controls var panel = item.Presenter.Panel; - if (panel != null) + if (item.Presenter?.Panel != null) { foreach (var child in panel.Children) { From c6464ba8d55f4713e89de2c2784959c2f740c06f Mon Sep 17 00:00:00 2001 From: Il Harper Date: Thu, 13 May 2021 21:59:34 +0800 Subject: [PATCH 02/84] fix: Refactor --- src/Avalonia.Controls/TreeView.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index e890e4346f..78cd22ae32 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -166,11 +166,9 @@ namespace Avalonia.Controls { item.IsExpanded = true; - var panel = item.Presenter.Panel; - if (item.Presenter?.Panel != null) { - foreach (var child in panel.Children) + foreach (var child in item.Presenter.Panel.Children) { if (child is TreeViewItem treeViewItem) { From 2956f79c5c8268dd4d3f763324a501336e756394 Mon Sep 17 00:00:00 2001 From: Scott Whitney Date: Thu, 27 May 2021 21:52:37 +0930 Subject: [PATCH 03/84] Added DecimalPlaces property to NumericUpDown control --- .../NumericUpDown/NumericUpDown.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index abfbc038eb..e3db619269 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -119,6 +119,12 @@ namespace Avalonia.Controls public static readonly StyledProperty VerticalContentAlignmentProperty = ContentControl.VerticalContentAlignmentProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty DecimalPlacesProperty = + AvaloniaProperty.Register(nameof(DecimalPlaces), 2); + private IDisposable _textBoxTextChangedSubscription; private double _value; @@ -261,6 +267,15 @@ namespace Avalonia.Controls } } + /// + /// Gets or sets the number of decimal places to display in the up-down control. + /// + public int DecimalPlaces + { + get { return GetValue(DecimalPlacesProperty); } + set { SetValue(DecimalPlacesProperty, value); } + } + /// /// Gets or sets the object to use as a watermark if the is null. /// @@ -620,7 +635,7 @@ namespace Avalonia.Controls /// private void OnIncrement() { - var result = Value + Increment; + var result = Math.Round(Value + Increment, DecimalPlaces); Value = MathUtilities.Clamp(result, Minimum, Maximum); } @@ -629,7 +644,7 @@ namespace Avalonia.Controls /// private void OnDecrement() { - var result = Value - Increment; + var result = Math.Round(Value - Increment, DecimalPlaces); Value = MathUtilities.Clamp(result, Minimum, Maximum); } From 4cc7dba01a98e4781544b78811188614c0d9da0d Mon Sep 17 00:00:00 2001 From: Scott Whitney Date: Fri, 28 May 2021 10:21:15 +0930 Subject: [PATCH 04/84] Changed backing types to decimal. Added NumberFormat property. Changed ThrowCannotBeGreaterThanException to accept generics as it displays the values only. --- src/Avalonia.Base/Utilities/MathUtilities.cs | 32 +++- .../NumericUpDown/NumericUpDown.cs | 165 +++++++++++------- .../NumericUpDownValueChangedEventArgs.cs | 6 +- 3 files changed, 132 insertions(+), 71 deletions(-) diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 1ea369954a..596cbf1d7e 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -224,6 +224,34 @@ namespace Avalonia.Utilities } } + /// + /// Clamps a value between a minimum and maximum value. + /// + /// The value. + /// The minimum value. + /// The maximum value. + /// The clamped value. + public static decimal Clamp(decimal val, decimal min, decimal max) + { + if (min > max) + { + ThrowCannotBeGreaterThanException(min, max); + } + + if (val < min) + { + return min; + } + else if (val > max) + { + return max; + } + else + { + return val; + } + } + /// /// Clamps a value between a minimum and maximum value. /// @@ -281,8 +309,8 @@ namespace Avalonia.Utilities { return angle * 2 * Math.PI; } - - private static void ThrowCannotBeGreaterThanException(double min, double max) + + private static void ThrowCannotBeGreaterThanException(T min, T max) { throw new ArgumentException($"{min} cannot be greater than {max}."); } diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index e3db619269..659bc272e0 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -49,6 +49,13 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(CultureInfo), o => o.CultureInfo, (o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture); + /// + /// Defines the property. + /// + public static readonly DirectProperty NumberFormatProperty = + AvaloniaProperty.RegisterDirect(nameof(NumberFormat), o => o.NumberFormat, + (o, v) => o.NumberFormat = v, NumberFormatInfo.CurrentInfo); + /// /// Defines the property. /// @@ -58,8 +65,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty IncrementProperty = - AvaloniaProperty.Register(nameof(Increment), 1.0d, coerce: OnCoerceIncrement); + public static readonly StyledProperty IncrementProperty = + AvaloniaProperty.Register(nameof(Increment), 1.0m, coerce: OnCoerceIncrement); /// /// Defines the property. @@ -70,14 +77,14 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty MaximumProperty = - AvaloniaProperty.Register(nameof(Maximum), double.MaxValue, coerce: OnCoerceMaximum); + public static readonly StyledProperty MaximumProperty = + AvaloniaProperty.Register(nameof(Maximum), decimal.MaxValue, coerce: OnCoerceMaximum); /// /// Defines the property. /// - public static readonly StyledProperty MinimumProperty = - AvaloniaProperty.Register(nameof(Minimum), double.MinValue, coerce: OnCoerceMinimum); + public static readonly StyledProperty MinimumProperty = + AvaloniaProperty.Register(nameof(Minimum), decimal.MinValue, coerce: OnCoerceMinimum); /// /// Defines the property. @@ -96,8 +103,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly DirectProperty ValueProperty = - AvaloniaProperty.RegisterDirect(nameof(Value), updown => updown.Value, + public static readonly DirectProperty ValueProperty = + AvaloniaProperty.RegisterDirect(nameof(Value), updown => updown.Value, (updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); /// @@ -106,7 +113,6 @@ namespace Avalonia.Controls public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register(nameof(Watermark)); - /// /// Defines the property. /// @@ -119,15 +125,9 @@ namespace Avalonia.Controls public static readonly StyledProperty VerticalContentAlignmentProperty = ContentControl.VerticalContentAlignmentProperty.AddOwner(); - /// - /// Defines the property. - /// - public static readonly StyledProperty DecimalPlacesProperty = - AvaloniaProperty.Register(nameof(DecimalPlaces), 2); - private IDisposable _textBoxTextChangedSubscription; - private double _value; + private decimal _value; private string _text; private bool _internalValueSet; private bool _clipValueToMinMax; @@ -135,7 +135,8 @@ namespace Avalonia.Controls private bool _isTextChangedFromUI; private CultureInfo _cultureInfo; private NumberStyles _parsingNumberStyle = NumberStyles.Any; - + private NumberFormatInfo _numberFormat; + /// /// Gets the Spinner template part. /// @@ -188,7 +189,21 @@ namespace Avalonia.Controls public CultureInfo CultureInfo { get { return _cultureInfo; } - set { SetAndRaise(CultureInfoProperty, ref _cultureInfo, value); } + set + { + SetAndRaise(CultureInfoProperty, ref _cultureInfo, value); + //Set and Raise the NumberFormatProperty when CultureInfo is changed. + SetAndRaise(NumberFormatProperty, ref _numberFormat, value?.NumberFormat); + } + } + + /// + /// Gets or sets the current NumberFormatInfo + /// + public NumberFormatInfo NumberFormat + { + get { return _numberFormat; } + set { SetAndRaise(NumberFormatProperty, ref _numberFormat, value); } } /// @@ -203,7 +218,7 @@ namespace Avalonia.Controls /// /// Gets or sets the amount in which to increment the . /// - public double Increment + public decimal Increment { get { return GetValue(IncrementProperty); } set { SetValue(IncrementProperty, value); } @@ -221,7 +236,7 @@ namespace Avalonia.Controls /// /// Gets or sets the maximum allowed value. /// - public double Maximum + public decimal Maximum { get { return GetValue(MaximumProperty); } set { SetValue(MaximumProperty, value); } @@ -230,7 +245,7 @@ namespace Avalonia.Controls /// /// Gets or sets the minimum allowed value. /// - public double Minimum + public decimal Minimum { get { return GetValue(MinimumProperty); } set { SetValue(MinimumProperty, value); } @@ -257,7 +272,7 @@ namespace Avalonia.Controls /// /// Gets or sets the value. /// - public double Value + public decimal Value { get { return _value; } set @@ -267,15 +282,6 @@ namespace Avalonia.Controls } } - /// - /// Gets or sets the number of decimal places to display in the up-down control. - /// - public int DecimalPlaces - { - get { return GetValue(DecimalPlacesProperty); } - set { SetValue(DecimalPlacesProperty, value); } - } - /// /// Gets or sets the object to use as a watermark if the is null. /// @@ -285,7 +291,6 @@ namespace Avalonia.Controls set { SetValue(WatermarkProperty, value); } } - /// /// Gets or sets the horizontal alignment of the content within the control. /// @@ -326,6 +331,7 @@ namespace Avalonia.Controls static NumericUpDown() { CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged); + NumberFormatProperty.Changed.Subscribe(OnNumberFormatChanged); FormatStringProperty.Changed.Subscribe(FormatStringChanged); IncrementProperty.Changed.Subscribe(IncrementChanged); IsReadOnlyProperty.Changed.Subscribe(OnIsReadOnlyChanged); @@ -412,6 +418,19 @@ namespace Avalonia.Controls } } + /// + /// Called when the property value changed. + /// + /// The old value. + /// The new value. + protected virtual void OnNumberFormatChanged(NumberFormatInfo oldValue, NumberFormatInfo newValue) + { + if (IsInitialized) + { + SyncTextAndValueProperties(false, null); + } + } + /// /// Called when the property value changed. /// @@ -430,7 +449,7 @@ namespace Avalonia.Controls /// /// The old value. /// The new value. - protected virtual void OnIncrementChanged(double oldValue, double newValue) + protected virtual void OnIncrementChanged(decimal oldValue, decimal newValue) { if (IsInitialized) { @@ -453,7 +472,7 @@ namespace Avalonia.Controls /// /// The old value. /// The new value. - protected virtual void OnMaximumChanged(double oldValue, double newValue) + protected virtual void OnMaximumChanged(decimal oldValue, decimal newValue) { if (IsInitialized) { @@ -470,7 +489,7 @@ namespace Avalonia.Controls /// /// The old value. /// The new value. - protected virtual void OnMinimumChanged(double oldValue, double newValue) + protected virtual void OnMinimumChanged(decimal oldValue, decimal newValue) { if (IsInitialized) { @@ -500,7 +519,7 @@ namespace Avalonia.Controls /// /// The old value. /// The new value. - protected virtual void OnValueChanged(double oldValue, double newValue) + protected virtual void OnValueChanged(decimal oldValue, decimal newValue) { if (!_internalValueSet && IsInitialized) { @@ -516,7 +535,7 @@ namespace Avalonia.Controls /// Called when the property has to be coerced. /// /// The value. - protected virtual double OnCoerceIncrement(double baseValue) + protected virtual decimal OnCoerceIncrement(decimal baseValue) { return baseValue; } @@ -525,7 +544,7 @@ namespace Avalonia.Controls /// Called when the property has to be coerced. /// /// The value. - protected virtual double OnCoerceMaximum(double baseValue) + protected virtual decimal OnCoerceMaximum(decimal baseValue) { return Math.Max(baseValue, Minimum); } @@ -534,7 +553,7 @@ namespace Avalonia.Controls /// Called when the property has to be coerced. /// /// The value. - protected virtual double OnCoerceMinimum(double baseValue) + protected virtual decimal OnCoerceMinimum(decimal baseValue) { return Math.Min(baseValue, Maximum); } @@ -543,7 +562,7 @@ namespace Avalonia.Controls /// Called when the property has to be coerced. /// /// The value. - protected virtual double OnCoerceValue(double baseValue) + protected virtual decimal OnCoerceValue(decimal baseValue) { return baseValue; } @@ -577,7 +596,7 @@ namespace Avalonia.Controls /// /// The old value. /// The new value. - protected virtual void RaiseValueChangedEvent(double oldValue, double newValue) + protected virtual void RaiseValueChangedEvent(decimal oldValue, decimal newValue) { var e = new NumericUpDownValueChangedEventArgs(ValueChangedEvent, oldValue, newValue); RaiseEvent(e); @@ -586,9 +605,9 @@ namespace Avalonia.Controls /// /// Converts the formatted text to a value. /// - private double ConvertTextToValue(string text) + private decimal ConvertTextToValue(string text) { - double result = 0; + decimal result = 0; if (string.IsNullOrEmpty(text)) { @@ -624,10 +643,10 @@ namespace Avalonia.Controls //Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind. if (FormatString.Contains("{0")) { - return string.Format(CultureInfo, FormatString, Value); + return string.Format(NumberFormat, FormatString, Value); } - return Value.ToString(FormatString, CultureInfo); + return Value.ToString(FormatString, NumberFormat); } /// @@ -635,7 +654,7 @@ namespace Avalonia.Controls /// private void OnIncrement() { - var result = Math.Round(Value + Increment, DecimalPlaces); + var result = Value + Increment; Value = MathUtilities.Clamp(result, Minimum, Maximum); } @@ -644,7 +663,7 @@ namespace Avalonia.Controls /// private void OnDecrement() { - var result = Math.Round(Value - Increment, DecimalPlaces); + var result = Value - Increment; Value = MathUtilities.Clamp(result, Minimum, Maximum); } @@ -689,6 +708,20 @@ namespace Avalonia.Controls } } + /// + /// Called when the property value changed. + /// + /// The event args. + private static void OnNumberFormatChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.Sender is NumericUpDown upDown) + { + var oldValue = (NumberFormatInfo)e.OldValue; + var newValue = (NumberFormatInfo)e.NewValue; + upDown.OnNumberFormatChanged(oldValue, newValue); + } + } + /// /// Called when the property value changed. /// @@ -697,8 +730,8 @@ namespace Avalonia.Controls { if (e.Sender is NumericUpDown upDown) { - var oldValue = (double)e.OldValue; - var newValue = (double)e.NewValue; + var oldValue = (decimal)e.OldValue; + var newValue = (decimal)e.NewValue; upDown.OnIncrementChanged(oldValue, newValue); } } @@ -739,8 +772,8 @@ namespace Avalonia.Controls { if (e.Sender is NumericUpDown upDown) { - var oldValue = (double)e.OldValue; - var newValue = (double)e.NewValue; + var oldValue = (decimal)e.OldValue; + var newValue = (decimal)e.NewValue; upDown.OnMaximumChanged(oldValue, newValue); } } @@ -753,8 +786,8 @@ namespace Avalonia.Controls { if (e.Sender is NumericUpDown upDown) { - var oldValue = (double)e.OldValue; - var newValue = (double)e.NewValue; + var oldValue = (decimal)e.OldValue; + var newValue = (decimal)e.NewValue; upDown.OnMinimumChanged(oldValue, newValue); } } @@ -781,13 +814,13 @@ namespace Avalonia.Controls { if (e.Sender is NumericUpDown upDown) { - var oldValue = (double)e.OldValue; - var newValue = (double)e.NewValue; + var oldValue = (decimal)e.OldValue; + var newValue = (decimal)e.NewValue; upDown.OnValueChanged(oldValue, newValue); } } - private void SetValueInternal(double value) + private void SetValueInternal(decimal value) { _internalValueSet = true; try @@ -800,7 +833,7 @@ namespace Avalonia.Controls } } - private static double OnCoerceMaximum(IAvaloniaObject instance, double value) + private static decimal OnCoerceMaximum(IAvaloniaObject instance, decimal value) { if (instance is NumericUpDown upDown) { @@ -810,7 +843,7 @@ namespace Avalonia.Controls return value; } - private static double OnCoerceMinimum(IAvaloniaObject instance, double value) + private static decimal OnCoerceMinimum(IAvaloniaObject instance, decimal value) { if (instance is NumericUpDown upDown) { @@ -820,7 +853,7 @@ namespace Avalonia.Controls return value; } - private static double OnCoerceIncrement(IAvaloniaObject instance, double value) + private static decimal OnCoerceIncrement(IAvaloniaObject instance, decimal value) { if (instance is NumericUpDown upDown) { @@ -992,23 +1025,23 @@ namespace Avalonia.Controls return parsedTextIsValid; } - private double ConvertTextToValueCore(string currentValueText, string text) + private decimal ConvertTextToValueCore(string currentValueText, string text) { - double result; + decimal result; if (IsPercent(FormatString)) { - result = decimal.ToDouble(ParsePercent(text, CultureInfo)); + result = ParsePercent(text, NumberFormat); } else { // Problem while converting new text - if (!double.TryParse(text, ParsingNumberStyle, CultureInfo, out var outputValue)) + if (!decimal.TryParse(text, ParsingNumberStyle, NumberFormat, out var outputValue)) { var shouldThrow = true; // Check if CurrentValueText is also failing => it also contains special characters. ex : 90° - if (!double.TryParse(currentValueText, ParsingNumberStyle, CultureInfo, out var _)) + if (!decimal.TryParse(currentValueText, ParsingNumberStyle, NumberFormat, out var _)) { // extract non-digit characters var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c)); @@ -1021,7 +1054,7 @@ namespace Avalonia.Controls text = text.Replace(character.ToString(), string.Empty); } // if without the special characters, parsing is good, do not throw - if (double.TryParse(text, ParsingNumberStyle, CultureInfo, out outputValue)) + if (decimal.TryParse(text, ParsingNumberStyle, NumberFormat, out outputValue)) { shouldThrow = false; } @@ -1038,7 +1071,7 @@ namespace Avalonia.Controls return result; } - private void ValidateMinMax(double value) + private void ValidateMinMax(decimal value) { if (value < Minimum) { diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs index e994ffdd15..9b467d682c 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs @@ -4,13 +4,13 @@ namespace Avalonia.Controls { public class NumericUpDownValueChangedEventArgs : RoutedEventArgs { - public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, double oldValue, double newValue) : base(routedEvent) + public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, decimal oldValue, decimal newValue) : base(routedEvent) { OldValue = oldValue; NewValue = newValue; } - public double OldValue { get; } - public double NewValue { get; } + public decimal OldValue { get; } + public decimal NewValue { get; } } } From 6c0d9e86bf175f265e5c0a5813fa6887a4c8a5d5 Mon Sep 17 00:00:00 2001 From: Scott Whitney Date: Fri, 28 May 2021 12:07:41 +0930 Subject: [PATCH 05/84] ApiCompatBaseline --- src/Avalonia.Controls/ApiCompatBaseline.txt | 26 ++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index a79b3b4d7b..2f312bb266 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -4,6 +4,30 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract. +MembersMustExist : Member 'public Avalonia.DirectProperty Avalonia.DirectProperty Avalonia.Controls.NumericUpDown.ValueProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.NumericUpDown.IncrementProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.NumericUpDown.MaximumProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.NumericUpDown.MinimumProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Increment.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Increment.set(System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Maximum.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Maximum.set(System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Minimum.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Minimum.set(System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceIncrement(System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceMaximum(System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceMinimum(System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceValue(System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnIncrementChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnMaximumChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnMinimumChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnValueChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.RaiseValueChangedEvent(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Value.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Value.set(System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDownValueChangedEventArgs..ctor(Avalonia.Interactivity.RoutedEvent, System.Double, System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.NewValue.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. @@ -11,4 +35,4 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 12 +Total Issues: 36 From 7ffc527cd5f76237ae92d32d2a81be6153853759 Mon Sep 17 00:00:00 2001 From: Scott Whitney Date: Sat, 29 May 2021 07:56:11 +0930 Subject: [PATCH 06/84] Added Obsolete attribute to CultureInfo --- src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index 659bc272e0..3a83d9ed7c 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -45,6 +45,7 @@ namespace Avalonia.Controls /// /// Defines the property. /// + [Obsolete] public static readonly DirectProperty CultureInfoProperty = AvaloniaProperty.RegisterDirect(nameof(CultureInfo), o => o.CultureInfo, (o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture); @@ -186,6 +187,7 @@ namespace Avalonia.Controls /// /// Gets or sets the current CultureInfo. /// + [Obsolete("CultureInfo is obsolete, please use NumberFormat instead.")] public CultureInfo CultureInfo { get { return _cultureInfo; } From 52e6b9a6d1ac48f71de073d9cdc53bbddb5c1124 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 5 Jun 2021 22:28:20 -0400 Subject: [PATCH 07/84] Make Animation.RunAsync cancellable --- src/Avalonia.Animation/Animation.cs | 14 +++++++++----- src/Avalonia.Animation/ApiCompatBaseline.txt | 6 ++++++ src/Avalonia.Animation/IAnimation.cs | 3 ++- 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 src/Avalonia.Animation/ApiCompatBaseline.txt diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index c42153ec4f..a170456854 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Threading; using System.Threading.Tasks; + using Avalonia.Animation.Animators; using Avalonia.Animation.Easings; -using Avalonia.Collections; using Avalonia.Data; using Avalonia.Metadata; @@ -292,7 +293,7 @@ namespace Avalonia.Animation return (newAnimatorInstances, subscriptions); } - /// + /// public IDisposable Apply(Animatable control, IClock clock, IObservable match, Action onComplete) { var (animators, subscriptions) = InterpretKeyframes(control); @@ -323,21 +324,24 @@ namespace Avalonia.Animation return new CompositeDisposable(subscriptions); } - /// - public Task RunAsync(Animatable control, IClock clock = null) + /// + public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default) { var run = new TaskCompletionSource(); if (this.IterationCount == IterationCount.Infinite) run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); - IDisposable subscriptions = null; + IDisposable subscriptions = null, cancellation = null; subscriptions = this.Apply(control, clock, Observable.Return(true), () => { run.SetResult(null); subscriptions?.Dispose(); + cancellation?.Dispose(); }); + cancellation = cancellationToken.Register(state => ((IDisposable)state).Dispose(), subscriptions); + return run.Task; } } diff --git a/src/Avalonia.Animation/ApiCompatBaseline.txt b/src/Avalonia.Animation/ApiCompatBaseline.txt new file mode 100644 index 0000000000..58cb7830e7 --- /dev/null +++ b/src/Avalonia.Animation/ApiCompatBaseline.txt @@ -0,0 +1,6 @@ +Compat issues with assembly Avalonia.Animation: +MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.Animation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock, System.Threading.CancellationToken)' is present in the implementation but not in the contract. +Total Issues: 4 diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index ff85535d8a..5844ba5688 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Avalonia.Animation @@ -16,6 +17,6 @@ namespace Avalonia.Animation /// /// Run the animation on the specified control. /// - Task RunAsync(Animatable control, IClock clock); + Task RunAsync(Animatable control, IClock clock, CancellationToken cancellationToken); } } From 4bbedf581562fcf14a5a4227f135876db3f8856f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 5 Jun 2021 22:28:45 -0400 Subject: [PATCH 08/84] Make PageTransition.Start cancellable --- .../Animation/CompositePageTransition.cs | 21 ++---- src/Avalonia.Visuals/Animation/CrossFade.cs | 64 ++++++++----------- .../Animation/IPageTransition.cs | 6 +- src/Avalonia.Visuals/Animation/PageSlide.cs | 30 ++++----- src/Avalonia.Visuals/ApiCompatBaseline.txt | 9 ++- 5 files changed, 55 insertions(+), 75 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/CompositePageTransition.cs b/src/Avalonia.Visuals/Animation/CompositePageTransition.cs index 9489914c97..2deebd7792 100644 --- a/src/Avalonia.Visuals/Animation/CompositePageTransition.cs +++ b/src/Avalonia.Visuals/Animation/CompositePageTransition.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Avalonia.Metadata; @@ -35,25 +36,11 @@ namespace Avalonia.Animation [Content] public List PageTransitions { get; set; } = new List(); - /// - /// Starts the animation. - /// - /// - /// The control that is being transitioned away from. May be null. - /// - /// - /// The control that is being transitioned to. May be null. - /// - /// - /// Defines the direction of the transition. - /// - /// - /// A that tracks the progress of the animation. - /// - public Task Start(Visual from, Visual to, bool forward) + /// + public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) { var transitionTasks = PageTransitions - .Select(transition => transition.Start(from, to, forward)) + .Select(transition => transition.Start(from, to, forward, cancellationToken)) .ToList(); return Task.WhenAll(transitionTasks); } diff --git a/src/Avalonia.Visuals/Animation/CrossFade.cs b/src/Avalonia.Visuals/Animation/CrossFade.cs index 0615b854da..9ff0d99b23 100644 --- a/src/Avalonia.Visuals/Animation/CrossFade.cs +++ b/src/Avalonia.Visuals/Animation/CrossFade.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Threading; using System.Threading.Tasks; using Avalonia.Animation.Easings; using Avalonia.Styling; @@ -97,49 +99,39 @@ namespace Avalonia.Animation set => _fadeOutAnimation.Easing = value; } - /// - /// Starts the animation. - /// - /// - /// The control that is being transitioned away from. May be null. - /// - /// - /// The control that is being transitioned to. May be null. - /// - /// - /// A that tracks the progress of the animation. - /// - public async Task Start(Visual from, Visual to) + /// + public async Task Start(Visual from, Visual to, CancellationToken cancellationToken) { - var tasks = new List(); - - if (to != null) - { - to.Opacity = 0; - } - - if (from != null) + if (cancellationToken.IsCancellationRequested) { - tasks.Add(_fadeOutAnimation.RunAsync(from)); + return; } - if (to != null) + var tasks = new List(); + using (var disposables = new CompositeDisposable()) { - to.IsVisible = true; - tasks.Add(_fadeInAnimation.RunAsync(to)); + if (to != null) + { + disposables.Add(to.SetValue(Visual.OpacityProperty, 0, Data.BindingPriority.Animation)); + } - } + if (from != null) + { + tasks.Add(_fadeOutAnimation.RunAsync(from, null, cancellationToken)); + } - await Task.WhenAll(tasks); + if (to != null) + { + to.IsVisible = true; + tasks.Add(_fadeInAnimation.RunAsync(to, null, cancellationToken)); + } - if (from != null) - { - from.IsVisible = false; - } + await Task.WhenAll(tasks); - if (to != null) - { - to.Opacity = 1; + if (from != null && !cancellationToken.IsCancellationRequested) + { + from.IsVisible = false; + } } } @@ -158,9 +150,9 @@ namespace Avalonia.Animation /// /// A that tracks the progress of the animation. /// - Task IPageTransition.Start(Visual from, Visual to, bool forward) + Task IPageTransition.Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) { - return Start(from, to); + return Start(from, to, cancellationToken); } } } diff --git a/src/Avalonia.Visuals/Animation/IPageTransition.cs b/src/Avalonia.Visuals/Animation/IPageTransition.cs index 659bc12424..2d19ddbb5b 100644 --- a/src/Avalonia.Visuals/Animation/IPageTransition.cs +++ b/src/Avalonia.Visuals/Animation/IPageTransition.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; namespace Avalonia.Animation @@ -19,9 +20,12 @@ namespace Avalonia.Animation /// /// If the animation is bidirectional, controls the direction of the animation. /// + /// + /// Animation cancellation. + /// /// /// A that tracks the progress of the animation. /// - Task Start(Visual from, Visual to, bool forward); + Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken); } } diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs index dd5d598e12..7d033ccf61 100644 --- a/src/Avalonia.Visuals/Animation/PageSlide.cs +++ b/src/Avalonia.Visuals/Animation/PageSlide.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Avalonia.Animation.Easings; using Avalonia.Media; @@ -60,23 +61,14 @@ namespace Avalonia.Animation /// public Easing SlideOutEasing { get; set; } = new LinearEasing(); - /// - /// Starts the animation. - /// - /// - /// The control that is being transitioned away from. May be null. - /// - /// - /// The control that is being transitioned to. May be null. - /// - /// - /// If true, the new page is slid in from the right, or if false from the left. - /// - /// - /// A that tracks the progress of the animation. - /// - public async Task Start(Visual from, Visual to, bool forward) + /// + public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + return; + } + var tasks = new List(); var parent = GetVisualParent(from, to); var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; @@ -109,7 +101,7 @@ namespace Avalonia.Animation }, Duration = Duration }; - tasks.Add(animation.RunAsync(from)); + tasks.Add(animation.RunAsync(from, null, cancellationToken)); } if (to != null) @@ -140,12 +132,12 @@ namespace Avalonia.Animation }, Duration = Duration }; - tasks.Add(animation.RunAsync(to)); + tasks.Add(animation.RunAsync(to, null, cancellationToken)); } await Task.WhenAll(tasks); - if (from != null) + if (from != null && !cancellationToken.IsCancellationRequested) { from.IsVisible = false; } diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index f9fd125615..c917902dc3 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -1,4 +1,10 @@ Compat issues with assembly Avalonia.Visuals: +MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.CompositePageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.CrossFade.Start(Avalonia.Visual, Avalonia.Visual)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract. +MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract. CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. @@ -63,9 +69,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation. MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract. -Total Issues: 64 InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. -Total Issues: 11 +Total Issues: 74 From f13ece461b43306c8f9c2680662e649c265b1e07 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 5 Jun 2021 22:29:16 -0400 Subject: [PATCH 09/84] Provide cancellation to animations where neccessary --- src/Avalonia.Controls/Expander.cs | 21 +++++++++++++------ .../Presenters/CarouselPresenter.cs | 2 +- .../TransitioningContentControl.cs | 13 +++++++++--- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index 052b42a233..b9c79e5749 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -1,7 +1,11 @@ +using System.Threading; + using Avalonia.Animation; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +#nullable enable + namespace Avalonia.Controls { /// @@ -36,8 +40,8 @@ namespace Avalonia.Controls [PseudoClasses(":expanded", ":up", ":down", ":left", ":right")] public class Expander : HeaderedContentControl { - public static readonly StyledProperty ContentTransitionProperty = - AvaloniaProperty.Register(nameof(ContentTransition)); + public static readonly StyledProperty ContentTransitionProperty = + AvaloniaProperty.Register(nameof(ContentTransition)); public static readonly StyledProperty ExpandDirectionProperty = AvaloniaProperty.Register(nameof(ExpandDirection), ExpandDirection.Down); @@ -50,6 +54,7 @@ namespace Avalonia.Controls defaultBindingMode: Data.BindingMode.TwoWay); private bool _isExpanded; + private CancellationTokenSource? _lastTransitionCts; static Expander() { @@ -61,7 +66,7 @@ namespace Avalonia.Controls UpdatePseudoClasses(ExpandDirection); } - public IPageTransition ContentTransition + public IPageTransition? ContentTransition { get => GetValue(ContentTransitionProperty); set => SetValue(ContentTransitionProperty, value); @@ -83,19 +88,23 @@ namespace Avalonia.Controls } } - protected virtual void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e) + protected virtual async void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e) { if (Content != null && ContentTransition != null && Presenter is Visual visualContent) { bool forward = ExpandDirection == ExpandDirection.Left || ExpandDirection == ExpandDirection.Up; + + _lastTransitionCts?.Cancel(); + _lastTransitionCts = new CancellationTokenSource(); + if (IsExpanded) { - ContentTransition.Start(null, visualContent, forward); + await ContentTransition.Start(null, visualContent, forward, _lastTransitionCts.Token); } else { - ContentTransition.Start(visualContent, null, !forward); + await ContentTransition.Start(visualContent, null, forward, _lastTransitionCts.Token); } } } diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index 7888249bdd..81f43865a7 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs @@ -186,7 +186,7 @@ namespace Avalonia.Controls.Presenters if (PageTransition != null && (from != null || to != null)) { - await PageTransition.Start((Visual)from, (Visual)to, fromIndex < toIndex); + await PageTransition.Start((Visual)from, (Visual)to, fromIndex < toIndex, default); } else if (to != null) { diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs index 9685ecbe91..c4dd79f468 100644 --- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs +++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; + using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Styling; @@ -22,7 +24,9 @@ namespace Avalonia.ReactiveUI /// public static readonly StyledProperty DefaultContentProperty = AvaloniaProperty.Register(nameof(DefaultContent)); - + + private CancellationTokenSource? _lastTransitionCts; + /// /// Gets or sets the animation played when content appears and disappears. /// @@ -62,11 +66,14 @@ namespace Avalonia.ReactiveUI /// New content to set. private async void UpdateContentWithTransition(object? content) { + _lastTransitionCts?.Cancel(); + _lastTransitionCts = new CancellationTokenSource(); + if (PageTransition != null) - await PageTransition.Start(this, null, true); + await PageTransition.Start(this, null, true, _lastTransitionCts.Token); base.Content = content; if (PageTransition != null) - await PageTransition.Start(null, this, true); + await PageTransition.Start(null, this, true, _lastTransitionCts.Token); } } } From 9da802d4845a8507b586926fb7ca4e65c1d84ce2 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 5 Jun 2021 22:38:19 -0400 Subject: [PATCH 10/84] Do not run animation if it was cancelled --- src/Avalonia.Animation/Animation.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index a170456854..eb48fd7b16 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -327,6 +327,11 @@ namespace Avalonia.Animation /// public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default) { + if (cancellationToken.IsCancellationRequested) + { + return Task.CompletedTask; + } + var run = new TaskCompletionSource(); if (this.IterationCount == IterationCount.Infinite) From a6cfe88c61bf971ce20fc6c9dabde5b3553a049e Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 23:40:40 +0200 Subject: [PATCH 11/84] Change ImmutableSolidColorBrush to a class. --- .../Media/Immutable/ImmutableSolidColorBrush.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs index 010184ad3b..8e93ac580e 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable /// /// Fills an area with a solid color. /// - public readonly struct ImmutableSolidColorBrush : ISolidColorBrush, IEquatable + public class ImmutableSolidColorBrush : ISolidColorBrush, IEquatable { /// /// Initializes a new instance of the class. @@ -48,8 +48,9 @@ namespace Avalonia.Media.Immutable public bool Equals(ImmutableSolidColorBrush other) { - // ReSharper disable once CompareOfFloatsByEqualityOperator - return Color == other.Color && Opacity == other.Opacity; + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Color.Equals(other.Color) && Opacity.Equals(other.Opacity); } public override bool Equals(object obj) @@ -67,12 +68,12 @@ namespace Avalonia.Media.Immutable public static bool operator ==(ImmutableSolidColorBrush left, ImmutableSolidColorBrush right) { - return left.Equals(right); + return Equals(left, right); } public static bool operator !=(ImmutableSolidColorBrush left, ImmutableSolidColorBrush right) { - return !left.Equals(right); + return !Equals(left, right); } /// From f9103e2c9561b614b3c94a71839c80f0ae8d4416 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 23:41:13 +0200 Subject: [PATCH 12/84] Parse brushes as immutable by default. --- src/Avalonia.Visuals/Media/Brush.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index fb03d19a4e..cf7f5f531c 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel; using Avalonia.Animation; using Avalonia.Animation.Animators; +using Avalonia.Media.Immutable; namespace Avalonia.Media { @@ -47,7 +48,7 @@ namespace Avalonia.Media if (s[0] == '#') { - return new SolidColorBrush(Color.Parse(s)); + return new ImmutableSolidColorBrush(Color.Parse(s)); } var brush = KnownColors.GetKnownBrush(s); From e742f81a7d2b1722d301132ce452a127a70e7af5 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 23:41:34 +0200 Subject: [PATCH 13/84] Parse brushes during compile time. --- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 14 ++++++++++++++ .../Transformers/AvaloniaXamlIlWellKnownTypes.cs | 9 ++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 059b5650cb..4592b9c8b4 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -207,6 +207,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return true; } + if (types.IBrush.IsAssignableFrom(type)) + { + if (Color.TryParse(text, out Color color)) + { + var brushTypeRef = new XamlAstClrTypeReference(node, types.ImmutableSolidColorBrush, false); + + result = new XamlAstNewClrObjectNode(node, brushTypeRef, + types.ImmutableSolidColorBrushConstructorColor, + new List { new XamlConstantNode(node, types.UInt, color.ToUint32()) }); + + return true; + } + } + result = null; return false; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index c4995b2de3..6dd3521183 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -80,7 +80,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType ColumnDefinitions { get; } public IXamlType Classes { get; } public IXamlMethod ClassesBindMethod { get; } - public IXamlProperty StyledElementClassesProperty { get; set; } + public IXamlProperty StyledElementClassesProperty { get; } + public IXamlType IBrush { get; } + public IXamlType ImmutableSolidColorBrush { get; } + public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -178,6 +181,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers .FindMethod( "BindClass", IDisposable, false, IStyledElement, cfg.WellKnownTypes.String, IBinding, cfg.WellKnownTypes.Object); + + IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush"); + ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush"); + ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List { UInt }); } } From 9feb2376a0b2e11ca3d1ffa4eeade4fa8034f161 Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Tue, 15 Jun 2021 20:37:48 +0100 Subject: [PATCH 14/84] Squash toplevel-totalsize --- native/Avalonia.Native/src/OSX/window.mm | 15 +++++++++ .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 ++ src/Avalonia.Controls/ApiCompatBaseline.txt | 4 ++- .../Offscreen/OffscreenTopLevelImpl.cs | 10 ++++++ .../Platform/ITopLevelImpl.cs | 5 +++ src/Avalonia.Controls/TopLevel.cs | 19 +++++++++++- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 1 + src/Avalonia.Headless/HeadlessWindowImpl.cs | 1 + src/Avalonia.Native/WindowImplBase.cs | 14 +++++++++ src/Avalonia.Native/avn.idl | 1 + src/Avalonia.X11/X11Structs.cs | 10 ++++++ src/Avalonia.X11/X11Window.cs | 31 +++++++++++++++++++ .../FramebufferToplevelImpl.cs | 1 + .../Wpf/WpfTopLevelImpl.cs | 1 + .../Interop/UnmanagedMethods.cs | 23 ++++++++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 10 ++++++ src/iOS/Avalonia.iOS/AvaloniaView.cs | 1 + .../MockWindowingPlatform.cs | 1 + 18 files changed, 148 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 870345e543..6bd09fe08b 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -202,6 +202,21 @@ public: } } + virtual HRESULT GetTotalSize(AvnSize* ret) override + { + @autoreleasepool + { + if(ret == nullptr) + return E_POINTER; + + auto frame = [Window frame]; + ret->Width = frame.size.width; + ret->Height = frame.size.height; + + return S_OK; + } + } + virtual HRESULT GetScaling (double* ret) override { @autoreleasepool diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 60b772a183..8b732a92da 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -55,6 +55,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform public virtual Size ClientSize => Size.ToSize(RenderScaling); + public Size TotalSize => ClientSize; + public IMouseDevice MouseDevice { get; } = new MouseDevice(); public Action Closed { get; set; } diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index a79b3b4d7b..166a005e4d 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -11,4 +11,6 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 12 +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.ITopLevelImpl.TotalSize' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.ITopLevelImpl.TotalSize.get()' is present in the implementation but not in the contract. +Total Issues: 14 diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index ca0e9d48b8..cb0c5d94e5 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -35,6 +35,16 @@ namespace Avalonia.Controls.Embedding.Offscreen } } + public Size TotalSize + { + get { return _clientSize; } + set + { + _clientSize = value; + Resized?.Invoke(value); + } + } + public double RenderScaling { get { return _scaling; } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 09f38042a1..546184f8a6 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -22,6 +22,11 @@ namespace Avalonia.Platform /// Size ClientSize { get; } + /// + /// Gets the total size of the toplevel, excluding shadows. + /// + Size TotalSize { get; } + /// /// Gets the scaling factor for the toplevel. This is used for rendering. /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 7a92836ddf..e2d9e7e697 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -42,6 +42,12 @@ namespace Avalonia.Controls public static readonly DirectProperty ClientSizeProperty = AvaloniaProperty.RegisterDirect(nameof(ClientSize), o => o.ClientSize); + /// + /// Defines the property. + /// + public static readonly DirectProperty TotalSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(TotalSize), o => o.TotalSize); + /// /// Defines the property. /// @@ -74,6 +80,7 @@ namespace Avalonia.Controls private readonly IPlatformRenderInterface _renderInterface; private readonly IGlobalStyles _globalStyles; private Size _clientSize; + private Size _totalSize; private WindowTransparencyLevel _actualTransparencyLevel; private ILayoutManager _layoutManager; private Border _transparencyFallbackBorder; @@ -84,6 +91,7 @@ namespace Avalonia.Controls static TopLevel() { AffectsMeasure(ClientSizeProperty); + AffectsMeasure(TotalSizeProperty); TransparencyLevelHintProperty.Changed.AddClassHandler( (tl, e) => @@ -194,9 +202,18 @@ namespace Avalonia.Controls public Size ClientSize { get { return _clientSize; } - protected set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); } + protected set + { + SetAndRaise(ClientSizeProperty, ref _clientSize, value); + SetAndRaise(TotalSizeProperty, ref _totalSize, PlatformImpl.TotalSize); + } } + /// + /// Gets or sets the total size of the window. + /// + public Size TotalSize => _totalSize; + /// /// Gets or sets the that the TopLevel should use when possible. /// diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index eedfc52d9d..91bc93d897 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -21,6 +21,7 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public Size MaxAutoSizeHint { get; } public Size ClientSize { get; } + public Size TotalSize => ClientSize; public double RenderScaling { get; } = 1.0; public double DesktopScaling => 1.0; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index af522f3e36..74f8c95ade 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -41,6 +41,7 @@ namespace Avalonia.Headless } public Size ClientSize { get; set; } + public Size TotalSize => ClientSize; public double RenderScaling { get; } = 1; public double DesktopScaling => RenderScaling; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f716464d14..18efd1a519 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -104,6 +104,20 @@ namespace Avalonia.Native } } + public Size TotalSize + { + get + { + if (_native != null) + { + var s = _native.TotalSize; + return new Size(s.Width, s.Height); + } + + return default; + } + } + public IEnumerable Surfaces => new[] { (_gpu ? _glSurface : (object)null), this diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index adcbeb2d3a..aeb456c3ea 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -435,6 +435,7 @@ interface IAvnWindowBase : IUnknown HRESULT Close(); HRESULT Activate(); HRESULT GetClientSize(AvnSize*ret); + HRESULT GetTotalSize(AvnSize*ret); HRESULT GetScaling(double*ret); HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); HRESULT Resize(double width, double height); diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index b1006b43ee..604fbf2b24 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -1740,6 +1740,16 @@ namespace Avalonia.X11 { public short W; public short H; } + + [StructLayout (LayoutKind.Sequential)] + [Serializable] + internal struct XFrameExtents + { + public int Left; + public int Right; + public int Top; + public int Bottom; + } [StructLayout (LayoutKind.Sequential)] diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 5ac4c4c9d0..c0a38eee95 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -297,6 +297,30 @@ namespace Avalonia.X11 public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling); + public Size TotalSize + { + get + { + XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_FRAME_EXTENTS, IntPtr.Zero, + new IntPtr(4), false, (IntPtr)Atom.AnyPropertyType, out var _, + out var _, out var nitems, out var _, out var prop); + + if (nitems.ToInt64() != 4) + { + // Window hasn't been mapped by the WM yet, so can't get the extents. + return ClientSize; + } + + var data = (IntPtr*)prop.ToPointer(); + var extents = new Thickness(data[0].ToInt32(), data[2].ToInt32(), data[1].ToInt32(), data[3].ToInt32()); + XFree(prop); + + return new Size( + (_realSize.Width + extents.Left + extents.Right) / RenderScaling, + (_realSize.Height + extents.Top + extents.Bottom) / RenderScaling); + } + } + public double RenderScaling { get @@ -589,6 +613,13 @@ namespace Avalonia.X11 private void OnPropertyChange(IntPtr atom, bool hasValue) { + if (atom == _x11.Atoms._NET_FRAME_EXTENTS) + { + // Occurs once the window has been mapped, which is the earliest the extents + // can be retrieved, so invoke event to force update of TopLevel.TotalSize. + Resized.Invoke(ClientSize); + } + if (atom == _x11.Atoms._NET_WM_STATE) { WindowState state = WindowState.Normal; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 4bbb58e53e..7f231aee17 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -62,6 +62,7 @@ namespace Avalonia.LinuxFramebuffer } public Size ClientSize => ScaledSize; + public Size TotalSize => ClientSize; public IMouseDevice MouseDevice => new MouseDevice(); public IPopupImpl CreatePopup() => null; diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index fe1d625efb..eedf99b33a 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -100,6 +100,7 @@ namespace Avalonia.Win32.Interop.Wpf } Size ITopLevelImpl.ClientSize => _finalSize; + Size ITopLevelImpl.TotalSize => _finalSize; IMouseDevice ITopLevelImpl.MouseDevice => _mouse; double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c137926e4c..ad409810b8 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -745,6 +745,26 @@ namespace Avalonia.Win32.Interop WM_DISPATCH_WORK_ITEM = WM_USER, } + public enum DwmWindowAttribute : uint + { + DWMWA_NCRENDERING_ENABLED = 1, + DWMWA_NCRENDERING_POLICY, + DWMWA_TRANSITIONS_FORCEDISABLED, + DWMWA_ALLOW_NCPAINT, + DWMWA_CAPTION_BUTTON_BOUNDS, + DWMWA_NONCLIENT_RTL_LAYOUT, + DWMWA_FORCE_ICONIC_REPRESENTATION, + DWMWA_FLIP3D_POLICY, + DWMWA_EXTENDED_FRAME_BOUNDS, + DWMWA_HAS_ICONIC_BITMAP, + DWMWA_DISALLOW_PEEK, + DWMWA_EXCLUDED_FROM_PEEK, + DWMWA_CLOAK, + DWMWA_CLOAKED, + DWMWA_FREEZE_REPRESENTATION, + DWMWA_LAST + }; + public enum MapVirtualKeyMapTypes : uint { MAPVK_VK_TO_VSC = 0x00, @@ -1388,6 +1408,9 @@ namespace Avalonia.Win32.Interop [DllImport("dwmapi.dll")] public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); + [DllImport("dwmapi.dll")] + public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out RECT pvAttribute, int cbAttribute); + [DllImport("dwmapi.dll")] public static extern int DwmIsCompositionEnabled(out bool enabled); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3a3342fd14..bb3f78715d 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -213,6 +213,16 @@ namespace Avalonia.Win32 } } + public Size TotalSize + { + get + { + DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf(typeof(RECT))); + + return new Size(rect.Width, rect.Height) / RenderScaling; + } + } + public IScreenImpl Screen { get; } public IPlatformHandle Handle { get; private set; } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 36a70ea410..ecd2a46b12 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -91,6 +91,7 @@ namespace Avalonia.iOS } public Size ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height); + public Size TotalSize => ClientSize; public double RenderScaling => _view.ContentScaleFactor; public IEnumerable Surfaces { get; set; } public Action Input { get; set; } diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 8a24a8366f..51c202f6bd 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -29,6 +29,7 @@ namespace Avalonia.UnitTests windowImpl.SetupAllProperties(); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); + windowImpl.Setup(x => x.TotalSize).Returns(() => clientSize); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); From d77d1fd5dab36976232bfd9a565a9c2d7cc956cf Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Tue, 15 Jun 2021 21:51:43 +0100 Subject: [PATCH 15/84] Rename to FrameSize --- native/Avalonia.Native/src/OSX/window.mm | 2 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- src/Avalonia.Controls/ApiCompatBaseline.txt | 4 +-- .../Offscreen/OffscreenTopLevelImpl.cs | 10 +------- .../Platform/ITopLevelImpl.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 25 +++++++++++-------- src/Avalonia.Controls/Window.cs | 1 + src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Headless/HeadlessWindowImpl.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 4 +-- src/Avalonia.Native/avn.idl | 2 +- src/Avalonia.X11/X11Window.cs | 4 +-- .../FramebufferToplevelImpl.cs | 2 +- .../Wpf/WpfTopLevelImpl.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- src/iOS/Avalonia.iOS/AvaloniaView.cs | 2 +- .../MockWindowingPlatform.cs | 2 +- 17 files changed, 33 insertions(+), 37 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 6bd09fe08b..76eca330a9 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -202,7 +202,7 @@ public: } } - virtual HRESULT GetTotalSize(AvnSize* ret) override + virtual HRESULT GetFrameSize(AvnSize* ret) override { @autoreleasepool { diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 8b732a92da..1e1e7066d4 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -55,7 +55,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public virtual Size ClientSize => Size.ToSize(RenderScaling); - public Size TotalSize => ClientSize; + public Size FrameSize => ClientSize; public IMouseDevice MouseDevice { get; } = new MouseDevice(); diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 166a005e4d..f81506252a 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -11,6 +11,6 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.ITopLevelImpl.TotalSize' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.ITopLevelImpl.TotalSize.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract. Total Issues: 14 diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index cb0c5d94e5..9242264470 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -35,15 +35,7 @@ namespace Avalonia.Controls.Embedding.Offscreen } } - public Size TotalSize - { - get { return _clientSize; } - set - { - _clientSize = value; - Resized?.Invoke(value); - } - } + public Size FrameSize => _clientSize; public double RenderScaling { diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 546184f8a6..d9fb22422a 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -25,7 +25,7 @@ namespace Avalonia.Platform /// /// Gets the total size of the toplevel, excluding shadows. /// - Size TotalSize { get; } + Size FrameSize { get; } /// /// Gets the scaling factor for the toplevel. This is used for rendering. diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index e2d9e7e697..65bd2b8d2f 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -43,10 +43,10 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(ClientSize), o => o.ClientSize); /// - /// Defines the property. + /// Defines the property. /// - public static readonly DirectProperty TotalSizeProperty = - AvaloniaProperty.RegisterDirect(nameof(TotalSize), o => o.TotalSize); + public static readonly DirectProperty FrameSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(FrameSize), o => o.FrameSize); /// /// Defines the property. @@ -80,7 +80,7 @@ namespace Avalonia.Controls private readonly IPlatformRenderInterface _renderInterface; private readonly IGlobalStyles _globalStyles; private Size _clientSize; - private Size _totalSize; + private Size _frameSize; private WindowTransparencyLevel _actualTransparencyLevel; private ILayoutManager _layoutManager; private Border _transparencyFallbackBorder; @@ -91,7 +91,6 @@ namespace Avalonia.Controls static TopLevel() { AffectsMeasure(ClientSizeProperty); - AffectsMeasure(TotalSizeProperty); TransparencyLevelHintProperty.Changed.AddClassHandler( (tl, e) => @@ -202,17 +201,13 @@ namespace Avalonia.Controls public Size ClientSize { get { return _clientSize; } - protected set - { - SetAndRaise(ClientSizeProperty, ref _clientSize, value); - SetAndRaise(TotalSizeProperty, ref _totalSize, PlatformImpl.TotalSize); - } + protected set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); } } /// /// Gets or sets the total size of the window. /// - public Size TotalSize => _totalSize; + public Size FrameSize => _frameSize; /// /// Gets or sets the that the TopLevel should use when possible. @@ -462,6 +457,14 @@ namespace Avalonia.Controls /// The event args. protected virtual void OnClosed(EventArgs e) => Closed?.Invoke(this, e); + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + if (change.Property == ClientSizeProperty) + { + SetAndRaise(FrameSizeProperty, ref _frameSize, PlatformImpl.FrameSize); + } + } + /// /// Tries to get a service from an , logging a /// warning if not found. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 700c3d9bad..cc8e27c3ec 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -950,6 +950,7 @@ namespace Avalonia.Controls protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { + base.OnPropertyChanged(change); if (change.Property == SystemDecorationsProperty) { var typedNewValue = change.NewValue.GetValueOrDefault(); diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 91bc93d897..aaaaedd2a6 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -21,7 +21,7 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public Size MaxAutoSizeHint { get; } public Size ClientSize { get; } - public Size TotalSize => ClientSize; + public Size FrameSize => ClientSize; public double RenderScaling { get; } = 1.0; public double DesktopScaling => 1.0; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 74f8c95ade..b1d53bc599 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -41,7 +41,7 @@ namespace Avalonia.Headless } public Size ClientSize { get; set; } - public Size TotalSize => ClientSize; + public Size FrameSize => ClientSize; public double RenderScaling { get; } = 1; public double DesktopScaling => RenderScaling; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 18efd1a519..f37c72cab4 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -104,13 +104,13 @@ namespace Avalonia.Native } } - public Size TotalSize + public Size FrameSize { get { if (_native != null) { - var s = _native.TotalSize; + var s = _native.FrameSize; return new Size(s.Width, s.Height); } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index aeb456c3ea..89e20463d8 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -435,7 +435,7 @@ interface IAvnWindowBase : IUnknown HRESULT Close(); HRESULT Activate(); HRESULT GetClientSize(AvnSize*ret); - HRESULT GetTotalSize(AvnSize*ret); + HRESULT GetFrameSize(AvnSize*ret); HRESULT GetScaling(double*ret); HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); HRESULT Resize(double width, double height); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index c0a38eee95..8493238b68 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -297,7 +297,7 @@ namespace Avalonia.X11 public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling); - public Size TotalSize + public Size FrameSize { get { @@ -616,7 +616,7 @@ namespace Avalonia.X11 if (atom == _x11.Atoms._NET_FRAME_EXTENTS) { // Occurs once the window has been mapped, which is the earliest the extents - // can be retrieved, so invoke event to force update of TopLevel.TotalSize. + // can be retrieved, so invoke event to force update of TopLevel.FrameSize. Resized.Invoke(ClientSize); } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 7f231aee17..19607ec47e 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -62,7 +62,7 @@ namespace Avalonia.LinuxFramebuffer } public Size ClientSize => ScaledSize; - public Size TotalSize => ClientSize; + public Size FrameSize => ClientSize; public IMouseDevice MouseDevice => new MouseDevice(); public IPopupImpl CreatePopup() => null; diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index eedf99b33a..f78328e36c 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -100,7 +100,7 @@ namespace Avalonia.Win32.Interop.Wpf } Size ITopLevelImpl.ClientSize => _finalSize; - Size ITopLevelImpl.TotalSize => _finalSize; + Size ITopLevelImpl.FrameSize => _finalSize; IMouseDevice ITopLevelImpl.MouseDevice => _mouse; double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bb3f78715d..6e105d2152 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -213,7 +213,7 @@ namespace Avalonia.Win32 } } - public Size TotalSize + public Size FrameSize { get { diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index ecd2a46b12..f11cd1983b 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -91,7 +91,7 @@ namespace Avalonia.iOS } public Size ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height); - public Size TotalSize => ClientSize; + public Size FrameSize => ClientSize; public double RenderScaling => _view.ContentScaleFactor; public IEnumerable Surfaces { get; set; } public Action Input { get; set; } diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 51c202f6bd..8db8f16784 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -29,7 +29,7 @@ namespace Avalonia.UnitTests windowImpl.SetupAllProperties(); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); - windowImpl.Setup(x => x.TotalSize).Returns(() => clientSize); + windowImpl.Setup(x => x.FrameSize).Returns(() => clientSize); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); From e65241992797ac596e782a56eb02ffd3ac8ada48 Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Tue, 15 Jun 2021 22:09:35 +0100 Subject: [PATCH 16/84] Fix regression --- src/Avalonia.Controls/TopLevel.cs | 16 +++++++--------- src/Avalonia.Controls/Window.cs | 1 - src/Avalonia.Controls/WindowBase.cs | 1 + 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 65bd2b8d2f..bbd551ace3 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -168,6 +168,7 @@ namespace Avalonia.Controls styler?.ApplyStyles(this); ClientSize = impl.ClientSize; + FrameSize = impl.FrameSize; this.GetObservable(PointerOverElementProperty) .Select( @@ -207,7 +208,11 @@ namespace Avalonia.Controls /// /// Gets or sets the total size of the window. /// - public Size FrameSize => _frameSize; + public Size FrameSize + { + get { return _frameSize; } + protected set { SetAndRaise(FrameSizeProperty, ref _frameSize, value); } + } /// /// Gets or sets the that the TopLevel should use when possible. @@ -378,6 +383,7 @@ namespace Avalonia.Controls protected virtual void HandleResized(Size clientSize) { ClientSize = clientSize; + FrameSize = PlatformImpl.FrameSize; Width = clientSize.Width; Height = clientSize.Height; LayoutManager.ExecuteLayoutPass(); @@ -457,14 +463,6 @@ namespace Avalonia.Controls /// The event args. protected virtual void OnClosed(EventArgs e) => Closed?.Invoke(this, e); - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - if (change.Property == ClientSizeProperty) - { - SetAndRaise(FrameSizeProperty, ref _frameSize, PlatformImpl.FrameSize); - } - } - /// /// Tries to get a service from an , logging a /// warning if not found. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index cc8e27c3ec..700c3d9bad 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -950,7 +950,6 @@ namespace Avalonia.Controls protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.OnPropertyChanged(change); if (change.Property == SystemDecorationsProperty) { var typedNewValue = change.NewValue.GetValueOrDefault(); diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index cdcb499e98..2b31cef8bd 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -222,6 +222,7 @@ namespace Avalonia.Controls protected override void HandleResized(Size clientSize) { ClientSize = clientSize; + FrameSize = PlatformImpl.FrameSize; LayoutManager.ExecuteLayoutPass(); Renderer?.Resized(clientSize); } From 3c911839005ab0b56561aae39a303353bf812397 Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Wed, 16 Jun 2021 13:39:23 +0100 Subject: [PATCH 17/84] Fix when dwm disabled --- src/Windows/Avalonia.Win32/WindowImpl.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 6e105d2152..645cb5fb99 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -217,8 +217,13 @@ namespace Avalonia.Win32 { get { - DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf(typeof(RECT))); + if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled) + { + GetWindowRect(_hwnd, out var rcWindow); + return new Size(rcWindow.Width, rcWindow.Height) / RenderScaling; + } + DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf(typeof(RECT))); return new Size(rect.Width, rect.Height) / RenderScaling; } } From d5f1365de911adc52bebc1433f05b49df8b49e0c Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Wed, 16 Jun 2021 15:21:28 +0100 Subject: [PATCH 18/84] Revert unused addition --- src/Avalonia.X11/X11Structs.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 604fbf2b24..b1006b43ee 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -1740,16 +1740,6 @@ namespace Avalonia.X11 { public short W; public short H; } - - [StructLayout (LayoutKind.Sequential)] - [Serializable] - internal struct XFrameExtents - { - public int Left; - public int Right; - public int Top; - public int Bottom; - } [StructLayout (LayoutKind.Sequential)] From bccbf0570c9891d8b46db6105e394dcf630c86a7 Mon Sep 17 00:00:00 2001 From: Nathan Garside Date: Thu, 17 Jun 2021 11:06:32 +0100 Subject: [PATCH 19/84] Make FrameSize nullable --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- src/Avalonia.Controls/ApiCompatBaseline.txt | 4 ++-- .../Embedding/Offscreen/OffscreenTopLevelImpl.cs | 2 +- src/Avalonia.Controls/Platform/ITopLevelImpl.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 8 ++++---- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Headless/HeadlessWindowImpl.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 2 +- src/Avalonia.X11/X11Window.cs | 4 ++-- .../Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs | 2 +- src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- src/iOS/Avalonia.iOS/AvaloniaView.cs | 2 +- tests/Avalonia.UnitTests/MockWindowingPlatform.cs | 1 - 14 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 1e1e7066d4..a72742580c 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -55,7 +55,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public virtual Size ClientSize => Size.ToSize(RenderScaling); - public Size FrameSize => ClientSize; + public Size? FrameSize => null; public IMouseDevice MouseDevice { get; } = new MouseDevice(); diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index f81506252a..443b085aa4 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -11,6 +11,6 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract. Total Issues: 14 diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 9242264470..83470f161d 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -35,7 +35,7 @@ namespace Avalonia.Controls.Embedding.Offscreen } } - public Size FrameSize => _clientSize; + public Size? FrameSize => null; public double RenderScaling { diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index d9fb22422a..6e53233898 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -25,7 +25,7 @@ namespace Avalonia.Platform /// /// Gets the total size of the toplevel, excluding shadows. /// - Size FrameSize { get; } + Size? FrameSize { get; } /// /// Gets the scaling factor for the toplevel. This is used for rendering. diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index bbd551ace3..7028dca769 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -45,8 +45,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly DirectProperty FrameSizeProperty = - AvaloniaProperty.RegisterDirect(nameof(FrameSize), o => o.FrameSize); + public static readonly DirectProperty FrameSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(FrameSize), o => o.FrameSize); /// /// Defines the property. @@ -80,7 +80,7 @@ namespace Avalonia.Controls private readonly IPlatformRenderInterface _renderInterface; private readonly IGlobalStyles _globalStyles; private Size _clientSize; - private Size _frameSize; + private Size? _frameSize; private WindowTransparencyLevel _actualTransparencyLevel; private ILayoutManager _layoutManager; private Border _transparencyFallbackBorder; @@ -208,7 +208,7 @@ namespace Avalonia.Controls /// /// Gets or sets the total size of the window. /// - public Size FrameSize + public Size? FrameSize { get { return _frameSize; } protected set { SetAndRaise(FrameSizeProperty, ref _frameSize, value); } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index aaaaedd2a6..c8203686f9 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -21,7 +21,7 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public Size MaxAutoSizeHint { get; } public Size ClientSize { get; } - public Size FrameSize => ClientSize; + public Size? FrameSize => null; public double RenderScaling { get; } = 1.0; public double DesktopScaling => 1.0; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index b1d53bc599..7f4b9face4 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -41,7 +41,7 @@ namespace Avalonia.Headless } public Size ClientSize { get; set; } - public Size FrameSize => ClientSize; + public Size? FrameSize => null; public double RenderScaling { get; } = 1; public double DesktopScaling => RenderScaling; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f37c72cab4..ced9cea3a8 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -104,7 +104,7 @@ namespace Avalonia.Native } } - public Size FrameSize + public Size? FrameSize { get { diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 8493238b68..3425da5deb 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -297,7 +297,7 @@ namespace Avalonia.X11 public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling); - public Size FrameSize + public Size? FrameSize { get { @@ -308,7 +308,7 @@ namespace Avalonia.X11 if (nitems.ToInt64() != 4) { // Window hasn't been mapped by the WM yet, so can't get the extents. - return ClientSize; + return null; } var data = (IntPtr*)prop.ToPointer(); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 19607ec47e..73b1b87ce9 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -62,7 +62,7 @@ namespace Avalonia.LinuxFramebuffer } public Size ClientSize => ScaledSize; - public Size FrameSize => ClientSize; + public Size? FrameSize => null; public IMouseDevice MouseDevice => new MouseDevice(); public IPopupImpl CreatePopup() => null; diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index f78328e36c..73e46b9e13 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -100,7 +100,7 @@ namespace Avalonia.Win32.Interop.Wpf } Size ITopLevelImpl.ClientSize => _finalSize; - Size ITopLevelImpl.FrameSize => _finalSize; + Size? ITopLevelImpl.FrameSize => null; IMouseDevice ITopLevelImpl.MouseDevice => _mouse; double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 645cb5fb99..646a6f5739 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -213,7 +213,7 @@ namespace Avalonia.Win32 } } - public Size FrameSize + public Size? FrameSize { get { diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index f11cd1983b..0371a7759a 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -91,7 +91,7 @@ namespace Avalonia.iOS } public Size ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height); - public Size FrameSize => ClientSize; + public Size? FrameSize => null; public double RenderScaling => _view.ContentScaleFactor; public IEnumerable Surfaces { get; set; } public Action Input { get; set; } diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 8db8f16784..8a24a8366f 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -29,7 +29,6 @@ namespace Avalonia.UnitTests windowImpl.SetupAllProperties(); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); - windowImpl.Setup(x => x.FrameSize).Returns(() => clientSize); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); From eb3933b636bea07a7a06e1e9994738f590d319cf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 17 Jun 2021 15:56:39 +0100 Subject: [PATCH 20/84] fix order of operations, set Offset before setting Extent --- src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index b0b52812b9..e3783febdd 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -511,8 +511,8 @@ namespace Avalonia.Controls.Presenters else if (scrollable.IsLogicalScrollEnabled) { Viewport = scrollable.Viewport; - Extent = scrollable.Extent; Offset = scrollable.Offset; + Extent = scrollable.Extent; } } From 955fb1ffdb75e4f77f4e55c1bfdf2d883435c4e9 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 15 Jun 2021 19:01:16 +0200 Subject: [PATCH 21/84] fixes: Warnings CS0169 --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 3 --- src/Avalonia.Controls/Menu.cs | 1 - src/Avalonia.Controls/Repeater/ViewportManager.cs | 1 - src/Avalonia.Controls/Repeater/VirtualizationInfo.cs | 1 - src/Avalonia.X11/TransparencyHelper.cs | 1 - src/Avalonia.X11/X11Window.cs | 1 - src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs | 1 - .../Avalonia.LinuxFramebuffer/Input/EvDev/EvDevTouchScreen.cs | 1 - src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs | 1 - .../WinRT/Composition/WinUICompositorConnection.cs | 1 - 10 files changed, 12 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 1b4632d368..83f13fe199 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -75,7 +75,6 @@ namespace Avalonia.Controls private const double DATAGRID_defaultMinColumnWidth = 20; private const double DATAGRID_defaultMaxColumnWidth = double.PositiveInfinity; - private List _validationErrors; private List _bindingValidationErrors; private IDisposable _validationSubscription; @@ -102,7 +101,6 @@ namespace Avalonia.Controls private bool _areHandlersSuspended; private bool _autoSizingColumns; private IndexToValueTable _collapsedSlotsTable; - private DataGridCellCoordinates _currentCellCoordinates; private Control _clickedElement; // used to store the current column during a Reset @@ -141,7 +139,6 @@ namespace Avalonia.Controls private DataGridSelectedItemsCollection _selectedItems; private bool _temporarilyResetCurrentCell; private object _uneditedValue; // Represents the original current cell value at the time it enters editing mode. - private ICellEditBinding _currentCellEditBinding; // An approximation of the sum of the heights in pixels of the scrolling rows preceding // the first displayed scrolling row. Since the scrolled off rows are discarded, the grid diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs index 4da044fec1..706be376a9 100644 --- a/src/Avalonia.Controls/Menu.cs +++ b/src/Avalonia.Controls/Menu.cs @@ -17,7 +17,6 @@ namespace Avalonia.Controls private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel { Orientation = Orientation.Horizontal }); - private LightDismissOverlayLayer? _overlay; /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index 6e24408aa9..da3c2b15e6 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -27,7 +27,6 @@ namespace Avalonia.Controls private IScrollAnchorProvider _scroller; private IControl _makeAnchorElement; private bool _isAnchorOutsideRealizedRange; - private Task _cacheBuildAction; private Rect _visibleWindow; private Rect _layoutExtent; // This is the expected shift by the layout. diff --git a/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs b/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs index f8cfde609e..7e6b24f1b5 100644 --- a/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs +++ b/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs @@ -27,7 +27,6 @@ namespace Avalonia.Controls internal class VirtualizationInfo { private int _pinCounter; - private object _data; public Rect ArrangeBounds { get; set; } public bool AutoRecycleCandidate { get; set; } diff --git a/src/Avalonia.X11/TransparencyHelper.cs b/src/Avalonia.X11/TransparencyHelper.cs index 0578680136..2140b61b6f 100644 --- a/src/Avalonia.X11/TransparencyHelper.cs +++ b/src/Avalonia.X11/TransparencyHelper.cs @@ -10,7 +10,6 @@ namespace Avalonia.X11 private readonly X11Globals _globals; private WindowTransparencyLevel _currentLevel; private WindowTransparencyLevel _requestedLevel; - private bool _isCompositing; private bool _blurAtomsAreSet; public Action TransparencyLevelChanged { get; set; } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 5ac4c4c9d0..37260aa78b 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -30,7 +30,6 @@ namespace Avalonia.X11 ITopLevelImplWithTextInputMethod { private readonly AvaloniaX11Platform _platform; - private readonly IWindowImpl _popupParent; private readonly bool _popup; private readonly X11Info _x11; private XConfigureEvent? _configure; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 4bbb58e53e..ac2fd40c54 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -15,7 +15,6 @@ namespace Avalonia.LinuxFramebuffer private readonly IOutputBackend _outputBackend; private readonly IInputBackend _inputBackend; - private bool _renderQueued; public IInputRoot InputRoot { get; private set; } public FramebufferToplevelImpl(IOutputBackend outputBackend, IInputBackend inputBackend) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevTouchScreen.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevTouchScreen.cs index b69b151c3b..c35a3d1174 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevTouchScreen.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevTouchScreen.cs @@ -7,7 +7,6 @@ namespace Avalonia.LinuxFramebuffer.Input.EvDev internal class EvDevSingleTouchScreen : EvDevDeviceHandler { private readonly IScreenInfoProvider _screenInfo; - private readonly int _width, _height; private readonly Matrix _calibration; private input_absinfo _axisX; private input_absinfo _axisY; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index dc44d2d55f..ee4125101c 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -16,7 +16,6 @@ namespace Avalonia.LinuxFramebuffer.Output public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface { private DrmCard _card; - private readonly EglGlPlatformSurface _eglPlatformSurface; public PixelSize PixelSize => _mode.Resolution; public double Scaling { get; set; } public IGlContext PrimaryContext => _deferredContext; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 2aa82436f6..1c3c959acf 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs @@ -17,7 +17,6 @@ namespace Avalonia.Win32.WinRT.Composition class WinUICompositorConnection : IRenderTimer { private readonly EglContext _syncContext; - private IntPtr _queue; private ICompositor _compositor; private ICompositor2 _compositor2; private ICompositor5 _compositor5; From 7d863e877f52323cff846d9c0b835c30d54787f9 Mon Sep 17 00:00:00 2001 From: amwx Date: Fri, 18 Jun 2021 21:22:53 -0500 Subject: [PATCH 22/84] Let Data and Stretch be overridden --- src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml index 2eee656dc6..60420f2f44 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml @@ -112,9 +112,7 @@ + VerticalAlignment="Center" /> + + From 82d2ee175f39f044b6cc7864899167eafb4712c9 Mon Sep 17 00:00:00 2001 From: Splitwirez Date: Sat, 19 Jun 2021 15:36:58 -0400 Subject: [PATCH 23/84] Add `Arc`...again... --- src/Avalonia.Controls/Shapes/Arc.cs | 99 +++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/Avalonia.Controls/Shapes/Arc.cs diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs new file mode 100644 index 0000000000..f6738e10f2 --- /dev/null +++ b/src/Avalonia.Controls/Shapes/Arc.cs @@ -0,0 +1,99 @@ +using System; +using Avalonia.Media; + +namespace Avalonia.Controls.Shapes +{ + public class Arc : Shape + { + /// + /// Defines the property. + /// + public static readonly StyledProperty StartAngleProperty = + AvaloniaProperty.Register(nameof(StartAngle), 0.0); + + /// + /// Defines the property. + /// + public static readonly StyledProperty SweepAngleProperty = + AvaloniaProperty.Register(nameof(SweepAngle), 0.0); + + static Arc() + { + StrokeThicknessProperty.OverrideDefaultValue(1); + AffectsGeometry(BoundsProperty, StrokeThicknessProperty, StartAngleProperty, SweepAngleProperty); + } + + /// + /// Gets or sets the angle at which the arc starts, in degrees. + /// + public double StartAngle + { + get { return GetValue(StartAngleProperty); } + set { SetValue(StartAngleProperty, value); } + } + + /// + /// Gets or sets the angle at which the arc ends relative to the start angle, in degrees. + /// + public double SweepAngle + { + get { return GetValue(SweepAngleProperty); } + set { SetValue(SweepAngleProperty, value); } + } + + protected override Geometry CreateDefiningGeometry() + { + double angle1 = DegreesToRad(StartAngle); + double angle2 = angle1 + DegreesToRad(SweepAngle); + + double startAngle = Math.Min(angle1, angle2); + double sweepAngle = Math.Max(angle1, angle2); + + double normStart = RadToNormRad(startAngle); + double normEnd = RadToNormRad(sweepAngle); + + var rect = new Rect(Bounds.Size); + + if ((normStart == normEnd) && (startAngle != sweepAngle)) //complete ring + { + return new EllipseGeometry(rect.Deflate(StrokeThickness / 2)); + } + else if (SweepAngle == 0) + { + return new StreamGeometry(); + } + else //partial arc + { + var deflatedRect = rect.Deflate(StrokeThickness / 2); + + double centerX = rect.Center.X; + double centerY = rect.Center.Y; + + double radiusX = deflatedRect.Width / 2; + double radiusY = deflatedRect.Height / 2; + + double angleGap = RadToNormRad(sweepAngle - startAngle); + + Point startPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, startAngle); + Point endPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, sweepAngle); + + StreamGeometry arcGeometry = new StreamGeometry(); + using (var ctx = arcGeometry.Open()) + { + ctx.BeginFigure(startPoint, false); + ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI, SweepDirection.Clockwise); + ctx.EndFigure(false); + } + return arcGeometry; + } + } + + static double DegreesToRad(double inAngle) => + inAngle * Math.PI / 180; + + static double RadToNormRad(double inAngle) => (0 + (inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); + + static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) => + new Point((radiusX * Math.Cos(angle)) + centerX, (radiusY * Math.Sin(angle)) + centerY); + } +} From a49ba4b0e346e9de534ae0e5e090c39b1ed1ca05 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 19 Jun 2021 19:59:15 -0400 Subject: [PATCH 24/84] Add tests --- src/Avalonia.Animation/Animation.cs | 13 +- src/Avalonia.Animation/IAnimation.cs | 2 +- .../AnimationIterationTests.cs | 195 ++++++++++++++++++ 3 files changed, 206 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index eb48fd7b16..b5f89c00ab 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -318,7 +318,9 @@ namespace Avalonia.Animation if (onComplete != null) { - Task.WhenAll(completionTasks).ContinueWith(_ => onComplete()); + Task.WhenAll(completionTasks).ContinueWith( + (_, state) => ((Action)state).Invoke(), + onComplete); } } return new CompositeDisposable(subscriptions); @@ -340,12 +342,17 @@ namespace Avalonia.Animation IDisposable subscriptions = null, cancellation = null; subscriptions = this.Apply(control, clock, Observable.Return(true), () => { - run.SetResult(null); + run.TrySetResult(null); subscriptions?.Dispose(); cancellation?.Dispose(); }); - cancellation = cancellationToken.Register(state => ((IDisposable)state).Dispose(), subscriptions); + cancellation = cancellationToken.Register(() => + { + run.TrySetResult(null); + subscriptions?.Dispose(); + cancellation?.Dispose(); + }); return run.Task; } diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index 5844ba5688..d037834630 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -17,6 +17,6 @@ namespace Avalonia.Animation /// /// Run the animation on the specified control. /// - Task RunAsync(Animatable control, IClock clock, CancellationToken cancellationToken); + Task RunAsync(Animatable control, IClock clock, CancellationToken cancellationToken = default); } } diff --git a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs index fe718ec32b..6ddc31ec1b 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs @@ -176,5 +176,200 @@ namespace Avalonia.Animation.UnitTests clock.Step(TimeSpan.FromSeconds(0.100d)); Assert.Equal(border.Width, 300d); } + + [Fact] + public void Do_Not_Run_Cancelled_Animation() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 200d), + }, + Cue = new Cue(1d) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 100d), + }, + Cue = new Cue(0d) + }; + + var animation = new Animation() + { + Duration = TimeSpan.FromSeconds(10), + Delay = TimeSpan.FromSeconds(0), + DelayBetweenIterations = TimeSpan.FromSeconds(0), + IterationCount = new IterationCount(1), + Children = + { + keyframe2, + keyframe1 + } + }; + + var border = new Border() + { + Height = 100d, + Width = 100d + }; + var propertyChangedCount = 0; + border.PropertyChanged += (sender, e) => + { + if (e.Property == Control.WidthProperty) + { + propertyChangedCount++; + } + }; + + var clock = new TestClock(); + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); + var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token); + + clock.Step(TimeSpan.FromSeconds(10)); + Assert.Equal(0, propertyChangedCount); + Assert.True(animationRun.IsCompleted); + } + + [Fact] + public void Cancellation_Should_Stop_Animation() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 200d), + }, + Cue = new Cue(1d) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 100d), + }, + Cue = new Cue(0d) + }; + + var animation = new Animation() + { + Duration = TimeSpan.FromSeconds(10), + Delay = TimeSpan.FromSeconds(0), + DelayBetweenIterations = TimeSpan.FromSeconds(0), + IterationCount = new IterationCount(1), + Children = + { + keyframe2, + keyframe1 + } + }; + + var border = new Border() + { + Height = 100d, + Width = 50d + }; + var propertyChangedCount = 0; + border.PropertyChanged += (sender, e) => + { + if (e.Property == Control.WidthProperty) + { + propertyChangedCount++; + } + }; + + var clock = new TestClock(); + var cancellationTokenSource = new CancellationTokenSource(); + var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token); + + Assert.Equal(0, propertyChangedCount); + + clock.Step(TimeSpan.FromSeconds(0)); + Assert.False(animationRun.IsCompleted); + Assert.Equal(1, propertyChangedCount); + + cancellationTokenSource.Cancel(); + clock.Step(TimeSpan.FromSeconds(1)); + clock.Step(TimeSpan.FromSeconds(2)); + clock.Step(TimeSpan.FromSeconds(3)); + //Assert.Equal(2, propertyChangedCount); + + animationRun.Wait(); + + clock.Step(TimeSpan.FromSeconds(6)); + Assert.True(animationRun.IsCompleted); + Assert.Equal(2, propertyChangedCount); + } + + [Fact] + public void Cancellation_Of_Completed_Animation_Does_Not_Fail() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 200d), + }, + Cue = new Cue(1d) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 100d), + }, + Cue = new Cue(0d) + }; + + var animation = new Animation() + { + Duration = TimeSpan.FromSeconds(10), + Delay = TimeSpan.FromSeconds(0), + DelayBetweenIterations = TimeSpan.FromSeconds(0), + IterationCount = new IterationCount(1), + Children = + { + keyframe2, + keyframe1 + } + }; + + var border = new Border() + { + Height = 100d, + Width = 50d + }; + var propertyChangedCount = 0; + border.PropertyChanged += (sender, e) => + { + if (e.Property == Control.WidthProperty) + { + propertyChangedCount++; + } + }; + + var clock = new TestClock(); + var cancellationTokenSource = new CancellationTokenSource(); + var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token); + + Assert.Equal(0, propertyChangedCount); + + clock.Step(TimeSpan.FromSeconds(0)); + Assert.False(animationRun.IsCompleted); + Assert.Equal(1, propertyChangedCount); + + clock.Step(TimeSpan.FromSeconds(10)); + Assert.True(animationRun.IsCompleted); + Assert.Equal(2, propertyChangedCount); + + cancellationTokenSource.Cancel(); + animationRun.Wait(); + } } } From 04d3ce168eafba7a4b45a66f2cabb8a7b2ea3e07 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 19 Jun 2021 20:01:08 -0400 Subject: [PATCH 25/84] Add failing test --- .../AnimationIterationTests.cs | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs index 6ddc31ec1b..60d4dddaf0 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs @@ -9,6 +9,8 @@ using Avalonia.UnitTests; using Avalonia.Data; using Xunit; using Avalonia.Animation.Easings; +using System.Threading; +using System.Reactive.Linq; namespace Avalonia.Animation.UnitTests { @@ -177,6 +179,77 @@ namespace Avalonia.Animation.UnitTests Assert.Equal(border.Width, 300d); } + [Fact] + public void Dispose_Subscription_Should_Stop_Animation() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 200d), + }, + Cue = new Cue(1d) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 100d), + }, + Cue = new Cue(0d) + }; + + var animation = new Animation() + { + Duration = TimeSpan.FromSeconds(10), + Delay = TimeSpan.FromSeconds(0), + DelayBetweenIterations = TimeSpan.FromSeconds(0), + IterationCount = new IterationCount(1), + Children = + { + keyframe2, + keyframe1 + } + }; + + var border = new Border() + { + Height = 100d, + Width = 50d + }; + var propertyChangedCount = 0; + var animationCompletedCount = 0; + border.PropertyChanged += (sender, e) => + { + if (e.Property == Control.WidthProperty) + { + propertyChangedCount++; + } + }; + + var clock = new TestClock(); + var disposable = animation.Apply(border, clock, Observable.Return(true), () => animationCompletedCount++); + + Assert.Equal(0, propertyChangedCount); + + clock.Step(TimeSpan.FromSeconds(0)); + Assert.Equal(0, animationCompletedCount); + Assert.Equal(1, propertyChangedCount); + + disposable.Dispose(); + + // Clock ticks should be ignored after Dispose + clock.Step(TimeSpan.FromSeconds(5)); + clock.Step(TimeSpan.FromSeconds(6)); + clock.Step(TimeSpan.FromSeconds(7)); + + // On animation disposing (cancellation) on completed is not invoked (is it expected?) + Assert.Equal(0, animationCompletedCount); + // Initial property changed before cancellation + animation value removal. + Assert.Equal(2, propertyChangedCount); + } + [Fact] public void Do_Not_Run_Cancelled_Animation() { From e44256b5dd28e9f7b1b8ad0cbb6c24eec5782be8 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 19 Jun 2021 20:29:30 -0400 Subject: [PATCH 26/84] Skip failing tests, issue created --- tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs index 60d4dddaf0..58bd7a42c3 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs @@ -179,7 +179,7 @@ namespace Avalonia.Animation.UnitTests Assert.Equal(border.Width, 300d); } - [Fact] + [Fact(Skip = "See #6111")] public void Dispose_Subscription_Should_Stop_Animation() { var keyframe1 = new KeyFrame() @@ -308,7 +308,7 @@ namespace Avalonia.Animation.UnitTests Assert.True(animationRun.IsCompleted); } - [Fact] + [Fact(Skip = "See #6111")] public void Cancellation_Should_Stop_Animation() { var keyframe1 = new KeyFrame() From 1739ed4138be8a1572600dd7b809b05359489fae Mon Sep 17 00:00:00 2001 From: aguahombre Date: Mon, 21 Jun 2021 19:42:00 +0100 Subject: [PATCH 27/84] Adds a pixel format parameter to Linux frame buffer platform setup. (#6101) * Add pixel format parameter to Linux frame buffer platform setup. Currently setup always changes the frame buffer pixel format to RGBA which results in the screen being cleared if the default pixel format is not RGBA (as on a Raspberry PI) . This clears any splash screen and leaves the screen blank for a period which is not a good UX. Now the frame buffer setup can select the correct pixel format or use null to leave the pixel format unchanged. * Remove unnecessary formatting changes. Add v0.10.x compatible constructor. * Keep old StartLinuxFbDev extension method for v0.10.x binary compatibility --- .../LinuxFramebufferPlatform.cs | 5 +- .../LockedFramebuffer.cs | 2 +- .../Output/FbdevOutput.cs | 88 ++++++++++++++----- 3 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index a6b70069c1..89f81a7649 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -132,7 +132,10 @@ public static class LinuxFramebufferPlatformExtensions { public static int StartLinuxFbDev(this T builder, string[] args, string fbdev = null, double scaling = 1) where T : AppBuilderBase, new() => - StartLinuxDirect(builder, args, new FbdevOutput(fbdev) {Scaling = scaling}); + StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }); + public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling) + where T : AppBuilderBase, new() => + StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }); public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) {Scaling = scaling}); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs b/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs index ed59166eb8..87c7b64c26 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs @@ -41,6 +41,6 @@ namespace Avalonia.LinuxFramebuffer public PixelSize Size => new PixelSize((int)_varInfo.xres, (int) _varInfo.yres); public int RowBytes => (int) _fixedInfo.line_length; public Vector Dpi { get; } - public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888; + public PixelFormat Format => _varInfo.bits_per_pixel == 16 ? PixelFormat.Rgb565 : _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs index b83fe6cbe8..add744ee16 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs @@ -16,16 +16,33 @@ namespace Avalonia.LinuxFramebuffer private IntPtr _mappedAddress; public double Scaling { get; set; } - public FbdevOutput(string fileName = null) + /// + /// Create a Linux frame buffer device output + /// + /// The frame buffer device name. + /// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set + public FbdevOutput(string fileName = null) : this(null, null) { - fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0"; + } + + /// + /// Create a Linux frame buffer device output + /// + /// The frame buffer device name. + /// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set + /// The required pixel format for the frame buffer. + /// A null value will leave the frame buffer in the current pixel format. + /// Otherwise sets the frame buffer to the required format + public FbdevOutput(string fileName, PixelFormat? format) + { + fileName ??= Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0"; _fd = NativeUnsafeMethods.open(fileName, 2, 0); if (_fd <= 0) throw new Exception("Error: " + Marshal.GetLastWin32Error()); try { - Init(); + Init(format); } catch { @@ -34,25 +51,28 @@ namespace Avalonia.LinuxFramebuffer } } - void Init() + void Init(PixelFormat? format) { fixed (void* pnfo = &_varInfo) { if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo)) throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); - SetBpp(); + if (format.HasValue) + { + SetBpp(format.Value); - if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo)) - _varInfo.transp = new fb_bitfield(); + if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo)) + _varInfo.transp = new fb_bitfield(); - NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo); + NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo); - if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo)) - throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); + if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo)) + throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); - if (_varInfo.bits_per_pixel != 32) - throw new Exception("Unable to set 32-bit display mode"); + if (_varInfo.bits_per_pixel != 32) + throw new Exception("Unable to set 32-bit display mode"); + } } fixed(void*pnfo = &_fixedInfo) if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_FSCREENINFO, pnfo)) @@ -70,17 +90,43 @@ namespace Avalonia.LinuxFramebuffer } } - void SetBpp() + void SetBpp(PixelFormat format) { - _varInfo.bits_per_pixel = 32; - _varInfo.grayscale = 0; - _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield + switch (format) { - length = 8 - }; - _varInfo.green.offset = 8; - _varInfo.blue.offset = 16; - _varInfo.transp.offset = 24; + case PixelFormat.Rgba8888: + _varInfo.bits_per_pixel = 32; + _varInfo.grayscale = 0; + _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield + { + length = 8 + }; + _varInfo.green.offset = 8; + _varInfo.blue.offset = 16; + _varInfo.transp.offset = 24; + break; + case PixelFormat.Bgra8888: + _varInfo.bits_per_pixel = 32; + _varInfo.grayscale = 0; + _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield + { + length = 8 + }; + _varInfo.green.offset = 8; + _varInfo.red.offset = 16; + _varInfo.transp.offset = 24; + break; + case PixelFormat.Rgb565: + _varInfo.bits_per_pixel = 16; + _varInfo.grayscale = 0; + _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield(); + _varInfo.red.length = 5; + _varInfo.green.offset = 5; + _varInfo.green.length = 6; + _varInfo.blue.offset = 11; + _varInfo.blue.length = 5; + break; + } } public string Id { get; private set; } From 49014f43b38bcc48690b21bf8d57275406b3186e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 24 Jun 2021 13:26:18 +0300 Subject: [PATCH 28/84] Update FbdevOutput.cs --- src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs index add744ee16..61f00b2795 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs @@ -21,7 +21,7 @@ namespace Avalonia.LinuxFramebuffer /// /// The frame buffer device name. /// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set - public FbdevOutput(string fileName = null) : this(null, null) + public FbdevOutput(string fileName = null) : this(fileName, null) { } From c81c97a19a2db066533100a303527113c578b0d2 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 24 Jun 2021 16:08:24 -0400 Subject: [PATCH 29/84] Use IsNullOrEmpty instead of IsNullOrWhiteSpace for :empty pseudoclass --- src/Avalonia.Controls/TextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 1bee15bccd..c1516613b3 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1290,7 +1290,7 @@ namespace Avalonia.Controls private void UpdatePseudoclasses() { - PseudoClasses.Set(":empty", string.IsNullOrWhiteSpace(Text)); + PseudoClasses.Set(":empty", string.IsNullOrEmpty(Text)); } private bool IsPasswordBox => PasswordChar != default(char); From b4e11b227d325871ff80c4b8bcbf8aad5ff3773c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 25 Jun 2021 10:09:15 +0200 Subject: [PATCH 30/84] fixes: code documentation --- src/Avalonia.Controls/Grid.cs | 1 + src/Avalonia.Controls/SplitView.cs | 2 +- src/Avalonia.Visuals/Animation/CrossFade.cs | 1 + .../Rendering/SceneGraph/BitmapBlendModeNode.cs | 4 ++-- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 2 +- src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs | 3 ++- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 2 +- 7 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index c7d598006d..229fa674f0 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -978,6 +978,7 @@ namespace Avalonia.Controls /// width is not registered in columns. /// Passed through to MeasureCell. /// When "true" cells' desired height is not registered in rows. + /// return true when desired size has ghanged private void MeasureCellsGroup( int cellsHead, Size referenceSize, diff --git a/src/Avalonia.Controls/SplitView.cs b/src/Avalonia.Controls/SplitView.cs index 4500d52484..0e35c610b2 100644 --- a/src/Avalonia.Controls/SplitView.cs +++ b/src/Avalonia.Controls/SplitView.cs @@ -133,7 +133,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(Pane)); /// - /// Defines the property. + /// Defines the property. /// public static readonly StyledProperty PaneTemplateProperty = AvaloniaProperty.Register(nameof(PaneTemplate)); diff --git a/src/Avalonia.Visuals/Animation/CrossFade.cs b/src/Avalonia.Visuals/Animation/CrossFade.cs index 9ff0d99b23..5eaa920b32 100644 --- a/src/Avalonia.Visuals/Animation/CrossFade.cs +++ b/src/Avalonia.Visuals/Animation/CrossFade.cs @@ -147,6 +147,7 @@ namespace Avalonia.Animation /// /// Unused for cross-fades. /// + /// allowed cancel transition /// /// A that tracks the progress of the animation. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs index 0a5c1f8db6..45b62b843b 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - /// Initializes a new instance of the class that represents an + /// Initializes a new instance of the class that represents an /// pop. /// public BitmapBlendModeNode() @@ -40,7 +40,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Determines if this draw operation equals another. /// - /// The opacity of the other draw operation. + /// the how to compare /// True if the draw operations are the same, otherwise false. /// /// The properties of the other draw operation are passed in as arguments to prevent diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index d8bd0607d1..2352b8b076 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -591,7 +591,7 @@ namespace Avalonia.Skia /// Configure paint wrapper for using gradient brush. /// /// Paint wrapper. - /// Target size. + /// Target bound rect. /// Gradient brush. private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush) { diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index aa86df7c23..32818dfdd2 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -16,11 +16,12 @@ namespace Avalonia.Skia /// Surfaces. /// Created render target or if it fails. ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable surfaces); - + /// /// Creates an offscreen render target surface /// /// size in pixels + /// current Skia render session ISkiaSurface TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session); } diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index cea77bb7c9..72ba3ab273 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1066,7 +1066,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection() { - /// Issue #2980. + // Issue #2980. using (Application()) { var target = new DerivedTreeView From b51957f9d87d9459db7c7ba36ae77d2ae84bac29 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 25 Jun 2021 11:58:23 +0200 Subject: [PATCH 31/84] fixes: typo --- src/Avalonia.Controls/Grid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 229fa674f0..a14df1eb43 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -978,7 +978,7 @@ namespace Avalonia.Controls /// width is not registered in columns. /// Passed through to MeasureCell. /// When "true" cells' desired height is not registered in rows. - /// return true when desired size has ghanged + /// return true when desired size has changed private void MeasureCellsGroup( int cellsHead, Size referenceSize, From 6e48959f1084d098c0d3bc7bf1daa4908b2c23bd Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 25 Jun 2021 12:25:46 +0200 Subject: [PATCH 32/84] fixes(MenuItem): nullable warnings --- src/Avalonia.Controls/MenuItem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 4c801c2e06..7b06d3c868 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -36,7 +36,7 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty HotKeyProperty = + public static readonly StyledProperty HotKeyProperty = HotKeyManager.HotKeyProperty.AddOwner(); /// @@ -108,7 +108,7 @@ namespace Avalonia.Controls private ICommand? _command; private bool _commandCanExecute = true; private Popup? _popup; - private KeyGesture _hotkey; + private KeyGesture? _hotkey; private bool _isEmbeddedInMenu; /// @@ -214,7 +214,7 @@ namespace Avalonia.Controls /// /// Gets or sets an associated with this control /// - public KeyGesture HotKey + public KeyGesture? HotKey { get { return GetValue(HotKeyProperty); } set { SetValue(HotKeyProperty, value); } From 97882e3c76f0b821b38e6796b3b5b2a59e43e1e1 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 25 Jun 2021 12:47:52 +0200 Subject: [PATCH 33/84] fixes(Application): some nullable warnings --- src/Avalonia.Controls/Application.cs | 31 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 54c576bb76..157bebe02b 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -13,6 +13,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Threading; +#nullable enable namespace Avalonia { @@ -35,27 +36,27 @@ namespace Avalonia /// /// The application-global data templates. /// - private DataTemplates _dataTemplates; + private DataTemplates? _dataTemplates; private readonly Lazy _clipboard = new Lazy(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))); private readonly Styler _styler = new Styler(); - private Styles _styles; - private IResourceDictionary _resources; + private Styles? _styles; + private IResourceDictionary? _resources; private bool _notifyingResourcesChanged; - private Action> _stylesAdded; - private Action> _stylesRemoved; + private Action>? _stylesAdded; + private Action>? _stylesRemoved; /// /// Defines the property. /// - public static readonly StyledProperty DataContextProperty = + public static readonly StyledProperty DataContextProperty = StyledElement.DataContextProperty.AddOwner(); /// - public event EventHandler ResourcesChanged; + public event EventHandler? ResourcesChanged; - public event EventHandler UrlsOpened; + public event EventHandler? UrlsOpened; /// /// Creates an instance of the class. @@ -72,7 +73,7 @@ namespace Avalonia /// The data context property specifies the default object that will /// be used for data binding. /// - public object DataContext + public object? DataContext { get { return GetValue(DataContextProperty); } set { SetValue(DataContextProperty, value); } @@ -162,7 +163,7 @@ namespace Avalonia /// /// Gets the styling parent of the application, which is null. /// - IStyleHost IStyleHost.StylingParent => null; + IStyleHost? IStyleHost.StylingParent => null; /// bool IStyleHost.IsStylesInitialized => _styles != null; @@ -194,7 +195,7 @@ namespace Avalonia public virtual void Initialize() { } /// - bool IResourceNode.TryGetResource(object key, out object value) + bool IResourceNode.TryGetResource(object key, out object? value) { value = null; return (_resources?.TryGetResource(key, out value) ?? false) || @@ -279,17 +280,17 @@ namespace Avalonia NotifyResourcesChanged(e); } - private string _name; + private string? _name; /// /// Defines Name property /// - public static readonly DirectProperty NameProperty = - AvaloniaProperty.RegisterDirect("Name", o => o.Name, (o, v) => o.Name = v); + public static readonly DirectProperty NameProperty = + AvaloniaProperty.RegisterDirect("Name", o => o.Name, (o, v) => o.Name = v); /// /// Application name to be used for various platform-specific purposes /// - public string Name + public string? Name { get => _name; set => SetAndRaise(NameProperty, ref _name, value); From 87639cab8ee433dcc27f6f7f408871f5a0cab802 Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Sat, 26 Jun 2021 18:59:08 +0300 Subject: [PATCH 34/84] adjust checking --- src/Avalonia.Controls.DataGrid/DataGridRows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index a69b8eafe1..4bfbd7d818 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -425,7 +425,7 @@ namespace Avalonia.Controls UpdateDisplayedRows(DisplayData.FirstScrollingSlot, CellsHeight); } - if (DisplayData.FirstScrollingSlot < slot && DisplayData.LastScrollingSlot > slot) + if (DisplayData.FirstScrollingSlot < slot && (DisplayData.LastScrollingSlot > slot || DisplayData.LastScrollingSlot == -1)) { // The row is already displayed in its entirety return true; From f94e4eb9e75d69f3b12b436f2d036331f4d4a0d9 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 29 Jun 2021 16:54:29 +0200 Subject: [PATCH 35/84] fixes(ComboBox): Closing window on Alt+F4 KeyDown when ComboBox is focused --- src/Avalonia.Controls/ComboBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index f8728af87f..dd2109e6cd 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -205,7 +205,7 @@ namespace Avalonia.Controls if (e.Handled) return; - if (e.Key == Key.F4 || + if ((e.Key == Key.F4 && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) == false) || ((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt))) { IsDropDownOpen = !IsDropDownOpen; From 83c141172f17de098907b7b207f4300f35e8ba73 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 29 Jun 2021 16:55:09 +0200 Subject: [PATCH 36/84] feat(tests): Add test to check closing window on Alt+F4 KeyDown when ComboBox is focused --- .../ComboBoxTests.cs | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 8f9c7fdb0b..cb2fd11175 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -201,6 +201,65 @@ namespace Avalonia.Controls.UnitTests } - } + } + + [Fact] + public void Close_Window_On_Alt_F4_When_ComboBox_Is_Focus() + { + var inputManagerMock = new Moq.Mock(); + var services = TestServices.StyledWindow.With(inputManager: inputManagerMock.Object); + + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = new Window(); + + window.KeyDown += (s, e) => + { + if (e.Handled == false + && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) == true + && e.Key == Key.F4 ) + { + e.Handled = true; + window.Close(); + } + }; + + var count = 0; + + var target = new ComboBox + { + Items = new[] { new Canvas() }, + SelectedIndex = 0, + Template = GetTemplate(), + }; + + window.Content = target; + + + window.Closing += + (sender, e) => + { + count++; + }; + + window.Show(); + + target.Focus(); + + _helper.Down(target); + _helper.Up(target); + Assert.True(target.IsDropDownOpen); + + target.RaiseEvent(new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + KeyModifiers = KeyModifiers.Alt, + Key = Key.F4 + }); + + + Assert.Equal(1, count); + } + } } } From 5bea0e4791e284829b53fae2935a26de4e86a6ea Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 2 Jul 2021 14:44:04 +0200 Subject: [PATCH 37/84] Add "Star" static to GridLength --- src/Avalonia.Controls/GridLength.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index b8418949d9..ee6a146d61 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -77,6 +77,12 @@ namespace Avalonia.Controls /// public static GridLength Auto => new GridLength(0, GridUnitType.Auto); + /// + /// Gets an instance of that indicates that a row or column should + /// fill its content. + /// + public static GridLength Star => new GridLength(1, GridUnitType.Star); + /// /// Gets the unit of the . /// From fa3f2c89327b3c31f9dc737fe15152fe184c365c Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 2 Jul 2021 15:49:19 +0200 Subject: [PATCH 38/84] Adding more ToString() overloads to support DevTools --- src/Avalonia.Controls/RowDefinitions.cs | 8 +++-- src/Avalonia.Visuals/Media/BoxShadow.cs | 41 ++++++++++++++++++++++++ src/Avalonia.Visuals/Media/BoxShadows.cs | 20 +++++++++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/RowDefinitions.cs b/src/Avalonia.Controls/RowDefinitions.cs index 02ab12b5af..a5ed6ae09d 100644 --- a/src/Avalonia.Controls/RowDefinitions.cs +++ b/src/Avalonia.Controls/RowDefinitions.cs @@ -1,5 +1,4 @@ using System.Linq; -using Avalonia.Collections; namespace Avalonia.Controls { @@ -25,6 +24,11 @@ namespace Avalonia.Controls AddRange(GridLength.ParseLengths(s).Select(x => new RowDefinition(x))); } + public override string ToString() + { + return string.Join(",", this.Select(x => x.Height)); + } + /// /// Parses a string representation of row definitions collection. /// @@ -32,4 +36,4 @@ namespace Avalonia.Controls /// The . public static RowDefinitions Parse(string s) => new RowDefinitions(s); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/BoxShadow.cs b/src/Avalonia.Visuals/Media/BoxShadow.cs index 69395fd3b8..7696c6c34b 100644 --- a/src/Avalonia.Visuals/Media/BoxShadow.cs +++ b/src/Avalonia.Visuals/Media/BoxShadow.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Text; using Avalonia.Animation.Animators; using Avalonia.Utilities; @@ -75,6 +76,46 @@ namespace Avalonia.Media return rv; } } + + public override string ToString() + { + var sb = new StringBuilder(); + + if (IsEmpty) + { + return "none"; + } + + if (IsInset) + { + sb.Append("inset"); + } + + if (OffsetX != 0.0) + { + sb.Append($" {OffsetX}"); + } + + if (OffsetY != 0.0) + { + sb.Append($" {OffsetY}"); + } + + if (Blur != 0.0) + { + sb.Append($" {Blur}"); + } + + if (Spread != 0.0) + { + sb.Append($" {Spread}"); + } + + sb.Append($" {Color.ToString()}"); + + return sb.ToString(); + } + public static unsafe BoxShadow Parse(string s) { if(s == null) diff --git a/src/Avalonia.Visuals/Media/BoxShadows.cs b/src/Avalonia.Visuals/Media/BoxShadows.cs index 9e4d6aacb0..6e1b0e7f7d 100644 --- a/src/Avalonia.Visuals/Media/BoxShadows.cs +++ b/src/Avalonia.Visuals/Media/BoxShadows.cs @@ -1,6 +1,6 @@ using System; -using System.Collections.Generic; using System.ComponentModel; +using System.Text; using Avalonia.Animation.Animators; namespace Avalonia.Media @@ -43,6 +43,24 @@ namespace Avalonia.Media } } + public override string ToString() + { + var sb = new StringBuilder(); + + if (Count == 0) + { + return "none"; + } + + foreach (var boxShadow in this) + { + sb.Append(boxShadow + " "); + } + + return sb.ToString(); + + } + [EditorBrowsable(EditorBrowsableState.Never)] public struct BoxShadowsEnumerator { From 40e6282375b3fc9ae56464b39eb2421df3c8a3b6 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 2 Jul 2021 15:51:47 +0200 Subject: [PATCH 39/84] Use interpolation string --- src/Avalonia.Visuals/Media/BoxShadows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/BoxShadows.cs b/src/Avalonia.Visuals/Media/BoxShadows.cs index 6e1b0e7f7d..2b1714aded 100644 --- a/src/Avalonia.Visuals/Media/BoxShadows.cs +++ b/src/Avalonia.Visuals/Media/BoxShadows.cs @@ -54,7 +54,7 @@ namespace Avalonia.Media foreach (var boxShadow in this) { - sb.Append(boxShadow + " "); + sb.Append($"{boxShadow} "); } return sb.ToString(); From cddae052e87ce0b06839cee7b8ed4c32d63fa058 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 2 Jul 2021 16:46:10 +0200 Subject: [PATCH 40/84] Don't box params, prevent creating temporary string --- src/Avalonia.Visuals/Media/BoxShadow.cs | 10 +++++----- src/Avalonia.Visuals/Media/BoxShadows.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Visuals/Media/BoxShadow.cs b/src/Avalonia.Visuals/Media/BoxShadow.cs index 7696c6c34b..50f75365b0 100644 --- a/src/Avalonia.Visuals/Media/BoxShadow.cs +++ b/src/Avalonia.Visuals/Media/BoxShadow.cs @@ -93,25 +93,25 @@ namespace Avalonia.Media if (OffsetX != 0.0) { - sb.Append($" {OffsetX}"); + sb.AppendFormat(" {0}", OffsetX.ToString()); } if (OffsetY != 0.0) { - sb.Append($" {OffsetY}"); + sb.AppendFormat(" {0}", OffsetY.ToString()); } if (Blur != 0.0) { - sb.Append($" {Blur}"); + sb.AppendFormat(" {0}", Blur.ToString()); } if (Spread != 0.0) { - sb.Append($" {Spread}"); + sb.AppendFormat(" {0}", Spread.ToString()); } - sb.Append($" {Color.ToString()}"); + sb.AppendFormat(" {0}", Color.ToString()); return sb.ToString(); } diff --git a/src/Avalonia.Visuals/Media/BoxShadows.cs b/src/Avalonia.Visuals/Media/BoxShadows.cs index 2b1714aded..810ac70b99 100644 --- a/src/Avalonia.Visuals/Media/BoxShadows.cs +++ b/src/Avalonia.Visuals/Media/BoxShadows.cs @@ -54,7 +54,7 @@ namespace Avalonia.Media foreach (var boxShadow in this) { - sb.Append($"{boxShadow} "); + sb.AppendFormat("{0} ", boxShadow.ToString()); } return sb.ToString(); From 6e99f1240634c36bc164a87399333af03b513ba9 Mon Sep 17 00:00:00 2001 From: Splitwirez Date: Sat, 3 Jul 2021 11:17:19 -0400 Subject: [PATCH 41/84] Fix comments --- src/Avalonia.Controls/Shapes/Arc.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs index f6738e10f2..4ce72319aa 100644 --- a/src/Avalonia.Controls/Shapes/Arc.cs +++ b/src/Avalonia.Controls/Shapes/Arc.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls.Shapes } /// - /// Gets or sets the angle at which the arc ends relative to the start angle, in degrees. + /// Gets or sets the angle, in degrees, added to the defining where the arc ends. A positive value is clockwise, negative is counter-clockwise. /// public double SweepAngle { @@ -54,7 +54,7 @@ namespace Avalonia.Controls.Shapes var rect = new Rect(Bounds.Size); - if ((normStart == normEnd) && (startAngle != sweepAngle)) //complete ring + if ((normStart == normEnd) && (startAngle != sweepAngle)) // complete ring { return new EllipseGeometry(rect.Deflate(StrokeThickness / 2)); } @@ -62,7 +62,7 @@ namespace Avalonia.Controls.Shapes { return new StreamGeometry(); } - else //partial arc + else // partial arc { var deflatedRect = rect.Deflate(StrokeThickness / 2); From 17c94d3cf3b43d71aea697f9a17c264ac80da87b Mon Sep 17 00:00:00 2001 From: amwx Date: Sun, 4 Jul 2021 21:33:52 -0500 Subject: [PATCH 42/84] Add impl from upstream --- src/Avalonia.Layout/ElementManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Layout/ElementManager.cs b/src/Avalonia.Layout/ElementManager.cs index cb13deb15f..3f106708e6 100644 --- a/src/Avalonia.Layout/ElementManager.cs +++ b/src/Avalonia.Layout/ElementManager.cs @@ -325,7 +325,10 @@ namespace Avalonia.Layout break; case NotifyCollectionChangedAction.Move: - throw new NotImplementedException(); + int size = args.OldItems != null ? args.OldItems.Count : 1; + OnItemsRemoved(args.OldStartingIndex, size); + OnItemsAdded(args.NewStartingIndex, size); + break; } } } From ea3f85e1263cfb75ce2452e205477080d1c9b531 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 09:34:18 +0200 Subject: [PATCH 43/84] Don't allow using a closed window as a parent/owner. --- src/Avalonia.Controls/Window.cs | 10 +++++++ .../WindowTests.cs | 30 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 700c3d9bad..2d369dae8c 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -635,6 +635,11 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot re-show a closed window."); } + if (parent != null && parent.PlatformImpl == null) + { + throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + } + if (IsVisible) { return; @@ -709,6 +714,11 @@ namespace Avalonia.Controls throw new ArgumentNullException(nameof(owner)); } + if (owner.PlatformImpl == null) + { + throw new InvalidOperationException("Cannot Show a Window with a closed owner."); + } + if (IsVisible) { throw new InvalidOperationException("The window is already being shown."); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index e8311b79ac..2d4559ddba 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -387,6 +387,36 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Calling_Show_With_Closed_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + parent.Close(); + + var ex = Assert.Throws(() => target.Show(parent)); + Assert.Equal("Cannot Show a Window with a closed parent.", ex.Message); + } + } + + [Fact] + public async Task Calling_ShowDialog_With_Closed_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + parent.Close(); + + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); + Assert.Equal("Cannot Show a Window with a closed owner.", ex.Message); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { From 69852a56f588fe0e744aa11e7577eeb351f29670 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 12:16:44 +0200 Subject: [PATCH 44/84] Don't allow self as parent/owner window. --- src/Avalonia.Controls/Window.cs | 17 +++++++++++-- .../WindowTests.cs | 24 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 2d369dae8c..9f6c605d46 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -635,9 +635,17 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot re-show a closed window."); } - if (parent != null && parent.PlatformImpl == null) + if (parent != null) { - throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + if (parent.PlatformImpl == null) + { + throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + } + + if (parent == this) + { + throw new InvalidOperationException("A Window cannot be its own parent."); + } } if (IsVisible) @@ -719,6 +727,11 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot Show a Window with a closed owner."); } + if (owner == this) + { + throw new InvalidOperationException("A Window cannot be its own owner."); + } + if (IsVisible) { throw new InvalidOperationException("The window is already being shown."); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 2d4559ddba..5ba529292f 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -417,6 +417,30 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Calling_Show_With_Self_As_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window(); + + var ex = Assert.Throws(() => target.Show(target)); + Assert.Equal("A Window cannot be its own parent.", ex.Message); + } + } + + [Fact] + public async Task Calling_ShowDialog_With_Self_As_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window(); + + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(target)); + Assert.Equal("A Window cannot be its own owner.", ex.Message); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { From 15cdee1bee18cc7466f68951f7bfa863fee748be Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 13:27:01 +0200 Subject: [PATCH 45/84] Ensure parent/owner windows are visible when showing child. --- src/Avalonia.Controls/Window.cs | 24 +++++---- .../WindowTests.cs | 50 +++++++++++++++---- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 9f6c605d46..ddda083aa8 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -639,13 +639,16 @@ namespace Avalonia.Controls { if (parent.PlatformImpl == null) { - throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + throw new InvalidOperationException("Cannot show a window with a closed parent."); } - - if (parent == this) + else if (parent == this) { throw new InvalidOperationException("A Window cannot be its own parent."); } + else if (!parent.IsVisible) + { + throw new InvalidOperationException("Cannot show window with non-visible parent."); + } } if (IsVisible) @@ -721,21 +724,22 @@ namespace Avalonia.Controls { throw new ArgumentNullException(nameof(owner)); } - - if (owner.PlatformImpl == null) + else if (owner.PlatformImpl == null) { - throw new InvalidOperationException("Cannot Show a Window with a closed owner."); + throw new InvalidOperationException("Cannot show a window with a closed owner."); } - - if (owner == this) + else if (owner == this) { throw new InvalidOperationException("A Window cannot be its own owner."); } - - if (IsVisible) + else if (IsVisible) { throw new InvalidOperationException("The window is already being shown."); } + else if (!owner.IsVisible) + { + throw new InvalidOperationException("Cannot show window with non-visible parent."); + } RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 5ba529292f..88c6c86c46 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -279,10 +279,11 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = Mock.Of(); + var parent = new Window(); var renderer = new Mock(); var target = new Window(CreateImpl(renderer)); + parent.Show(); target.ShowDialog(parent); renderer.Verify(x => x.Start(), Times.Once); @@ -294,10 +295,11 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = Mock.Of(); + var parent = new Window(); var target = new Window(); var raised = false; + parent.Show(); target.Opened += (s, e) => raised = true; target.ShowDialog(parent); @@ -326,14 +328,15 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = new Mock(); + var parent = new Window(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); + parent.Show(); var target = new Window(windowImpl.Object); - var task = target.ShowDialog(parent.Object); + var task = target.ShowDialog(parent); windowImpl.Object.Closed(); @@ -366,14 +369,16 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = new Mock(); + var parent = new Window(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); + parent.Show(); + var target = new Window(windowImpl.Object); - var task = target.ShowDialog(parent.Object); + var task = target.ShowDialog(parent); windowImpl.Object.Closed(); await task; @@ -381,7 +386,7 @@ namespace Avalonia.Controls.UnitTests var openedRaised = false; target.Opened += (s, e) => openedRaised = true; - var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent.Object)); + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); Assert.Equal("Cannot re-show a closed window.", ex.Message); Assert.False(openedRaised); } @@ -398,7 +403,7 @@ namespace Avalonia.Controls.UnitTests parent.Close(); var ex = Assert.Throws(() => target.Show(parent)); - Assert.Equal("Cannot Show a Window with a closed parent.", ex.Message); + Assert.Equal("Cannot show a window with a closed parent.", ex.Message); } } @@ -413,7 +418,33 @@ namespace Avalonia.Controls.UnitTests parent.Close(); var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); - Assert.Equal("Cannot Show a Window with a closed owner.", ex.Message); + Assert.Equal("Cannot show a window with a closed owner.", ex.Message); + } + } + + [Fact] + public void Calling_Show_With_Invisible_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + var ex = Assert.Throws(() => target.Show(parent)); + Assert.Equal("Cannot show window with non-visible parent.", ex.Message); + } + } + + [Fact] + public async Task Calling_ShowDialog_With_Invisible_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); + Assert.Equal("Cannot show window with non-visible parent.", ex.Message); } } @@ -740,6 +771,7 @@ namespace Avalonia.Controls.UnitTests protected override void Show(Window window) { var owner = new Window(); + owner.Show(); window.ShowDialog(owner); } } From a42334d128eb919c6f29de6c7d51cd333b4221a0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 14:00:09 +0200 Subject: [PATCH 46/84] Hide child windows when hiding parent/owner. --- src/Avalonia.Controls/Window.cs | 8 +++++ .../WindowTests.cs | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ddda083aa8..ae314a33ce 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -592,6 +592,14 @@ namespace Avalonia.Controls owner.RemoveChild(this); } + if (_children.Count > 0) + { + foreach (var child in _children.ToArray()) + { + child.child.Hide(); + } + } + Owner = null; PlatformImpl?.Hide(); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 88c6c86c46..6b9921d83d 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -472,6 +472,42 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Hiding_Parent_Window_Should_Close_Children() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var parent = new Window(); + var child = new Window(); + + parent.Show(); + child.Show(parent); + + parent.Hide(); + + Assert.False(parent.IsVisible); + Assert.False(child.IsVisible); + } + } + + [Fact] + public void Hiding_Parent_Window_Should_Close_Dialog_Children() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var parent = new Window(); + var child = new Window(); + + parent.Show(); + child.ShowDialog(parent); + + parent.Hide(); + + Assert.False(parent.IsVisible); + Assert.False(child.IsVisible); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { From f9824007e1a3f47a2583bd86f819bd6cccc9f3b7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 6 Jul 2021 20:26:23 +0100 Subject: [PATCH 47/84] add a method to retain com object during com calls. --- native/Avalonia.Native/inc/comimpl.h | 184 ++++++++++++++------------- 1 file changed, 95 insertions(+), 89 deletions(-) diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index 0ff64b7215..c9f70cfa3b 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -10,6 +10,95 @@ __IID_DEF(IUnknown, 0, 0, 0, C0, 00, 00, 00, 00, 00, 00, 46); +template +class ComPtr +{ +private: + TInterface* _obj; +public: + ComPtr() + { + _obj = 0; + } + + ComPtr(TInterface* pObj) + { + _obj = 0; + + if (pObj) + { + _obj = pObj; + _obj->AddRef(); + } + } + + ComPtr(const ComPtr& ptr) + { + _obj = 0; + + if (ptr._obj) + { + _obj = ptr._obj; + _obj->AddRef(); + } + + } + + ComPtr& operator=(ComPtr other) + { + if(_obj != NULL) + _obj->Release(); + _obj = other._obj; + if(_obj != NULL) + _obj->AddRef(); + return *this; + } + + ~ComPtr() + { + if (_obj) + { + _obj->Release(); + _obj = 0; + } + } + + TInterface* getRaw() + { + return _obj; + } + + TInterface* getRetainedReference() + { + if(_obj == NULL) + return NULL; + _obj->AddRef(); + return _obj; + } + + TInterface** getPPV() + { + return &_obj; + } + + operator TInterface*() const + { + return _obj; + } + TInterface& operator*() const + { + return *_obj; + } + TInterface** operator&() + { + return &_obj; + } + TInterface* operator->() const + { + return _obj; + } +}; + class ComObject : public virtual IUnknown { private: @@ -58,6 +147,12 @@ public: _refCount++; return S_OK; } + +protected: + ComPtr UnknownSelf() + { + return this; + } }; @@ -104,94 +199,5 @@ public: virtual ~ComSingleObject(){} }; -template -class ComPtr -{ -private: - TInterface* _obj; -public: - ComPtr() - { - _obj = 0; - } - - ComPtr(TInterface* pObj) - { - _obj = 0; - - if (pObj) - { - _obj = pObj; - _obj->AddRef(); - } - } - - ComPtr(const ComPtr& ptr) - { - _obj = 0; - - if (ptr._obj) - { - _obj = ptr._obj; - _obj->AddRef(); - } - - } - - ComPtr& operator=(ComPtr other) - { - if(_obj != NULL) - _obj->Release(); - _obj = other._obj; - if(_obj != NULL) - _obj->AddRef(); - return *this; - } - - ~ComPtr() - { - if (_obj) - { - _obj->Release(); - _obj = 0; - } - } - - TInterface* getRaw() - { - return _obj; - } - - TInterface* getRetainedReference() - { - if(_obj == NULL) - return NULL; - _obj->AddRef(); - return _obj; - } - - TInterface** getPPV() - { - return &_obj; - } - - operator TInterface*() const - { - return _obj; - } - TInterface& operator*() const - { - return *_obj; - } - TInterface** operator&() - { - return &_obj; - } - TInterface* operator->() const - { - return _obj; - } -}; - #endif // COMIMPL_H_INCLUDED #pragma clang diagnostic pop From 8058a46d260b1c6d173eb5cc775506f2c0f83565 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 6 Jul 2021 20:27:04 +0100 Subject: [PATCH 48/84] prevent osx crash when closing inside activate. --- native/Avalonia.Native/src/OSX/window.mm | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index c0936356d2..7bbff96270 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -113,9 +113,11 @@ public: UpdateStyle(); [Window setContentView: StandardContainer]; + [Window setTitle:_lastTitle]; if(ShouldTakeFocusOnShow() && activate) { + [Window orderFront: Window]; [Window makeKeyAndOrderFront:Window]; [NSApp activateIgnoringOtherApps:YES]; } @@ -123,7 +125,6 @@ public: { [Window orderFront: Window]; } - [Window setTitle:_lastTitle]; _shown = true; @@ -180,7 +181,10 @@ public: { if (Window != nullptr) { - [Window close]; + auto window = Window; + Window = nullptr; + + [window close]; } return S_OK; @@ -549,6 +553,11 @@ private: void HideOrShowTrafficLights () { + if (Window == nil) + { + return; + } + for (id subview in Window.contentView.superview.subviews) { if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) { NSView *titlebarView = [subview subviews][0]; @@ -963,6 +972,11 @@ private: virtual HRESULT SetWindowState (AvnWindowState state) override { + if(Window == nullptr) + { + return S_OK; + } + @autoreleasepool { if(_actualWindowState == state) From 57440e9d36c9bbb2825cc56d688b02ecb21abc91 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 6 Jul 2021 20:27:18 +0100 Subject: [PATCH 49/84] retain self (this) --- native/Avalonia.Native/src/OSX/window.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 7bbff96270..439f6710b9 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -585,7 +585,9 @@ private: virtual HRESULT Show (bool activate) override { @autoreleasepool - { + { + auto r = this->UnknownSelf(); + WindowBaseImpl::Show(activate); HideOrShowTrafficLights(); @@ -1925,7 +1927,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent { if(![self windowShouldClose:self]) return; } - + [self close]; } From f2907c4f49ff1f8b0d77e828154d49342d36178f Mon Sep 17 00:00:00 2001 From: Royce551 Date: Wed, 7 Jul 2021 22:54:10 -0500 Subject: [PATCH 50/84] Implement ItemsControl.IsTextSearchEnabled --- src/Avalonia.Controls/ItemsControl.cs | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 20d032f597..c544f753e5 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Controls.Metadata; @@ -12,6 +13,7 @@ using Avalonia.Controls.Utils; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Metadata; +using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -52,6 +54,11 @@ namespace Avalonia.Controls public static readonly StyledProperty ItemTemplateProperty = AvaloniaProperty.Register(nameof(ItemTemplate)); + public static readonly StyledProperty IsTextSearchEnabledProperty = + AvaloniaProperty.Register(nameof(IsTextSearchEnabled), true); + + private string _textSearchTerm = string.Empty; + private DispatcherTimer _textSearchTimer; private IEnumerable _items = new AvaloniaList(); private int _itemCount; private IItemContainerGenerator _itemContainerGenerator; @@ -135,6 +142,12 @@ namespace Avalonia.Controls set { SetValue(ItemTemplateProperty, value); } } + public bool IsTextSearchEnabled + { + get { return GetValue(IsTextSearchEnabledProperty); } + set { SetValue(IsTextSearchEnabledProperty, value); } + } + /// /// Gets the items presenter control. /// @@ -323,6 +336,36 @@ namespace Avalonia.Controls base.OnKeyDown(e); } + protected override void OnTextInput(TextInputEventArgs e) + { + if (!e.Handled && this is SelectingItemsControl selectingItemsControl) + { + if (!IsTextSearchEnabled) + return; + + StopTextSearchTimer(); + + _textSearchTerm += e.Text; + + bool match(ItemContainerInfo info) => + info.ContainerControl is IContentControl control && + control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true; + + var info = ItemContainerGenerator.Containers.FirstOrDefault(match); + + if (info != null) + { + selectingItemsControl.SelectedIndex = info.Index; + } + + StartTextSearchTimer(); + + e.Handled = true; + } + + base.OnTextInput(e); + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -456,6 +499,32 @@ namespace Avalonia.Controls } } + private void StartTextSearchTimer() + { + _textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; + _textSearchTimer.Tick += TextSearchTimer_Tick; + _textSearchTimer.Start(); + } + + private void StopTextSearchTimer() + { + if (_textSearchTimer == null) + { + return; + } + + _textSearchTimer.Stop(); + _textSearchTimer.Tick -= TextSearchTimer_Tick; + + _textSearchTimer = null; + } + + private void TextSearchTimer_Tick(object sender, EventArgs e) + { + _textSearchTerm = string.Empty; + StopTextSearchTimer(); + } + private void UpdateItemCount() { if (Items == null) From 875655e3f3bd051fbb1840d6c8f3bab03891d255 Mon Sep 17 00:00:00 2001 From: Royce551 Date: Wed, 7 Jul 2021 23:04:43 -0500 Subject: [PATCH 51/84] Remove IsTextSearchEnabled implemenetation from ComboBox --- src/Avalonia.Controls/ComboBox.cs | 69 ------------------------------- 1 file changed, 69 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index dd2109e6cd..89cfb5fa8f 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -77,14 +77,6 @@ namespace Avalonia.Controls public static readonly StyledProperty VerticalContentAlignmentProperty = ContentControl.VerticalContentAlignmentProperty.AddOwner(); - /// - /// Defines the property. - /// - public static readonly StyledProperty IsTextSearchEnabledProperty = - AvaloniaProperty.Register(nameof(IsTextSearchEnabled), true); - - private string _textSearchTerm = string.Empty; - private DispatcherTimer _textSearchTimer; private bool _isDropDownOpen; private Popup _popup; private object _selectionBoxItem; @@ -173,15 +165,6 @@ namespace Avalonia.Controls set { SetValue(VerticalContentAlignmentProperty, value); } } - /// - /// Gets or sets a value that specifies whether a user can jump to a value by typing. - /// - public bool IsTextSearchEnabled - { - get { return GetValue(IsTextSearchEnabledProperty); } - set { SetValue(IsTextSearchEnabledProperty, value); } - } - /// protected override IItemContainerGenerator CreateItemContainerGenerator() { @@ -247,32 +230,6 @@ namespace Avalonia.Controls } } - /// - protected override void OnTextInput(TextInputEventArgs e) - { - if (!IsTextSearchEnabled || e.Handled) - return; - - StopTextSearchTimer(); - - _textSearchTerm += e.Text; - - bool match(ItemContainerInfo info) => - info.ContainerControl is IContentControl control && - control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true; - - var info = ItemContainerGenerator.Containers.FirstOrDefault(match); - - if (info != null) - { - SelectedIndex = info.Index; - } - - StartTextSearchTimer(); - - e.Handled = true; - } - /// protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { @@ -470,31 +427,5 @@ namespace Avalonia.Controls SelectedIndex = prev; } - - private void StartTextSearchTimer() - { - _textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; - _textSearchTimer.Tick += TextSearchTimer_Tick; - _textSearchTimer.Start(); - } - - private void StopTextSearchTimer() - { - if (_textSearchTimer == null) - { - return; - } - - _textSearchTimer.Stop(); - _textSearchTimer.Tick -= TextSearchTimer_Tick; - - _textSearchTimer = null; - } - - private void TextSearchTimer_Tick(object sender, EventArgs e) - { - _textSearchTerm = string.Empty; - StopTextSearchTimer(); - } } } From 4653c8ff607b7d82c8535472f0d0fecfdc67f302 Mon Sep 17 00:00:00 2001 From: Royce551 Date: Wed, 7 Jul 2021 23:14:13 -0500 Subject: [PATCH 52/84] Readd xmldocs --- src/Avalonia.Controls/ItemsControl.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index c544f753e5..c86f2e591b 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -54,6 +54,9 @@ namespace Avalonia.Controls public static readonly StyledProperty ItemTemplateProperty = AvaloniaProperty.Register(nameof(ItemTemplate)); + /// + /// Defines the property. + /// public static readonly StyledProperty IsTextSearchEnabledProperty = AvaloniaProperty.Register(nameof(IsTextSearchEnabled), true); @@ -142,6 +145,9 @@ namespace Avalonia.Controls set { SetValue(ItemTemplateProperty, value); } } + /// + /// Gets or sets a value that specifies whether a user can jump to a value by typing. + /// public bool IsTextSearchEnabled { get { return GetValue(IsTextSearchEnabledProperty); } From f5ded2632133803632116efad9e5465f4811fee8 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:07:59 +0800 Subject: [PATCH 53/84] Update Arc.cs --- src/Avalonia.Controls/Shapes/Arc.cs | 47 +++++++++++++++-------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs index 4ce72319aa..dad47875c3 100644 --- a/src/Avalonia.Controls/Shapes/Arc.cs +++ b/src/Avalonia.Controls/Shapes/Arc.cs @@ -28,33 +28,34 @@ namespace Avalonia.Controls.Shapes /// public double StartAngle { - get { return GetValue(StartAngleProperty); } - set { SetValue(StartAngleProperty, value); } + get => GetValue(StartAngleProperty); + set => SetValue(StartAngleProperty, value); } /// - /// Gets or sets the angle, in degrees, added to the defining where the arc ends. A positive value is clockwise, negative is counter-clockwise. + /// Gets or sets the angle, in degrees, added to the defining where the arc ends. + /// A positive value is clockwise, negative is counter-clockwise. /// public double SweepAngle { - get { return GetValue(SweepAngleProperty); } - set { SetValue(SweepAngleProperty, value); } + get => GetValue(SweepAngleProperty); + set => SetValue(SweepAngleProperty, value); } protected override Geometry CreateDefiningGeometry() { - double angle1 = DegreesToRad(StartAngle); - double angle2 = angle1 + DegreesToRad(SweepAngle); + var angle1 = DegreesToRad(StartAngle); + var angle2 = angle1 + DegreesToRad(SweepAngle); - double startAngle = Math.Min(angle1, angle2); - double sweepAngle = Math.Max(angle1, angle2); + var startAngle = Math.Min(angle1, angle2); + var sweepAngle = Math.Max(angle1, angle2); - double normStart = RadToNormRad(startAngle); - double normEnd = RadToNormRad(sweepAngle); + var normStart = RadToNormRad(startAngle); + var normEnd = RadToNormRad(sweepAngle); var rect = new Rect(Bounds.Size); - if ((normStart == normEnd) && (startAngle != sweepAngle)) // complete ring + if ((normStart == normEnd) && (startAngle != sweepAngle)) // Complete ring. { return new EllipseGeometry(rect.Deflate(StrokeThickness / 2)); } @@ -62,28 +63,30 @@ namespace Avalonia.Controls.Shapes { return new StreamGeometry(); } - else // partial arc + else // Partial arc. { var deflatedRect = rect.Deflate(StrokeThickness / 2); - double centerX = rect.Center.X; - double centerY = rect.Center.Y; + var centerX = rect.Center.X; + var centerY = rect.Center.Y; - double radiusX = deflatedRect.Width / 2; - double radiusY = deflatedRect.Height / 2; + var radiusX = deflatedRect.Width / 2; + var radiusY = deflatedRect.Height / 2; - double angleGap = RadToNormRad(sweepAngle - startAngle); + var angleGap = RadToNormRad(sweepAngle - startAngle); - Point startPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, startAngle); - Point endPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, sweepAngle); + var startPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, startAngle); + var endPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, sweepAngle); - StreamGeometry arcGeometry = new StreamGeometry(); + var arcGeometry = new StreamGeometry(); + using (var ctx = arcGeometry.Open()) { ctx.BeginFigure(startPoint, false); ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI, SweepDirection.Clockwise); ctx.EndFigure(false); } + return arcGeometry; } } @@ -91,7 +94,7 @@ namespace Avalonia.Controls.Shapes static double DegreesToRad(double inAngle) => inAngle * Math.PI / 180; - static double RadToNormRad(double inAngle) => (0 + (inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); + static double RadToNormRad(double inAngle) => (inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) => new Point((radiusX * Math.Cos(angle)) + centerX, (radiusY * Math.Sin(angle)) + centerY); From 3740c9d9733693e118454aca541c671bce110a85 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 8 Jul 2021 14:33:18 +0800 Subject: [PATCH 54/84] Update Arc.cs --- src/Avalonia.Controls/Shapes/Arc.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs index dad47875c3..5ebb321f9b 100644 --- a/src/Avalonia.Controls/Shapes/Arc.cs +++ b/src/Avalonia.Controls/Shapes/Arc.cs @@ -41,12 +41,12 @@ namespace Avalonia.Controls.Shapes get => GetValue(SweepAngleProperty); set => SetValue(SweepAngleProperty, value); } - + protected override Geometry CreateDefiningGeometry() { var angle1 = DegreesToRad(StartAngle); var angle2 = angle1 + DegreesToRad(SweepAngle); - + var startAngle = Math.Min(angle1, angle2); var sweepAngle = Math.Max(angle1, angle2); @@ -72,29 +72,30 @@ namespace Avalonia.Controls.Shapes var radiusX = deflatedRect.Width / 2; var radiusY = deflatedRect.Height / 2; - + var angleGap = RadToNormRad(sweepAngle - startAngle); var startPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, startAngle); var endPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, sweepAngle); var arcGeometry = new StreamGeometry(); - + using (var ctx = arcGeometry.Open()) { ctx.BeginFigure(startPoint, false); - ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI, SweepDirection.Clockwise); + ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI, + SweepDirection.Clockwise); ctx.EndFigure(false); } - + return arcGeometry; } } static double DegreesToRad(double inAngle) => inAngle * Math.PI / 180; - - static double RadToNormRad(double inAngle) => (inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); + + static double RadToNormRad(double inAngle) => ((inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) => new Point((radiusX * Math.Cos(angle)) + centerX, (radiusY * Math.Sin(angle)) + centerY); From ec85bf14e602c3e0eaed87a7761b1c09fd43ea17 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 8 Jul 2021 11:41:19 +0200 Subject: [PATCH 55/84] Call makeFirstResponder when showing window. This is needed in order for the view to receive keyboard events immediately after showing the window. Fixes #6202 --- native/Avalonia.Native/src/OSX/window.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index e40ec12461..17d9a150a8 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -117,6 +117,7 @@ public: if(ShouldTakeFocusOnShow() && activate) { [Window makeKeyAndOrderFront:Window]; + [Window makeFirstResponder:View]; [NSApp activateIgnoringOtherApps:YES]; } else From 975e48d3a2afb16d072a1f78c9735e5e2cb4d3bd Mon Sep 17 00:00:00 2001 From: Royce551 Date: Thu, 8 Jul 2021 08:11:27 -0500 Subject: [PATCH 56/84] Move to SelectingItemsControl --- src/Avalonia.Controls/ItemsControl.cs | 74 ------------------- .../Primitives/SelectingItemsControl.cs | 74 +++++++++++++++++++ 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index c86f2e591b..55645d4dbb 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -13,7 +13,6 @@ using Avalonia.Controls.Utils; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Metadata; -using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -54,14 +53,6 @@ namespace Avalonia.Controls public static readonly StyledProperty ItemTemplateProperty = AvaloniaProperty.Register(nameof(ItemTemplate)); - /// - /// Defines the property. - /// - public static readonly StyledProperty IsTextSearchEnabledProperty = - AvaloniaProperty.Register(nameof(IsTextSearchEnabled), true); - - private string _textSearchTerm = string.Empty; - private DispatcherTimer _textSearchTimer; private IEnumerable _items = new AvaloniaList(); private int _itemCount; private IItemContainerGenerator _itemContainerGenerator; @@ -145,15 +136,6 @@ namespace Avalonia.Controls set { SetValue(ItemTemplateProperty, value); } } - /// - /// Gets or sets a value that specifies whether a user can jump to a value by typing. - /// - public bool IsTextSearchEnabled - { - get { return GetValue(IsTextSearchEnabledProperty); } - set { SetValue(IsTextSearchEnabledProperty, value); } - } - /// /// Gets the items presenter control. /// @@ -342,36 +324,6 @@ namespace Avalonia.Controls base.OnKeyDown(e); } - protected override void OnTextInput(TextInputEventArgs e) - { - if (!e.Handled && this is SelectingItemsControl selectingItemsControl) - { - if (!IsTextSearchEnabled) - return; - - StopTextSearchTimer(); - - _textSearchTerm += e.Text; - - bool match(ItemContainerInfo info) => - info.ContainerControl is IContentControl control && - control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true; - - var info = ItemContainerGenerator.Containers.FirstOrDefault(match); - - if (info != null) - { - selectingItemsControl.SelectedIndex = info.Index; - } - - StartTextSearchTimer(); - - e.Handled = true; - } - - base.OnTextInput(e); - } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -505,32 +457,6 @@ namespace Avalonia.Controls } } - private void StartTextSearchTimer() - { - _textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; - _textSearchTimer.Tick += TextSearchTimer_Tick; - _textSearchTimer.Start(); - } - - private void StopTextSearchTimer() - { - if (_textSearchTimer == null) - { - return; - } - - _textSearchTimer.Stop(); - _textSearchTimer.Tick -= TextSearchTimer_Tick; - - _textSearchTimer = null; - } - - private void TextSearchTimer_Tick(object sender, EventArgs e) - { - _textSearchTerm = string.Empty; - StopTextSearchTimer(); - } - private void UpdateItemCount() { if (Items == null) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 34d3347434..1207b7f2dc 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -10,6 +10,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; +using Avalonia.Threading; using Avalonia.VisualTree; #nullable enable @@ -91,6 +92,12 @@ namespace Avalonia.Controls.Primitives AvaloniaProperty.Register( nameof(SelectionMode)); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsTextSearchEnabledProperty = + AvaloniaProperty.Register(nameof(IsTextSearchEnabled), true); + /// /// Event that should be raised by items that implement to /// notify the parent that their selection state @@ -110,6 +117,8 @@ namespace Avalonia.Controls.Primitives RoutingStrategies.Bubble); private static readonly IList Empty = Array.Empty(); + private string _textSearchTerm = string.Empty; + private DispatcherTimer? _textSearchTimer; private ISelectionModel? _selection; private int _oldSelectedIndex; private object? _oldSelectedItem; @@ -305,6 +314,15 @@ namespace Avalonia.Controls.Primitives } } + /// + /// Gets or sets a value that specifies whether a user can jump to a value by typing. + /// + public bool IsTextSearchEnabled + { + get { return GetValue(IsTextSearchEnabledProperty); } + set { SetValue(IsTextSearchEnabledProperty, value); } + } + /// /// Gets or sets the selection mode. /// @@ -490,6 +508,36 @@ namespace Avalonia.Controls.Primitives } } + protected override void OnTextInput(TextInputEventArgs e) + { + if (!e.Handled) + { + if (!IsTextSearchEnabled) + return; + + StopTextSearchTimer(); + + _textSearchTerm += e.Text; + + bool match(ItemContainerInfo info) => + info.ContainerControl is IContentControl control && + control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true; + + var info = ItemContainerGenerator.Containers.FirstOrDefault(match); + + if (info != null) + { + SelectedIndex = info.Index; + } + + StartTextSearchTimer(); + + e.Handled = true; + } + + base.OnTextInput(e); + } + protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); @@ -962,6 +1010,32 @@ namespace Avalonia.Controls.Primitives } } + private void StartTextSearchTimer() + { + _textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; + _textSearchTimer.Tick += TextSearchTimer_Tick; + _textSearchTimer.Start(); + } + + private void StopTextSearchTimer() + { + if (_textSearchTimer == null) + { + return; + } + + _textSearchTimer.Stop(); + _textSearchTimer.Tick -= TextSearchTimer_Tick; + + _textSearchTimer = null; + } + + private void TextSearchTimer_Tick(object sender, EventArgs e) + { + _textSearchTerm = string.Empty; + StopTextSearchTimer(); + } + // When in a BeginInit..EndInit block, or when the DataContext is updating, we need to // defer changes to the selection model because we have no idea in which order properties // will be set. Consider: From f053bfef3a6f948053ebe495e6eac6a0fdc1e9b3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Jul 2021 14:18:47 +0100 Subject: [PATCH 57/84] macro and add self pointer retaining to all COM calls. --- native/Avalonia.Native/inc/comimpl.h | 1 + native/Avalonia.Native/src/OSX/AvnString.mm | 26 ++- native/Avalonia.Native/src/OSX/Screens.mm | 4 + native/Avalonia.Native/src/OSX/cgl.mm | 8 + native/Avalonia.Native/src/OSX/clipboard.mm | 82 ++++--- native/Avalonia.Native/src/OSX/controlhost.mm | 63 ++++-- native/Avalonia.Native/src/OSX/cursor.mm | 52 +++-- native/Avalonia.Native/src/OSX/main.mm | 174 +++++++++++---- native/Avalonia.Native/src/OSX/menu.mm | 22 ++ .../src/OSX/platformthreading.mm | 2 + .../Avalonia.Native/src/OSX/rendertarget.mm | 6 + native/Avalonia.Native/src/OSX/window.mm | 207 +++++++++++++----- 12 files changed, 461 insertions(+), 186 deletions(-) diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index c9f70cfa3b..45a8e8690d 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -7,6 +7,7 @@ #define COMIMPL_H_INCLUDED #include +#define START_COM_CALL auto r = this->UnknownSelf() __IID_DEF(IUnknown, 0, 0, 0, C0, 00, 00, 00, 00, 00, 00, 46); diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 001cf151d8..6d057fb705 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -43,6 +43,8 @@ public: virtual HRESULT Pointer(void**retOut) override { + START_COM_CALL; + @autoreleasepool { if(retOut == nullptr) @@ -58,14 +60,13 @@ public: virtual HRESULT Length(int*retOut) override { - if(retOut == nullptr) + START_COM_CALL; + + @autoreleasepool { - return E_POINTER; + i@autoreleasepool + {eturn S_OK; } - - *retOut = _length; - - return S_OK; } }; @@ -109,10 +110,15 @@ public: virtual HRESULT Get(unsigned int index, IAvnString**ppv) override { - if(_list.size() <= index) - return E_INVALIDARG; - *ppv = _list[index].getRetainedReference(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + if(_list.size() <= index) + return E_INVALIDARG; + *ppv = _list[index].getRetainedReference(); + return S_OK; + } } }; diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm index 10f698ff45..b9c75ed742 100644 --- a/native/Avalonia.Native/src/OSX/Screens.mm +++ b/native/Avalonia.Native/src/OSX/Screens.mm @@ -8,6 +8,8 @@ class Screens : public ComSingleObject public: virtual HRESULT GetScreenCount (int* ret) override { + START_COM_CALL; + @autoreleasepool { *ret = (int)[NSScreen screens].count; @@ -18,6 +20,8 @@ public: virtual HRESULT GetScreen (int index, AvnScreen* ret) override { + START_COM_CALL; + @autoreleasepool { if(index < 0 || index >= [NSScreen screens].count) diff --git a/native/Avalonia.Native/src/OSX/cgl.mm b/native/Avalonia.Native/src/OSX/cgl.mm index a9d94cdf04..085037978e 100644 --- a/native/Avalonia.Native/src/OSX/cgl.mm +++ b/native/Avalonia.Native/src/OSX/cgl.mm @@ -69,6 +69,8 @@ public: virtual HRESULT LegacyMakeCurrent() override { + START_COM_CALL; + if(CGLSetCurrentContext(Context) != 0) return E_FAIL; return S_OK; @@ -76,6 +78,8 @@ public: virtual HRESULT MakeCurrent(IUnknown** ppv) override { + START_COM_CALL; + CGLContextObj saved = CGLGetCurrentContext(); CGLLockContext(Context); if(CGLSetCurrentContext(Context) != 0) @@ -128,6 +132,8 @@ public: virtual HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv) override { + START_COM_CALL; + CGLContextObj shareContext = nil; if(share != nil) { @@ -144,6 +150,8 @@ public: virtual HRESULT WrapContext(void* native, IAvnGlContext**ppv) override { + START_COM_CALL; + if(native == nil) return E_INVALIDARG; *ppv = new AvnGlContext((CGLContextObj) native); diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index f148374759..9966971b73 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -25,6 +25,8 @@ public: virtual HRESULT GetText (char* type, IAvnString**ppv) override { + START_COM_CALL; + @autoreleasepool { if(ppv == nullptr) @@ -42,6 +44,8 @@ public: virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) override { + START_COM_CALL; + @autoreleasepool { *ppv= nil; @@ -69,56 +73,71 @@ public: virtual HRESULT SetText (char* type, char* utf8String) override { - Clear(); + START_COM_CALL; + @autoreleasepool { + Clear(); + auto string = [NSString stringWithUTF8String:(const char*)utf8String]; auto typeString = [NSString stringWithUTF8String:(const char*)type]; if(_item == nil) [_pb setString: string forType: typeString]; else [_item setString: string forType:typeString]; - } - return S_OK; + return S_OK; + } } virtual HRESULT SetBytes(char* type, void* bytes, int len) override { - auto typeString = [NSString stringWithUTF8String:(const char*)type]; - auto data = [NSData dataWithBytes:bytes length:len]; - if(_item == nil) - [_pb setData:data forType:typeString]; - else - [_item setData:data forType:typeString]; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + auto typeString = [NSString stringWithUTF8String:(const char*)type]; + auto data = [NSData dataWithBytes:bytes length:len]; + if(_item == nil) + [_pb setData:data forType:typeString]; + else + [_item setData:data forType:typeString]; + return S_OK; + } } virtual HRESULT GetBytes(char* type, IAvnString**ppv) override { - *ppv = nil; - auto typeString = [NSString stringWithUTF8String:(const char*)type]; - NSData*data; - @try + START_COM_CALL; + + @autoreleasepool { - if(_item) - data = [_item dataForType:typeString]; - else - data = [_pb dataForType:typeString]; - if(data == nil) + *ppv = nil; + auto typeString = [NSString stringWithUTF8String:(const char*)type]; + NSData*data; + @try + { + if(_item) + data = [_item dataForType:typeString]; + else + data = [_pb dataForType:typeString]; + if(data == nil) + return E_FAIL; + } + @catch(NSException* e) + { return E_FAIL; + } + *ppv = CreateByteArray((void*)data.bytes, (int)data.length); + return S_OK; } - @catch(NSException* e) - { - return E_FAIL; - } - *ppv = CreateByteArray((void*)data.bytes, (int)data.length); - return S_OK; } virtual HRESULT Clear() override { + START_COM_CALL; + @autoreleasepool { if(_item != nil) @@ -128,15 +147,20 @@ public: [_pb clearContents]; [_pb setString:@"" forType:NSPasteboardTypeString]; } - } - return S_OK; + return S_OK; + } } virtual HRESULT ObtainFormats(IAvnStringArray** ppv) override { - *ppv = CreateAvnStringArray(_item == nil ? [_pb types] : [_item types]); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = CreateAvnStringArray(_item == nil ? [_pb types] : [_item types]); + return S_OK; + } } }; diff --git a/native/Avalonia.Native/src/OSX/controlhost.mm b/native/Avalonia.Native/src/OSX/controlhost.mm index 5ee2344ac7..f8e9a3b6d1 100644 --- a/native/Avalonia.Native/src/OSX/controlhost.mm +++ b/native/Avalonia.Native/src/OSX/controlhost.mm @@ -16,11 +16,16 @@ public: virtual HRESULT CreateDefaultChild(void* parent, void** retOut) override { - NSView* view = [NSView new]; - [view setWantsLayer: true]; + START_COM_CALL; - *retOut = (__bridge_retained void*)view; - return S_OK; + @autoreleasepool + { + NSView* view = [NSView new]; + [view setWantsLayer: true]; + + *retOut = (__bridge_retained void*)view; + return S_OK; + } }; virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() override @@ -69,32 +74,42 @@ public: virtual HRESULT InitializeWithChildHandle(void* child) override { - if(_child != nil) - return E_FAIL; - _child = (__bridge NSView*)child; - if(_child == nil) - return E_FAIL; - [_holder addSubview:_child]; - [_child setHidden: false]; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + if(_child != nil) + return E_FAIL; + _child = (__bridge NSView*)child; + if(_child == nil) + return E_FAIL; + [_holder addSubview:_child]; + [_child setHidden: false]; + return S_OK; + } }; virtual HRESULT AttachTo(IAvnNativeControlHost* host) override { - if(host == nil) - { - [_holder removeFromSuperview]; - [_holder setHidden: true]; - } - else + START_COM_CALL; + + @autoreleasepool { - AvnNativeControlHost* chost = dynamic_cast(host); - if(chost == nil || chost->View == nil) - return E_FAIL; - [_holder setHidden:true]; - [chost->View addSubview:_holder]; + if(host == nil) + { + [_holder removeFromSuperview]; + [_holder setHidden: true]; + } + else + { + AvnNativeControlHost* chost = dynamic_cast(host); + if(chost == nil || chost->View == nil) + return E_FAIL; + [_holder setHidden:true]; + [chost->View addSubview:_holder]; + } + return S_OK; } - return S_OK; }; virtual void ShowInBounds(float x, float y, float width, float height) override diff --git a/native/Avalonia.Native/src/OSX/cursor.mm b/native/Avalonia.Native/src/OSX/cursor.mm index 1732d6e71f..dc38294a18 100644 --- a/native/Avalonia.Native/src/OSX/cursor.mm +++ b/native/Avalonia.Native/src/OSX/cursor.mm @@ -53,36 +53,46 @@ public: virtual HRESULT GetCursor (AvnStandardCursorType cursorType, IAvnCursor** retOut) override { - *retOut = s_cursorMap[cursorType]; + START_COM_CALL; - if(*retOut != nullptr) + @autoreleasepool { - (*retOut)->AddRef(); - } + *retOut = s_cursorMap[cursorType]; - return S_OK; + if(*retOut != nullptr) + { + (*retOut)->AddRef(); + } + + return S_OK; + } } virtual HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut) override { - if(bitmapData == nullptr || retOut == nullptr) + START_COM_CALL; + + @autoreleasepool { - return E_POINTER; + if(bitmapData == nullptr || retOut == nullptr) + { + return E_POINTER; + } + + NSData *imageData = [NSData dataWithBytes:bitmapData length:length]; + NSImage *image = [[NSImage alloc] initWithData:imageData]; + + + NSPoint hotSpot; + hotSpot.x = hotPixel.Width; + hotSpot.y = hotPixel.Height; + + *retOut = new Cursor([[NSCursor new] initWithImage: image hotSpot: hotSpot]); + + (*retOut)->AddRef(); + + return S_OK; } - - NSData *imageData = [NSData dataWithBytes:bitmapData length:length]; - NSImage *image = [[NSImage alloc] initWithData:imageData]; - - - NSPoint hotSpot; - hotSpot.x = hotPixel.Width; - hotSpot.y = hotPixel.Height; - - *retOut = new Cursor([[NSCursor new] initWithImage: image hotSpot: hotSpot]); - - (*retOut)->AddRef(); - - return S_OK; } }; diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index aaaf381b26..3e152a6125 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -107,27 +107,42 @@ public: virtual HRESULT SetApplicationTitle(char* utf8String) override { - auto appTitle = [NSString stringWithUTF8String: utf8String]; + START_COM_CALL; - [[NSProcessInfo processInfo] setProcessName:appTitle]; - - - SetProcessName(appTitle); - - return S_OK; + @autoreleasepool + { + auto appTitle = [NSString stringWithUTF8String: utf8String]; + + [[NSProcessInfo processInfo] setProcessName:appTitle]; + + + SetProcessName(appTitle); + + return S_OK; + } } virtual HRESULT SetShowInDock(int show) override { - AvnDesiredActivationPolicy = show - ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + AvnDesiredActivationPolicy = show + ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory; + return S_OK; + } } virtual HRESULT SetDisableDefaultApplicationMenuItems (bool enabled) override { - SetAutoGenerateDefaultAppMenuItems(!enabled); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + SetAutoGenerateDefaultAppMenuItems(!enabled); + return S_OK; + } } }; @@ -165,6 +180,8 @@ public: FORWARD_IUNKNOWN() virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* events) override { + START_COM_CALL; + _deallocator = deallocator; @autoreleasepool{ [[ThreadingInitializer new] do]; @@ -180,89 +197,154 @@ public: virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) override { - if(cb == nullptr || ppv == nullptr) - return E_POINTER; - *ppv = CreateAvnWindow(cb, gl); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + if(cb == nullptr || ppv == nullptr) + return E_POINTER; + *ppv = CreateAvnWindow(cb, gl); + return S_OK; + } }; virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) override { - if(cb == nullptr || ppv == nullptr) - return E_POINTER; + START_COM_CALL; - *ppv = CreateAvnPopup(cb, gl); - return S_OK; + @autoreleasepool + { + if(cb == nullptr || ppv == nullptr) + return E_POINTER; + + *ppv = CreateAvnPopup(cb, gl); + return S_OK; + } } virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv) override { - *ppv = CreatePlatformThreading(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = CreatePlatformThreading(); + return S_OK; + } } virtual HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv) override { - *ppv = ::CreateSystemDialogs(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateSystemDialogs(); + return S_OK; + } } virtual HRESULT CreateScreens (IAvnScreens** ppv) override { - *ppv = ::CreateScreens (); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateScreens (); + return S_OK; + } } virtual HRESULT CreateClipboard(IAvnClipboard** ppv) override { - *ppv = ::CreateClipboard (nil, nil); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateClipboard (nil, nil); + return S_OK; + } } virtual HRESULT CreateDndClipboard(IAvnClipboard** ppv) override { - *ppv = ::CreateClipboard (nil, [NSPasteboardItem new]); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateClipboard (nil, [NSPasteboardItem new]); + return S_OK; + } } virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) override { - *ppv = ::CreateCursorFactory(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateCursorFactory(); + return S_OK; + } } virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) override { - auto rv = ::GetGlDisplay(); - if(rv == NULL) - return E_FAIL; - rv->AddRef(); - *ppv = rv; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + auto rv = ::GetGlDisplay(); + if(rv == NULL) + return E_FAIL; + rv->AddRef(); + *ppv = rv; + return S_OK; + } } virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) override { - *ppv = ::CreateAppMenu(cb); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateAppMenu(cb); + return S_OK; + } } virtual HRESULT CreateMenuItem (IAvnMenuItem** ppv) override { - *ppv = ::CreateAppMenuItem(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateAppMenuItem(); + return S_OK; + } } virtual HRESULT CreateMenuItemSeparator (IAvnMenuItem** ppv) override { - *ppv = ::CreateAppMenuItemSeparator(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateAppMenuItemSeparator(); + return S_OK; + } } virtual HRESULT SetAppMenu (IAvnMenu* appMenu) override { - ::SetAppMenu(s_appTitle, appMenu); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + ::SetAppMenu(s_appTitle, appMenu); + return S_OK; + } } }; diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index b9a95e7b3c..38f8c2a7cb 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -95,6 +95,8 @@ NSMenuItem* AvnAppMenuItem::GetNative() HRESULT AvnAppMenuItem::SetSubMenu (IAvnMenu* menu) { + START_COM_CALL; + @autoreleasepool { if(menu != nullptr) @@ -114,6 +116,8 @@ HRESULT AvnAppMenuItem::SetSubMenu (IAvnMenu* menu) HRESULT AvnAppMenuItem::SetTitle (char* utf8String) { + START_COM_CALL; + @autoreleasepool { if (utf8String != nullptr) @@ -128,6 +132,8 @@ HRESULT AvnAppMenuItem::SetTitle (char* utf8String) HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers) { + START_COM_CALL; + @autoreleasepool { if(key != AvnKeyNone) @@ -183,6 +189,8 @@ HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers) HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) { + START_COM_CALL; + @autoreleasepool { _predicate = predicate; @@ -193,6 +201,8 @@ HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionC HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked) { + START_COM_CALL; + @autoreleasepool { [_native setState:(isChecked && _isCheckable ? NSOnState : NSOffState)]; @@ -202,6 +212,8 @@ HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked) HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType) { + START_COM_CALL; + @autoreleasepool { switch(toggleType) @@ -231,6 +243,8 @@ HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType) HRESULT AvnAppMenuItem::SetIcon(void *data, size_t length) { + START_COM_CALL; + @autoreleasepool { if(data != nullptr) @@ -317,6 +331,8 @@ void AvnAppMenu::RaiseClosed() HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item) { + START_COM_CALL; + @autoreleasepool { if([_native hasGlobalMenuItem]) @@ -337,6 +353,8 @@ HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item) HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item) { + START_COM_CALL; + @autoreleasepool { auto avnMenuItem = dynamic_cast(item); @@ -352,6 +370,8 @@ HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item) HRESULT AvnAppMenu::SetTitle (char* utf8String) { + START_COM_CALL; + @autoreleasepool { if (utf8String != nullptr) @@ -365,6 +385,8 @@ HRESULT AvnAppMenu::SetTitle (char* utf8String) HRESULT AvnAppMenu::Clear() { + START_COM_CALL; + @autoreleasepool { [_native removeAllItems]; diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm index e83bf53331..6d5bd4aa02 100644 --- a/native/Avalonia.Native/src/OSX/platformthreading.mm +++ b/native/Avalonia.Native/src/OSX/platformthreading.mm @@ -114,6 +114,8 @@ public: virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) override { + START_COM_CALL; + auto can = dynamic_cast(cancel); if(can->Cancelled) return S_OK; diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index b2d4341bb9..dc5c24e41e 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -247,6 +247,8 @@ public: virtual HRESULT GetPixelSize(AvnPixelSize* ret) override { + START_COM_CALL; + if(!_surface) return E_FAIL; *ret = _surface->size; @@ -255,6 +257,8 @@ public: virtual HRESULT GetScaling(double* ret) override { + START_COM_CALL; + if(!_surface) return E_FAIL; *ret = _surface->scale; @@ -281,6 +285,8 @@ public: virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override { + START_COM_CALL; + ComPtr releaseContext; @synchronized (_target->lock) { if(_target->surface == nil) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 439f6710b9..a163106d2d 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -54,6 +54,8 @@ public: virtual HRESULT ObtainNSWindowHandle(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -66,6 +68,8 @@ public: virtual HRESULT ObtainNSWindowHandleRetained(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -78,6 +82,8 @@ public: virtual HRESULT ObtainNSViewHandle(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -90,6 +96,8 @@ public: virtual HRESULT ObtainNSViewHandleRetained(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -107,6 +115,8 @@ public: virtual HRESULT Show(bool activate) override { + START_COM_CALL; + @autoreleasepool { SetPosition(lastPositionSet); @@ -139,6 +149,8 @@ public: virtual HRESULT Hide () override { + START_COM_CALL; + @autoreleasepool { if(Window != nullptr) @@ -153,6 +165,8 @@ public: virtual HRESULT Activate () override { + START_COM_CALL; + @autoreleasepool { if(Window != nullptr) @@ -167,6 +181,8 @@ public: virtual HRESULT SetTopMost (bool value) override { + START_COM_CALL; + @autoreleasepool { [Window setLevel: value ? NSFloatingWindowLevel : NSNormalWindowLevel]; @@ -177,6 +193,8 @@ public: virtual HRESULT Close() override { + START_COM_CALL; + @autoreleasepool { if (Window != nullptr) @@ -193,6 +211,8 @@ public: virtual HRESULT GetClientSize(AvnSize* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -208,6 +228,8 @@ public: virtual HRESULT GetScaling (double* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -226,6 +248,8 @@ public: virtual HRESULT SetMinMaxSize (AvnSize minSize, AvnSize maxSize) override { + START_COM_CALL; + @autoreleasepool { [Window setMinSize: ToNSSize(minSize)]; @@ -237,6 +261,8 @@ public: virtual HRESULT Resize(double x, double y) override { + START_COM_CALL; + @autoreleasepool { auto maxSize = [Window maxSize]; @@ -276,6 +302,8 @@ public: virtual HRESULT Invalidate (AvnRect rect) override { + START_COM_CALL; + @autoreleasepool { [View setNeedsDisplayInRect:[View frame]]; @@ -286,6 +314,8 @@ public: virtual HRESULT SetMainMenu(IAvnMenu* menu) override { + START_COM_CALL; + _mainMenu = menu; auto nativeMenu = dynamic_cast(menu); @@ -304,6 +334,8 @@ public: virtual HRESULT BeginMoveDrag () override { + START_COM_CALL; + @autoreleasepool { auto lastEvent = [View lastMouseDownEvent]; @@ -321,11 +353,15 @@ public: virtual HRESULT BeginResizeDrag (AvnWindowEdge edge) override { + START_COM_CALL; + return S_OK; } virtual HRESULT GetPosition (AvnPoint* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -346,6 +382,8 @@ public: virtual HRESULT SetPosition (AvnPoint point) override { + START_COM_CALL; + @autoreleasepool { lastPositionSet = point; @@ -357,6 +395,8 @@ public: virtual HRESULT PointToClient (AvnPoint point, AvnPoint* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -375,6 +415,8 @@ public: virtual HRESULT PointToScreen (AvnPoint point, AvnPoint* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -392,12 +434,16 @@ public: virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose) override { + START_COM_CALL; + [View setSwRenderedFrame: fb dispose: dispose]; return S_OK; } virtual HRESULT SetCursor(IAvnCursor* cursor) override { + START_COM_CALL; + @autoreleasepool { Cursor* avnCursor = dynamic_cast(cursor); @@ -427,6 +473,8 @@ public: virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ppv) override { + START_COM_CALL; + if(View == NULL) return E_FAIL; *ppv = [renderTarget createSurfaceRenderTarget]; @@ -435,6 +483,8 @@ public: virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) override { + START_COM_CALL; + if(View == NULL) return E_FAIL; *retOut = ::CreateNativeControlHost(View); @@ -443,6 +493,8 @@ public: virtual HRESULT SetBlurEnabled (bool enable) override { + START_COM_CALL; + [StandardContainer ShowBlur:enable]; return S_OK; @@ -452,6 +504,8 @@ public: IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle) override { + START_COM_CALL; + auto item = TryGetPasteboardItem(clipboard); [item setString:@"" forType:GetAvnCustomDataType()]; if(item == nil) @@ -584,10 +638,10 @@ private: virtual HRESULT Show (bool activate) override { + START_COM_CALL; + @autoreleasepool { - auto r = this->UnknownSelf(); - WindowBaseImpl::Show(activate); HideOrShowTrafficLights(); @@ -598,6 +652,8 @@ private: virtual HRESULT SetEnabled (bool enable) override { + START_COM_CALL; + @autoreleasepool { [Window setEnabled:enable]; @@ -607,6 +663,8 @@ private: virtual HRESULT SetParent (IAvnWindow* parent) override { + START_COM_CALL; + @autoreleasepool { if(parent == nullptr) @@ -718,6 +776,8 @@ private: virtual HRESULT SetCanResize(bool value) override { + START_COM_CALL; + @autoreleasepool { _canResize = value; @@ -728,6 +788,8 @@ private: virtual HRESULT SetDecorations(SystemDecorations value) override { + START_COM_CALL; + @autoreleasepool { auto currentWindowState = _lastWindowState; @@ -793,6 +855,8 @@ private: virtual HRESULT SetTitle (char* utf8title) override { + START_COM_CALL; + @autoreleasepool { _lastTitle = [NSString stringWithUTF8String:(const char*)utf8title]; @@ -804,6 +868,8 @@ private: virtual HRESULT SetTitleBarColor(AvnColor color) override { + START_COM_CALL; + @autoreleasepool { float a = (float)color.Alpha / 255.0f; @@ -833,6 +899,8 @@ private: virtual HRESULT GetWindowState (AvnWindowState*ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -866,86 +934,111 @@ private: virtual HRESULT TakeFocusFromChildren () override { - if(Window == nil) - return S_OK; - if([Window isKeyWindow]) - [Window makeFirstResponder: View]; + START_COM_CALL; - return S_OK; + @autoreleasepool + { + if(Window == nil) + return S_OK; + if([Window isKeyWindow]) + [Window makeFirstResponder: View]; + + return S_OK; + } } virtual HRESULT SetExtendClientArea (bool enable) override { - _isClientAreaExtended = enable; + START_COM_CALL; - if(enable) + @autoreleasepool { - Window.titleVisibility = NSWindowTitleHidden; - - [Window setTitlebarAppearsTransparent:true]; - - auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); - - if (wantsTitleBar) - { - [StandardContainer ShowTitleBar:true]; - } - else - { - [StandardContainer ShowTitleBar:false]; - } + _isClientAreaExtended = enable; - if(_extendClientHints & AvnOSXThickTitleBar) + if(enable) { - Window.toolbar = [NSToolbar new]; - Window.toolbar.showsBaselineSeparator = false; + Window.titleVisibility = NSWindowTitleHidden; + + [Window setTitlebarAppearsTransparent:true]; + + auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); + + if (wantsTitleBar) + { + [StandardContainer ShowTitleBar:true]; + } + else + { + [StandardContainer ShowTitleBar:false]; + } + + if(_extendClientHints & AvnOSXThickTitleBar) + { + Window.toolbar = [NSToolbar new]; + Window.toolbar.showsBaselineSeparator = false; + } + else + { + Window.toolbar = nullptr; + } } else { + Window.titleVisibility = NSWindowTitleVisible; Window.toolbar = nullptr; + [Window setTitlebarAppearsTransparent:false]; + View.layer.zPosition = 0; } + + [Window setIsExtended:enable]; + + HideOrShowTrafficLights(); + + UpdateStyle(); + + return S_OK; } - else - { - Window.titleVisibility = NSWindowTitleVisible; - Window.toolbar = nullptr; - [Window setTitlebarAppearsTransparent:false]; - View.layer.zPosition = 0; - } - - [Window setIsExtended:enable]; - - HideOrShowTrafficLights(); - - UpdateStyle(); - - return S_OK; } virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override { - _extendClientHints = hints; + START_COM_CALL; - SetExtendClientArea(_isClientAreaExtended); - return S_OK; + @autoreleasepool + { + _extendClientHints = hints; + + SetExtendClientArea(_isClientAreaExtended); + return S_OK; + } } virtual HRESULT GetExtendTitleBarHeight (double*ret) override { - if(ret == nullptr) + START_COM_CALL; + + @autoreleasepool { - return E_POINTER; + if(ret == nullptr) + { + return E_POINTER; + } + + *ret = [Window getExtendedTitleBarHeight]; + + return S_OK; } - - *ret = [Window getExtendedTitleBarHeight]; - - return S_OK; } virtual HRESULT SetExtendTitleBarHeight (double value) override { - [StandardContainer SetTitleBarHeightHint:value]; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + [StandardContainer SetTitleBarHeightHint:value]; + return S_OK; + } } void EnterFullScreenMode () @@ -974,13 +1067,15 @@ private: virtual HRESULT SetWindowState (AvnWindowState state) override { - if(Window == nullptr) - { - return S_OK; - } + START_COM_CALL; @autoreleasepool { + if(Window == nullptr) + { + return S_OK; + } + if(_actualWindowState == state) { return S_OK; From 9da6a28b5161d11d346c6205fce9cbde1cc3629d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Jul 2021 14:21:18 +0100 Subject: [PATCH 58/84] fix --- native/Avalonia.Native/src/OSX/AvnString.mm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 6d057fb705..cd0e2cdf94 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -64,8 +64,14 @@ public: @autoreleasepool { - i@autoreleasepool - {eturn S_OK; + if(retOut == nullptr) + { + return E_POINTER; + } + + *retOut = _length; + + return S_OK; } } }; From 927fd90d860038f97c0581da5e1cc59132f0fa51 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Jul 2021 15:24:21 +0100 Subject: [PATCH 59/84] add documentation about START_COM_CALL. --- native/Avalonia.Native/inc/comimpl.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index 45a8e8690d..d38216b49e 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -7,6 +7,17 @@ #define COMIMPL_H_INCLUDED #include + +/** + START_COM_CALL causes AddRef to be called at the beggining of a function. + When a function is exited, it causes ReleaseRef to be called. + This ensures that the object cannot be destructed whilst the function is running. + For example: Window Show is called, which triggers an event, and user calls Close inside the event + causing the refcount to reach 0, and the object to be destroyed. Function then continues and this pointer + will now be invalid. + + START_COM_CALL protects against this scenario. + */ #define START_COM_CALL auto r = this->UnknownSelf() __IID_DEF(IUnknown, 0, 0, 0, C0, 00, 00, 00, 00, 00, 00, 46); From bac825c9995b8a48636e0f1eda81c3b9383a2041 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Jul 2021 15:33:44 +0100 Subject: [PATCH 60/84] destroyed, not destructed. --- native/Avalonia.Native/inc/comimpl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index d38216b49e..47b0a3c5f2 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -11,7 +11,7 @@ /** START_COM_CALL causes AddRef to be called at the beggining of a function. When a function is exited, it causes ReleaseRef to be called. - This ensures that the object cannot be destructed whilst the function is running. + This ensures that the object cannot be destroyed whilst the function is running. For example: Window Show is called, which triggers an event, and user calls Close inside the event causing the refcount to reach 0, and the object to be destroyed. Function then continues and this pointer will now be invalid. From 3b56b93ba1822149d495d560263679d7d14ab80e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 9 Jul 2021 13:42:46 +0200 Subject: [PATCH 61/84] Make the designer work in the sandbox project. --- samples/Sandbox/Program.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/Sandbox/Program.cs b/samples/Sandbox/Program.cs index 1e74105196..52321b46a9 100644 --- a/samples/Sandbox/Program.cs +++ b/samples/Sandbox/Program.cs @@ -4,12 +4,12 @@ namespace Sandbox { public class Program { - static void Main(string[] args) - { + static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() - .LogToTrace() - .StartWithClassicDesktopLifetime(args); - } + .LogToTrace(); } } From 96ae9b19b30aa5fe3afe9467fd57acdd5e8e997b Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 9 Jul 2021 16:23:53 +0200 Subject: [PATCH 62/84] Expose EnsureCapacity() on AvaloniaList --- src/Avalonia.Base/Collections/AvaloniaList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs index 5681214222..85b10d8af9 100644 --- a/src/Avalonia.Base/Collections/AvaloniaList.cs +++ b/src/Avalonia.Base/Collections/AvaloniaList.cs @@ -633,7 +633,7 @@ namespace Avalonia.Collections /// Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList(); - private void EnsureCapacity(int capacity) + public void EnsureCapacity(int capacity) { // Adapted from List implementation. var currentCapacity = _inner.Capacity; From 0864f3c562f70f7d9900914d388714360e18819d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miha=20Marki=C4=8D?= Date: Sat, 10 Jul 2021 14:24:32 +0200 Subject: [PATCH 63/84] Makes TextBox.UndoRedoState struct readonly --- src/Avalonia.Controls/TextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index c1516613b3..ddf55808ce 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -145,7 +145,7 @@ namespace Avalonia.Controls (o, v) => o.UndoLimit = v, unsetValue: -1); - struct UndoRedoState : IEquatable + readonly struct UndoRedoState : IEquatable { public string Text { get; } public int CaretPosition { get; } From cd54a9255df1e6f66c13cdf7a839df83d7b104dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miha=20Marki=C4=8D?= Date: Sat, 10 Jul 2021 14:30:28 +0200 Subject: [PATCH 64/84] Adds CodeRush directory to GIT ignored files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 7d672c7755..abf7674560 100644 --- a/.gitignore +++ b/.gitignore @@ -106,6 +106,9 @@ _NCrunch_*/ *.ncrunchsolution.user nCrunchTemp_* +# CodeRush +.cr/ + # Others sql/ *.Cache From 43083edd2f9380c39ed9d2089c57e9286c67e795 Mon Sep 17 00:00:00 2001 From: Royce551 Date: Sat, 10 Jul 2021 10:57:28 -0500 Subject: [PATCH 65/84] Unsubscribe before stopping timer --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 1207b7f2dc..2fd08ef77c 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -1024,8 +1024,8 @@ namespace Avalonia.Controls.Primitives return; } - _textSearchTimer.Stop(); _textSearchTimer.Tick -= TextSearchTimer_Tick; + _textSearchTimer.Stop(); _textSearchTimer = null; } From 17d9755c3f22f3a20b09996fc7d72e2ffd3f406e Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 10 Jul 2021 21:43:19 +0200 Subject: [PATCH 66/84] Update API compat. --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index c917902dc3..39a4c3004c 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -5,6 +5,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract. MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. +TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract. CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. @@ -73,4 +74,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. -Total Issues: 74 +Total Issues: 75 From e55c11ec37a1fb87fe7e376c86919f69936700ea Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 10 Jul 2021 21:57:53 +0200 Subject: [PATCH 67/84] Fix unit tests. --- .../StaticResourceExtensionTests.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index 0ee7384c7d..fb3fd6d7d4 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var border = userControl.FindControl("border"); - var brush = (SolidColorBrush)border.Background; + var brush = (ISolidColorBrush)border.Background; Assert.Equal(0xff506070, brush.Color.ToUint32()); } @@ -72,7 +72,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var border = userControl.FindControl("border"); - var brush = (SolidColorBrush)border.Background; + var brush = (ISolidColorBrush)border.Background; Assert.Equal(0xff506070, brush.Color.ToUint32()); } @@ -119,7 +119,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var border = userControl.FindControl("border"); - var brush = (SolidColorBrush)border.Background; + var brush = (ISolidColorBrush)border.Background; Assert.Equal(0xff506070, brush.Color.ToUint32()); } @@ -149,7 +149,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var border = userControl.FindControl("border"); - var brush = (SolidColorBrush)border.Background; + var brush = (ISolidColorBrush)border.Background; Assert.Equal(0xff506070, brush.Color.ToUint32()); } @@ -200,7 +200,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var button = window.FindControl