From aee66ef47117800ab23add607fda27b53658e7d8 Mon Sep 17 00:00:00 2001 From: malaguenha Date: Sat, 13 May 2023 15:02:56 +0900 Subject: [PATCH 01/93] Commit to solve the issue #11353, IME, Windows - buffer overrun? when entering long composition string --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 7510b48270..0fdc103d3a 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1902,7 +1902,7 @@ namespace Avalonia.Win32.Interop if (bufferLength > 0) { - var buffer = bufferLength <= 64 ? stackalloc byte[bufferLength] : new byte[bufferLength]; + var buffer = bufferLength <= 64 ? stackalloc byte[bufferLength + 8] : new byte[bufferLength]; fixed (byte* bufferPtr = buffer) { From 0479dffaa5735fa40a0e041a6781443afc026f9a Mon Sep 17 00:00:00 2001 From: malaguenha Date: Sat, 13 May 2023 20:39:12 +0900 Subject: [PATCH 02/93] Fix using Encoding.Unicode.GetString --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 0fdc103d3a..af47890b70 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1909,7 +1909,7 @@ namespace Avalonia.Win32.Interop var result = ImmGetCompositionString(hIMC, dwIndex, (IntPtr)bufferPtr, (uint)bufferLength); if (result >= 0) { - return Marshal.PtrToStringUni((IntPtr)bufferPtr); + return Encoding.Unicode.GetString(buffer.ToArray()); } } } From f90971204be74a4f79ea54b2436f8994b48995d6 Mon Sep 17 00:00:00 2001 From: malaguenha Date: Sat, 13 May 2023 20:46:49 +0900 Subject: [PATCH 03/93] No extra buffer --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index af47890b70..d5eff1611c 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1902,7 +1902,7 @@ namespace Avalonia.Win32.Interop if (bufferLength > 0) { - var buffer = bufferLength <= 64 ? stackalloc byte[bufferLength + 8] : new byte[bufferLength]; + var buffer = bufferLength <= 64 ? stackalloc byte[bufferLength] : new byte[bufferLength]; fixed (byte* bufferPtr = buffer) { From dd1cffcfdaf30de8a725f78efbf53f4ed9351657 Mon Sep 17 00:00:00 2001 From: malaguenha Date: Sat, 13 May 2023 22:28:16 +0900 Subject: [PATCH 04/93] Fixed to use pointer not array --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index d5eff1611c..f26c783e47 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1909,7 +1909,7 @@ namespace Avalonia.Win32.Interop var result = ImmGetCompositionString(hIMC, dwIndex, (IntPtr)bufferPtr, (uint)bufferLength); if (result >= 0) { - return Encoding.Unicode.GetString(buffer.ToArray()); + return Encoding.Unicode.GetString(bufferPtr, bufferLength); } } } From 4314a8756868b41ff449c4c6742bccb8cf77efb7 Mon Sep 17 00:00:00 2001 From: malaguenha Date: Sat, 13 May 2023 22:49:34 +0900 Subject: [PATCH 05/93] Just one more correction --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f26c783e47..39363beae3 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1909,7 +1909,7 @@ namespace Avalonia.Win32.Interop var result = ImmGetCompositionString(hIMC, dwIndex, (IntPtr)bufferPtr, (uint)bufferLength); if (result >= 0) { - return Encoding.Unicode.GetString(bufferPtr, bufferLength); + return Encoding.Unicode.GetString(bufferPtr, result); } } } From 5c74e2c32bc401cc26b8d80f61e7e57ea4804c34 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 13 May 2023 14:26:35 -0400 Subject: [PATCH 06/93] Add RangeBase.ValueChanged event --- .../RoutedPropertyChangedEventArgs.cs | 46 +++++++++++++++++++ src/Avalonia.Controls/Primitives/RangeBase.cs | 37 ++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Base/Interactivity/RoutedPropertyChangedEventArgs.cs diff --git a/src/Avalonia.Base/Interactivity/RoutedPropertyChangedEventArgs.cs b/src/Avalonia.Base/Interactivity/RoutedPropertyChangedEventArgs.cs new file mode 100644 index 0000000000..22134b518d --- /dev/null +++ b/src/Avalonia.Base/Interactivity/RoutedPropertyChangedEventArgs.cs @@ -0,0 +1,46 @@ +namespace Avalonia.Interactivity +{ + /// + /// Provides both old and new property values with a routed event. + /// + /// The type of values. + public class RoutedPropertyChangedEventArgs : RoutedEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The old property value. + /// The new property value. + /// The routed event associated with these event args. + public RoutedPropertyChangedEventArgs(T oldValue, T newValue, RoutedEvent? routedEvent) + : base(routedEvent) + { + OldValue = oldValue; + NewValue = newValue; + } + + /// + /// Initializes a new instance of the class. + /// + /// The old property value. + /// The new property value. + /// The routed event associated with these event args. + /// The source object that raised the routed event. + public RoutedPropertyChangedEventArgs(T oldValue, T newValue, RoutedEvent? routedEvent, object? source) + : base(routedEvent, source) + { + OldValue = oldValue; + NewValue = newValue; + } + + /// + /// Gets the old value of the property. + /// + public T OldValue { get; init; } + + /// + /// Gets the new value of the property. + /// + public T NewValue { get; init; } + } +} diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs index fd9de47236..ebf7879412 100644 --- a/src/Avalonia.Controls/Primitives/RangeBase.cs +++ b/src/Avalonia.Controls/Primitives/RangeBase.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Data; +using Avalonia.Interactivity; using Avalonia.Utilities; namespace Avalonia.Controls.Primitives @@ -42,7 +43,23 @@ namespace Avalonia.Controls.Primitives AvaloniaProperty.Register(nameof(LargeChange), 10); /// - /// Gets or sets the minimum value. + /// Defines the event. + /// + public static readonly RoutedEvent> ValueChangedEvent = + RoutedEvent.Register>( + nameof(ValueChanged), RoutingStrategies.Bubble); + + /// + /// Occurs when the property changes. + /// + public event EventHandler>? ValueChanged + { + add => AddHandler(ValueChangedEvent, value); + remove => RemoveHandler(ValueChangedEvent, value); + } + + /// + /// Gets or sets the minimum possible value. /// public double Minimum { @@ -65,7 +82,7 @@ namespace Avalonia.Controls.Primitives } /// - /// Gets or sets the maximum value. + /// Gets or sets the maximum possible value. /// public double Maximum { @@ -104,18 +121,25 @@ namespace Avalonia.Controls.Primitives : sender.GetValue(ValueProperty); } + /// + /// Gets or sets the small increment value added or subtracted from the . + /// public double SmallChange { get => GetValue(SmallChangeProperty); set => SetValue(SmallChangeProperty, value); } + /// + /// Gets or sets the large increment value added or subtracted from the . + /// public double LargeChange { get => GetValue(LargeChangeProperty); set => SetValue(LargeChangeProperty, value); } + /// protected override void OnInitialized() { base.OnInitialized(); @@ -124,6 +148,7 @@ namespace Avalonia.Controls.Primitives CoerceValue(ValueProperty); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -136,6 +161,14 @@ namespace Avalonia.Controls.Primitives { OnMaximumChanged(); } + else if (change.Property == ValueProperty) + { + var valueChangedEventArgs = new RoutedPropertyChangedEventArgs( + change.GetOldValue(), + change.GetNewValue(), + ValueChangedEvent); + RaiseEvent(valueChangedEventArgs); + } } /// From d9b11b0725552e87c908a42bc274be5723a7881b Mon Sep 17 00:00:00 2001 From: Tom Edwards Date: Sat, 13 May 2023 15:09:18 +0200 Subject: [PATCH 07/93] Added tests and control catalog demo for live changes to TabItem.ContentTemplate. The next commit will fix both. --- .../ControlCatalog/Pages/TabControlPage.xaml | 29 ++++++++++++++++--- .../TabControlTests.cs | 21 ++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml index 3a2464e9fd..1faa74c1ce 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -4,7 +4,27 @@ xmlns="https://github.com/avaloniaui" xmlns:viewModels="using:ControlCatalog.ViewModels" x:DataType="viewModels:TabControlPageViewModel"> - + + + + + + - + - + - - - - - - - - + + + + + + + + - @@ -239,16 +238,17 @@ + - - - - + + + + diff --git a/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml index a5b0649655..bd5151ad1a 100644 --- a/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml @@ -49,7 +49,7 @@ TargetType="dialogs:ManagedFileChooser"> - + - - + + @@ -136,7 +136,6 @@ DockPanel.Dock="Top"> - @@ -144,19 +143,45 @@ + - - - - + + - - + + + - - + + + + + - @@ -177,19 +201,20 @@ + - - - - + From 81084454f4aef7d5ea6b9bfe8ad0491b44071cf7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 May 2023 16:02:54 +0200 Subject: [PATCH 63/93] Added failing tests for #11484. --- .../AvaloniaObjectTests_Coercion.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs index 0d0456dbda..42720cbb4c 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs @@ -124,6 +124,19 @@ namespace Avalonia.Base.UnitTests Assert.Equal(2, target.CoreChanges.Count); } + [Fact] + public void CoerceValue_Calls_Coerce_Callback_Only_Once() + { + var target = new Class1 { Foo = 99 }; + + target.MaxFoo = 50; + + target.CoerceFooInvocations.Clear(); + target.CoerceValue(Class1.FooProperty); + + Assert.Equal(new[] { 99 }, target.CoerceFooInvocations); + } + [Fact] public void Coerced_Value_Can_Be_Restored_If_Limit_Changed() { @@ -218,6 +231,18 @@ namespace Avalonia.Base.UnitTests Assert.Equal(1, raised); } + [Fact] + public void Default_Value_Is_Coerced_Only_Once() + { + var target = new Class1(); + + target.MinFoo = 20; + target.CoerceFooInvocations.Clear(); + target.CoerceValue(Class1.FooProperty); + + Assert.Equal(new[] { 11 }, target.CoerceFooInvocations); + } + [Fact] public void ClearValue_Respects_Coerced_Default_Value() { @@ -338,10 +363,12 @@ namespace Avalonia.Base.UnitTests public int MinFoo { get; set; } = 0; public int MaxFoo { get; set; } = 100; + public List CoerceFooInvocations { get; } = new(); public List CoreChanges { get; } = new(); public static int CoerceFoo(AvaloniaObject instance, int value) { + (instance as Class1)?.CoerceFooInvocations.Add(value); return instance is Class1 o ? Math.Clamp(value, o.MinFoo, o.MaxFoo) : Math.Clamp(value, 0, 100); From 531f059c76f9d3a937fa3afcf6d21eea430c3399 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 May 2023 16:10:03 +0200 Subject: [PATCH 64/93] Only call coerce callback a single time. --- src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index b725326855..4518289335 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -208,7 +208,7 @@ namespace Avalonia.PropertyStore IsOverridenCurrentValue = isOverriddenCurrentValue; IsCoercedDefaultValue = isCoercedDefaultValue; - if (_uncommon?._coerce is { } coerce) + if (!isCoercedDefaultValue && _uncommon?._coerce is { } coerce) v = coerce(owner.Owner, value); if (priority <= Priority) @@ -262,7 +262,8 @@ namespace Avalonia.PropertyStore if (_uncommon?._coerce is { } coerce) { v = coerce(owner.Owner, value); - bv = coerce(owner.Owner, baseValue); + if (priority != basePriority) + bv = coerce(owner.Owner, baseValue); } if (!EqualityComparer.Default.Equals(Value, v)) From 1d0ac58755f68a973913e8a17387f2d86616411b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 May 2023 19:54:51 +0100 Subject: [PATCH 65/93] add repro for popup transparency pointer event issue. --- .../ControlCatalog/Pages/CheckBoxPage.xaml | 43 +++++++++---------- .../ControlCatalog/Pages/CheckBoxPage.xaml.cs | 15 ++++++- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml b/samples/ControlCatalog/Pages/CheckBoxPage.xaml index 2f60fc5dae..78a8ff5550 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml @@ -1,28 +1,25 @@ - - A check box control + x:Class="ControlCatalog.Pages.CheckBoxPage" Background="White"> + + + + + + + + - - - _Unchecked - _Checked - _Indeterminate - Disabled - - - Three State: Unchecked - Three State: Checked - Three State: Indeterminate - Three State: Disabled + + + + + + Popup Text + + + - - + + diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs index 5027c94f5e..c3066c55f9 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs @@ -1,18 +1,31 @@ using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Markup.Xaml; namespace ControlCatalog.Pages { - public class CheckBoxPage : UserControl + public partial class CheckBoxPage : UserControl { + private TextBlock myTb; + public CheckBoxPage() { this.InitializeComponent(); + + myTb = this.FindControl("myTb"); + } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + + protected override void OnPointerMoved(PointerEventArgs e) + { + base.OnPointerMoved(e); + + myTb.Text = e.GetPosition(this).ToString(); + } } } From a9e16dc89069f6baa5b4aa24fec3af942c542a2a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 May 2023 20:00:06 +0100 Subject: [PATCH 66/93] update repro --- samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs | 3 ++- src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs index c3066c55f9..60e75561e1 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs @@ -7,6 +7,7 @@ namespace ControlCatalog.Pages public partial class CheckBoxPage : UserControl { private TextBlock myTb; + private int count; public CheckBoxPage() { @@ -25,7 +26,7 @@ namespace ControlCatalog.Pages { base.OnPointerMoved(e); - myTb.Text = e.GetPosition(this).ToString(); + myTb.Text = e.GetPosition(this).ToString() + ", " + count++; } } } diff --git a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml index 5a924830b1..16be9630c1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml @@ -13,6 +13,7 @@ + + From 7aa531530be53bd70c6a33d64d951097838e6593 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 May 2023 20:25:55 +0100 Subject: [PATCH 67/93] implent popup routing events to parent toplevel. --- src/Avalonia.Base/Input/PointerEventArgs.cs | 11 ++++++++++- .../Controls/PopupRoot.xaml | 18 +++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index 7f82199b56..0556d8702f 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -70,7 +70,16 @@ namespace Avalonia.Input if (relativeTo == null) return pt; - return pt * _rootVisual.TransformToVisual(relativeTo) ?? default; + if (!ReferenceEquals(_rootVisual, relativeTo.VisualRoot)) + { + var screenPt = _rootVisual.PointToScreen(pt); + + return relativeTo.PointToClient(screenPt); + } + else + { + return pt * _rootVisual.TransformToVisual(relativeTo) ?? default; + } } /// diff --git a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml index 16be9630c1..567c9b72e7 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml @@ -13,15 +13,15 @@ - - - - - + + + + + From d064ad4e6ab1dd12638b1b249953403a0a574512 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 May 2023 20:34:04 +0100 Subject: [PATCH 68/93] transform to visual. --- src/Avalonia.Base/Input/PointerEventArgs.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index 0556d8702f..a53791e414 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -70,16 +70,14 @@ namespace Avalonia.Input if (relativeTo == null) return pt; - if (!ReferenceEquals(_rootVisual, relativeTo.VisualRoot)) + if (!ReferenceEquals(_rootVisual, relativeTo.VisualRoot) && relativeTo.VisualRoot is Visual v) { var screenPt = _rootVisual.PointToScreen(pt); - return relativeTo.PointToClient(screenPt); - } - else - { - return pt * _rootVisual.TransformToVisual(relativeTo) ?? default; + return relativeTo.PointToClient(screenPt) * v.TransformToVisual(relativeTo) ?? default; } + + return pt * _rootVisual.TransformToVisual(relativeTo) ?? default; } /// From ba8ddd04189f95ec35711019d1c90f5c605a649b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 May 2023 20:37:14 +0100 Subject: [PATCH 69/93] no need for transformtovisual. --- src/Avalonia.Base/Input/PointerEventArgs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index a53791e414..5db9266d06 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -74,7 +74,7 @@ namespace Avalonia.Input { var screenPt = _rootVisual.PointToScreen(pt); - return relativeTo.PointToClient(screenPt) * v.TransformToVisual(relativeTo) ?? default; + return relativeTo.PointToClient(screenPt); } return pt * _rootVisual.TransformToVisual(relativeTo) ?? default; From d24903957eb27ca89670c28e5f4d34b8e123e58f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 23 May 2023 20:40:25 +0100 Subject: [PATCH 70/93] revert control catalog changes. --- .../ControlCatalog/Pages/CheckBoxPage.xaml | 43 ++++++++++--------- .../ControlCatalog/Pages/CheckBoxPage.xaml.cs | 16 +------ 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml b/samples/ControlCatalog/Pages/CheckBoxPage.xaml index 78a8ff5550..2f60fc5dae 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml @@ -1,25 +1,28 @@ - - - - - - - - + x:Class="ControlCatalog.Pages.CheckBoxPage"> + + A check box control - - - - - - Popup Text - - - + + + _Unchecked + _Checked + _Indeterminate + Disabled + + + Three State: Unchecked + Three State: Checked + Three State: Indeterminate + Three State: Disabled - - + + diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs index 60e75561e1..5027c94f5e 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs @@ -1,32 +1,18 @@ using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Markup.Xaml; namespace ControlCatalog.Pages { - public partial class CheckBoxPage : UserControl + public class CheckBoxPage : UserControl { - private TextBlock myTb; - private int count; - public CheckBoxPage() { this.InitializeComponent(); - - myTb = this.FindControl("myTb"); - } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } - - protected override void OnPointerMoved(PointerEventArgs e) - { - base.OnPointerMoved(e); - - myTb.Text = e.GetPosition(this).ToString() + ", " + count++; - } } } From 2ac8849fb0d2799f021cef2db216937c367a1bec Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 24 May 2023 04:05:53 -0400 Subject: [PATCH 71/93] Fix Browser not wrapping file handles into StorageItem object --- src/Browser/Avalonia.Browser/Interop/StorageHelper.cs | 3 +++ .../Avalonia.Browser/Storage/BrowserStorageProvider.cs | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs index d95d4405ba..c28efbb308 100644 --- a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs @@ -39,6 +39,9 @@ internal static partial class StorageHelper [JSImport("StorageItem.openRead", AvaloniaModule.StorageModuleName)] public static partial Task OpenRead(JSObject item); + + [JSImport("StorageItem.createFromHandle", AvaloniaModule.StorageModuleName)] + public static partial JSObject? StorageItemFromHandle(JSObject handle); [JSImport("StorageItem.getItemsIterator", AvaloniaModule.StorageModuleName)] [return: JSMarshalAs] diff --git a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs index a28fd4cbde..ef34826238 100644 --- a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs +++ b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs @@ -319,13 +319,14 @@ internal class JSStorageFolder : JSStorageItem, IStorageBookmarkFolder } var kind = storageItem.GetPropertyAsString("kind"); + var item = StorageHelper.StorageItemFromHandle(storageItem)!; switch (kind) { case "directory": - yield return new JSStorageFolder(storageItem); + yield return new JSStorageFolder(item); break; case "file": - yield return new JSStorageFile(storageItem); + yield return new JSStorageFile(item); break; } } From 6c5c13aa5cb7958d2a97337612665d4ee71439c6 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 24 May 2023 04:22:23 -0400 Subject: [PATCH 72/93] Fix ICustomDrawOperation and browser splash screen --- .../Composition/Drawing/RenderDataDrawingContext.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs index 3d5033086e..971ae1d8aa 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs @@ -177,7 +177,10 @@ internal class RenderDataDrawingContext : DrawingContext }); } - public override void Custom(ICustomDrawOperation custom) => Add(new RenderDataCustomNode()); + public override void Custom(ICustomDrawOperation custom) => Add(new RenderDataCustomNode + { + Operation = custom + }); public override void DrawGlyphRun(IBrush? foreground, GlyphRun? glyphRun) { From 7640434affc3c5e82be0e258528ddacea5e897b6 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 24 May 2023 08:39:56 +0000 Subject: [PATCH 73/93] use MinWidth to set button widths --- src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml | 4 ++-- src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml index e903e7d3a8..8519099a27 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml @@ -179,8 +179,8 @@ - - + + diff --git a/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml index bd5151ad1a..cc049938ad 100644 --- a/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml @@ -93,8 +93,8 @@ - - + + From 10b89ac13a3d7ac24a598b37427bd6c8ac65dd10 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 24 May 2023 11:19:15 +0100 Subject: [PATCH 74/93] Revert "revert control catalog changes." This reverts commit d24903957eb27ca89670c28e5f4d34b8e123e58f. --- .../ControlCatalog/Pages/CheckBoxPage.xaml | 43 +++++++++---------- .../ControlCatalog/Pages/CheckBoxPage.xaml.cs | 16 ++++++- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml b/samples/ControlCatalog/Pages/CheckBoxPage.xaml index 2f60fc5dae..78a8ff5550 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml @@ -1,28 +1,25 @@ - - A check box control + x:Class="ControlCatalog.Pages.CheckBoxPage" Background="White"> + + + + + + + + - - - _Unchecked - _Checked - _Indeterminate - Disabled - - - Three State: Unchecked - Three State: Checked - Three State: Indeterminate - Three State: Disabled + + + + + + Popup Text + + + - - + + diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs index 5027c94f5e..60e75561e1 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs @@ -1,18 +1,32 @@ using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Markup.Xaml; namespace ControlCatalog.Pages { - public class CheckBoxPage : UserControl + public partial class CheckBoxPage : UserControl { + private TextBlock myTb; + private int count; + public CheckBoxPage() { this.InitializeComponent(); + + myTb = this.FindControl("myTb"); + } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + + protected override void OnPointerMoved(PointerEventArgs e) + { + base.OnPointerMoved(e); + + myTb.Text = e.GetPosition(this).ToString() + ", " + count++; + } } } From 966c5a0c5e2599aaf87a70ce196686320daa77e3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 25 May 2023 16:14:06 +0200 Subject: [PATCH 75/93] Added ControlAutomationPeer.FromElement. --- .../Automation/Peers/ControlAutomationPeer.cs | 20 +++++++++++++++++++ src/Avalonia.Controls/Control.cs | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index c55bd0f3e5..756951667b 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -31,11 +31,31 @@ namespace Avalonia.Automation.Peers return CreatePeerForElement(element); } + /// + /// Gets the for a , creating it if + /// necessary. + /// + /// The control. + /// The automation peer. + /// + /// Despite the name (which comes from the analogous WPF API), this method does not create + /// a new peer if one already exists: instead it returns the existing peer. + /// public static AutomationPeer CreatePeerForElement(Control element) { return element.GetOrCreateAutomationPeer(); } + /// + /// Gets an existing for a . + /// + /// The control. + /// The automation peer if already created; otherwise null. + /// + /// To ensure that a peer is created, use . + /// + public static AutomationPeer? FromElement(Control element) => element.GetAutomationPeer(); + protected override void BringIntoViewCore() => Owner.BringIntoView(); protected override IReadOnlyList GetOrCreateChildrenCore() diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 13e3978ca6..8d140bdd62 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -440,6 +440,12 @@ namespace Avalonia.Controls return new NoneAutomationPeer(this); } + internal AutomationPeer? GetAutomationPeer() + { + VerifyAccess(); + return _automationPeer; + } + internal AutomationPeer GetOrCreateAutomationPeer() { VerifyAccess(); From 215707ba7e30ad709d4d9adae2950376c7c9ee8a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 May 2023 19:44:52 +0100 Subject: [PATCH 76/93] add comments explaining and remove unnecessary condition. --- src/Avalonia.Base/Input/PointerEventArgs.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index 5db9266d06..383c807a73 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -70,10 +70,14 @@ namespace Avalonia.Input if (relativeTo == null) return pt; - if (!ReferenceEquals(_rootVisual, relativeTo.VisualRoot) && relativeTo.VisualRoot is Visual v) + // If the visual the user passed in, is not connected to the same visual root + // (i.e. they called it for a control inside a popup. + if (!ReferenceEquals(_rootVisual, relativeTo.VisualRoot)) { + // Convert to absolute screen coordinates. var screenPt = _rootVisual.PointToScreen(pt); + // Convert to client co-ordinates of the visual inside the other visual root. return relativeTo.PointToClient(screenPt); } From b3869981701114356fbb1b2fac6fdcbbf7757250 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 May 2023 19:45:06 +0100 Subject: [PATCH 77/93] remove template change. --- src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml index 567c9b72e7..87e954b630 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml @@ -13,7 +13,6 @@ - - From b7568940c82848621bdcffa9b37af6665f75c821 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 May 2023 19:48:20 +0100 Subject: [PATCH 78/93] revert changes. --- .../ControlCatalog/Pages/CheckBoxPage.xaml | 43 ++++++++++--------- .../ControlCatalog/Pages/CheckBoxPage.xaml.cs | 16 +------ .../Controls/PopupRoot.xaml | 14 +++--- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml b/samples/ControlCatalog/Pages/CheckBoxPage.xaml index 78a8ff5550..2f60fc5dae 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml @@ -1,25 +1,28 @@ - - - - - - - - + x:Class="ControlCatalog.Pages.CheckBoxPage"> + + A check box control - - - - - - Popup Text - - - + + + _Unchecked + _Checked + _Indeterminate + Disabled + + + Three State: Unchecked + Three State: Checked + Three State: Indeterminate + Three State: Disabled - - + + diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs index 60e75561e1..5027c94f5e 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs @@ -1,32 +1,18 @@ using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Markup.Xaml; namespace ControlCatalog.Pages { - public partial class CheckBoxPage : UserControl + public class CheckBoxPage : UserControl { - private TextBlock myTb; - private int count; - public CheckBoxPage() { this.InitializeComponent(); - - myTb = this.FindControl("myTb"); - } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } - - protected override void OnPointerMoved(PointerEventArgs e) - { - base.OnPointerMoved(e); - - myTb.Text = e.GetPosition(this).ToString() + ", " + count++; - } } } diff --git a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml index 87e954b630..5a924830b1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml @@ -13,13 +13,13 @@ - - - + + + From e2511344b8225817381db31af3c7e08f9a3ba4b0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 May 2023 20:37:37 +0100 Subject: [PATCH 79/93] fix tests. --- src/Avalonia.Base/Input/PointerEventArgs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index 383c807a73..9c24e5c314 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -72,7 +72,7 @@ namespace Avalonia.Input // If the visual the user passed in, is not connected to the same visual root // (i.e. they called it for a control inside a popup. - if (!ReferenceEquals(_rootVisual, relativeTo.VisualRoot)) + if (!ReferenceEquals(_rootVisual, relativeTo.VisualRoot) && relativeTo.VisualRoot is { }) { // Convert to absolute screen coordinates. var screenPt = _rootVisual.PointToScreen(pt); From d94c0670c82a321f82981607a2afdb61f7aa3e01 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Fri, 26 May 2023 00:59:35 +0200 Subject: [PATCH 80/93] Log DBus Exceptions --- .../ControlCatalog/Pages/DialogsPage.xaml.cs | 2 +- .../DBusIme/DBusTextInputMethodBase.cs | 3 +++ .../DBusIme/Fcitx/FcitxX11TextInputMethod.cs | 4 ++++ .../DBusIme/IBus/IBusX11TextInputMethod.cs | 7 +++++++ src/Avalonia.FreeDesktop/DBusSystemDialog.cs | 18 +++++++++++------- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 971a6a6718..4d617fd313 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -307,7 +307,7 @@ namespace ControlCatalog.Pages Content: "; - resultText += await ReadTextFromFile(file, 10000); + resultText += await ReadTextFromFile(file, 500); } openedFileContent.Text = resultText; diff --git a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs index d3c14f285d..936e856cf0 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs @@ -76,7 +76,10 @@ namespace Avalonia.FreeDesktop.DBusIme private async void OnNameChange(Exception? e, (string ServiceName, string? OldOwner, string? NewOwner) args) { if (e is not null) + { + Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnNameChange failed: {e}"); return; + } if (args.NewOwner is not null && _currentName is null) { diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs index 1cf3507cc2..2eca5a1fef 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; +using Avalonia.Logging; using Tmds.DBus.Protocol; using Tmds.DBus.SourceGenerator; @@ -138,7 +139,10 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx private void OnCommitString(Exception? e, string s) { if (e is not null) + { + Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnCommitString failed: {e}"); return; + } FireCommit(s); } diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs index 59e9ecd1cf..26fa971106 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; +using Avalonia.Logging; using Tmds.DBus.Protocol; using Tmds.DBus.SourceGenerator; @@ -31,7 +32,10 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus private void OnForwardKey(Exception? e, (uint keyval, uint keycode, uint state) k) { if (e is not null) + { + Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnForwardKey failed: {e}"); return; + } var state = (IBusModifierMask)k.state; KeyModifiers mods = default; @@ -54,7 +58,10 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus private void OnCommitText(Exception? e, DBusVariantItem variantItem) { if (e is not null) + { + Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnCommitText failed: {e}"); return; + } if (variantItem.Value is DBusStructItem { Count: >= 3 } structItem && structItem[2] is DBusStringItem stringItem) FireCommit(stringItem.Value); diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs index cd6f829d7a..f6964ab92a 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using Avalonia.Platform; using Avalonia.Platform.Storage; @@ -66,8 +67,9 @@ namespace Avalonia.FreeDesktop using var disposable = await request.WatchResponseAsync((e, x) => { if (e is not null) - return; - tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); + tsc.TrySetException(e); + else + tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); }); var uris = await tsc.Task ?? Array.Empty(); @@ -86,7 +88,7 @@ namespace Avalonia.FreeDesktop if (options.SuggestedFileName is { } currentName) chooserOptions.Add("current_name", new DBusVariantItem("s", new DBusStringItem(currentName))); if (options.SuggestedStartLocation?.TryGetLocalPath() is { } folderPath) - chooserOptions.Add("current_folder", new DBusVariantItem("s", new DBusStringItem(folderPath))); + chooserOptions.Add("current_folder", new DBusVariantItem("ay", new DBusArrayItem(DBusType.Byte, Encoding.UTF8.GetBytes(folderPath).Select(static x => new DBusByteItem(x))))); objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); var request = new OrgFreedesktopPortalRequest(_connection, "org.freedesktop.portal.Desktop", objectPath); @@ -94,8 +96,9 @@ namespace Avalonia.FreeDesktop using var disposable = await request.WatchResponseAsync((e, x) => { if (e is not null) - return; - tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); + tsc.TrySetException(e); + else + tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); }); var uris = await tsc.Task; @@ -124,8 +127,9 @@ namespace Avalonia.FreeDesktop using var disposable = await request.WatchResponseAsync((e, x) => { if (e is not null) - return; - tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); + tsc.TrySetException(e); + else + tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); }); var uris = await tsc.Task ?? Array.Empty(); From ca046c06f57ad6df5b80ff2751dcbec8e6dffbde Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Fri, 26 May 2023 00:59:57 +0200 Subject: [PATCH 81/93] Bump Tmds.DBus.SourceGenerator --- src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index 31b65dcc02..779c41757a 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -13,7 +13,7 @@ - + From a78c41dd41a8dc699dbc64dafe5b535f62f5062c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 May 2023 11:20:01 +0100 Subject: [PATCH 82/93] add a unit test. --- .../Primitives/PopupTests.cs | 70 ++++++++++++++++++- .../MockWindowingPlatform.cs | 17 +++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 51399d1202..e307a80441 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -19,6 +19,7 @@ using Avalonia.Rendering; using System.Threading.Tasks; using Avalonia.Threading; using Avalonia.Interactivity; +using Avalonia.Media; namespace Avalonia.Controls.UnitTests.Primitives { @@ -1076,10 +1077,77 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void GetPosition_On_Control_In_Popup_Called_From_Parent_Should_Return_Valid_Coordinates() + { + using (CreateServices()) + { + var popupContent = new Border() { Height = 100, Width = 100, Background = Brushes.Red }; + var popup = new Popup { Child = popupContent, HorizontalOffset = 40, VerticalOffset = 40, Placement = PlacementMode.AnchorAndGravity, + PlacementAnchor = PopupAnchor.TopLeft, PlacementGravity = PopupGravity.BottomRight}; + var popupParent = new Border { Child = popup }; + var root = PreparedWindow(popupParent); + + var raised = 0; + + root.LayoutManager.ExecuteInitialLayoutPass(); + popup.Open(); + root.LayoutManager.ExecuteLayoutPass(); + + var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); + + var pts = popupContent.PointToScreen(new Point(10, 10)); + + Assert.Equal(new PixelPoint(50, 50), pts); + + var ev = new PointerPressedEventArgs( + popupContent, + pointer, + popupContent.VisualRoot as PopupRoot, + new Point(50 , 50), + 0, + new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), + KeyModifiers.None); + + Point pointRelativeToWindowContent = default; + + popupParent.AddHandler(Button.PointerPressedEvent, (s, e) => + { + ++raised; + + pointRelativeToWindowContent = e.GetPosition(popupParent); + }); + + popupContent.RaiseEvent(ev); + + Assert.Equal(1, raised); + Assert.Equal(new Point(90, 90), pointRelativeToWindowContent); + } + } + + private static PopupRoot CreateRoot(TopLevel popupParent, IPopupImpl impl = null) + { + impl ??= popupParent.PlatformImpl.CreatePopup(); + + var result = new PopupRoot(popupParent, impl) + { + Template = new FuncControlTemplate((parent, scope) => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = parent[!PopupRoot.ContentProperty], + }.RegisterInNameScope(scope)), + }; + + result.ApplyTemplate(); + + return result; + } + private IDisposable CreateServices() { return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: - new MockWindowingPlatform(null, + new MockWindowingPlatform(() => MockWindowingPlatform.CreateWindowMock().Object, x => { if(UsePopupHost) diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index ca71a97a6e..65612e45a0 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -55,6 +55,12 @@ namespace Avalonia.UnitTests windowImpl.Object.PositionChanged?.Invoke(x); }); + windowImpl.Setup(x => x.PointToScreen(It.IsAny())) + .Returns((point) => PixelPoint.FromPoint(point, 1) + position); + + windowImpl.Setup(x => x.PointToClient(It.IsAny())) + .Returns(point => (point - position).ToPoint(1)); + windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny())) .Callback((x, y) => { @@ -83,10 +89,12 @@ namespace Avalonia.UnitTests { var popupImpl = new Mock(); var clientSize = new Size(); + var position = new PixelPoint(); var positionerHelper = new ManagedPopupPositionerPopupImplHelper(parent, (pos, size, scale) => { clientSize = size.Constrain(s_screenSize); + position = pos; popupImpl.Object.PositionChanged?.Invoke(pos); popupImpl.Object.Resized?.Invoke(clientSize, WindowResizeReason.Unspecified); }); @@ -100,6 +108,15 @@ namespace Avalonia.UnitTests popupImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); popupImpl.Setup(x => x.RenderScaling).Returns(1); popupImpl.Setup(x => x.PopupPositioner).Returns(positioner); + popupImpl.Setup(x => x.Position).Returns(()=>position); + + popupImpl.Setup(x => x.PointToScreen(It.IsAny())) + .Returns((point) => PixelPoint.FromPoint(point, 1) + position); + + popupImpl.Setup(x => x.PointToClient(It.IsAny())) + .Returns(point => (point - position).ToPoint(1)); + + popupImpl.Setup(r => r.TryGetFeature(It.IsAny())).Returns(null); popupImpl.Setup(x => x.Dispose()).Callback(() => From 460ea0e093535966c100b80c3b96338d43bafa16 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 26 May 2023 13:24:27 +0200 Subject: [PATCH 83/93] Rework TextLine GetTextBounds and GetDistanceFromCharacterHit Fix TextAlignment.Right offset calculation --- src/Avalonia.Base/Media/CharacterHit.cs | 1 + .../Media/TextFormatting/BidiReorderer.cs | 74 ++- .../Media/TextFormatting/IndexedTextRun.cs | 10 + .../Media/TextFormatting/TextBounds.cs | 2 + .../Media/TextFormatting/TextLineImpl.cs | 594 +++++++----------- .../Media/TextFormatting/TextLineTests.cs | 49 ++ 6 files changed, 328 insertions(+), 402 deletions(-) create mode 100644 src/Avalonia.Base/Media/TextFormatting/IndexedTextRun.cs diff --git a/src/Avalonia.Base/Media/CharacterHit.cs b/src/Avalonia.Base/Media/CharacterHit.cs index 6bbbff4f5b..27cf3a42dc 100644 --- a/src/Avalonia.Base/Media/CharacterHit.cs +++ b/src/Avalonia.Base/Media/CharacterHit.cs @@ -19,6 +19,7 @@ namespace Avalonia.Media /// Index of the first character that got hit. /// In the case of a leading edge, this value is 0. In the case of a trailing edge, /// this value is the number of code points until the next valid caret position. + [DebuggerStepThrough] public CharacterHit(int firstCharacterIndex, int trailingLength = 0) { FirstCharacterIndex = firstCharacterIndex; diff --git a/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs b/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs index 4db55fae6d..39ef8cce48 100644 --- a/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs +++ b/src/Avalonia.Base/Media/TextFormatting/BidiReorderer.cs @@ -18,14 +18,14 @@ namespace Avalonia.Media.TextFormatting public static BidiReorderer Instance => t_instance ??= new(); - public void BidiReorder(Span textRuns, FlowDirection flowDirection) + public IndexedTextRun[] BidiReorder(Span textRuns, FlowDirection flowDirection, int firstTextSourceIndex) { Debug.Assert(_runs.Length == 0); Debug.Assert(_ranges.Length == 0); if (textRuns.IsEmpty) { - return; + return Array.Empty(); } try @@ -46,6 +46,22 @@ namespace Avalonia.Media.TextFormatting // Reorder them into visual order. var firstIndex = LinearReorder(); + var indexedTextRuns = new IndexedTextRun[textRuns.Length]; + + for (var i = 0; i < textRuns.Length; i++) + { + var currentRun = textRuns[i]; + + indexedTextRuns[i] = new IndexedTextRun + { + TextRun = currentRun, + TextSourceCharacterIndex = firstTextSourceIndex, + RunIndex = i, + NextRunIndex = i + 1 + }; + + firstTextSourceIndex += currentRun.Length; + } // Now perform a recursive reversal of each run. // From the highest level found in the text to the lowest odd level on each line, including intermediate levels @@ -76,7 +92,7 @@ namespace Avalonia.Media.TextFormatting if (max == 0 || (min == max && (max & 1) == 0)) { // Nothing to reverse. - return; + return indexedTextRuns; } // Now apply the reversal and replace the original contents. @@ -107,13 +123,25 @@ namespace Avalonia.Media.TextFormatting var index = 0; currentIndex = firstIndex; + while (currentIndex >= 0) { ref var current = ref _runs[currentIndex]; - textRuns[index++] = current.Run; + + textRuns[index] = current.Run; + + var indexedRun = indexedTextRuns[index]; + + indexedRun.RunIndex = current.RunIndex; + + indexedRun.NextRunIndex = current.NextRunIndex; + + index++; currentIndex = current.NextRunIndex; } + + return indexedTextRuns; } finally { @@ -227,25 +255,6 @@ namespace Avalonia.Media.TextFormatting return previousIndex; } - private struct OrderedBidiRun - { - public OrderedBidiRun(int runIndex, TextRun run, sbyte level) - { - RunIndex = runIndex; - Run = run; - Level = level; - NextRunIndex = -1; - } - - public int RunIndex { get; } - - public sbyte Level { get; } - - public TextRun Run { get; } - - public int NextRunIndex { get; set; } // -1 if none - } - private struct BidiRange { public BidiRange(sbyte level, int leftRunIndex, int rightRunIndex, int previousRangeIndex) @@ -265,4 +274,23 @@ namespace Avalonia.Media.TextFormatting public int PreviousRangeIndex { get; } // -1 if none } } + + internal struct OrderedBidiRun + { + public OrderedBidiRun(int runIndex, TextRun run, sbyte level) + { + RunIndex = runIndex; + Run = run; + Level = level; + NextRunIndex = -1; + } + + public int RunIndex { get; } + + public sbyte Level { get; } + + public TextRun Run { get; } + + public int NextRunIndex { get; set; } // -1 if none + } } diff --git a/src/Avalonia.Base/Media/TextFormatting/IndexedTextRun.cs b/src/Avalonia.Base/Media/TextFormatting/IndexedTextRun.cs new file mode 100644 index 0000000000..0eb98533d2 --- /dev/null +++ b/src/Avalonia.Base/Media/TextFormatting/IndexedTextRun.cs @@ -0,0 +1,10 @@ +namespace Avalonia.Media.TextFormatting +{ + internal class IndexedTextRun + { + public int TextSourceCharacterIndex { get; init; } + public int RunIndex { get; set; } + public int NextRunIndex { get; set; } + public TextRun? TextRun { get; init; } + } +} diff --git a/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs b/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs index 93edf68348..946c2e6931 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextBounds.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; namespace Avalonia.Media.TextFormatting { @@ -10,6 +11,7 @@ namespace Avalonia.Media.TextFormatting /// /// Constructing TextBounds object /// + [DebuggerStepThrough] internal TextBounds(Rect bounds, FlowDirection flowDirection, IList runBounds) { Rectangle = bounds; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index a0d7cabefd..ae2dbe0c8c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -4,8 +4,12 @@ using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { - internal sealed class TextLineImpl : TextLine + internal class TextLineImpl : TextLine { + internal static Comparer TextBoundsComparer { get; } = + Comparer.Create((x, y) => x.Rectangle.Left.CompareTo(y.Rectangle.Left)); + + private IReadOnlyList? _indexedTextRuns; private readonly TextRun[] _textRuns; private readonly double _paragraphWidth; private readonly TextParagraphProperties _paragraphProperties; @@ -338,184 +342,171 @@ namespace Avalonia.Media.TextFormatting /// public override double GetDistanceFromCharacterHit(CharacterHit characterHit) { - var flowDirection = _paragraphProperties.FlowDirection; - var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - var currentPosition = FirstTextSourceIndex; - var remainingLength = characterIndex - FirstTextSourceIndex; - - var currentDistance = Start; - - if (flowDirection == FlowDirection.LeftToRight) + if (_indexedTextRuns is null || _indexedTextRuns.Count == 0) { - for (var index = 0; index < _textRuns.Length; index++) - { - var currentRun = _textRuns[index]; - - if (currentRun is ShapedTextRun shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) - { - var i = index; - - var rightToLeftWidth = shapedRun.Size.Width; - - while (i + 1 <= _textRuns.Length - 1) - { - var nextRun = _textRuns[i + 1]; + return Start; + } - if (nextRun is ShapedTextRun nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight) - { - i++; + var characterIndex = Math.Min( + characterHit.FirstCharacterIndex + characterHit.TrailingLength, + FirstTextSourceIndex + Length); - rightToLeftWidth += nextShapedRun.Size.Width; + var currentPosition = FirstTextSourceIndex; - continue; - } + static FlowDirection GetDirection(TextRun textRun, FlowDirection currentDirection) + { + if (textRun is ShapedTextRun shapedTextRun) + { + return shapedTextRun.ShapedBuffer.IsLeftToRight ? + FlowDirection.LeftToRight : + FlowDirection.RightToLeft; + } - break; - } + return currentDirection; + } - if (i > index) - { - while (i >= index) - { - currentRun = _textRuns[i]; + IndexedTextRun FindIndexedRun() + { + var i = 0; - if (currentRun is DrawableTextRun drawable) - { - rightToLeftWidth -= drawable.Size.Width; - } + IndexedTextRun currentIndexedRun = _indexedTextRuns[i]; - if (currentPosition + currentRun.Length >= characterIndex) - { - break; - } + while(currentIndexedRun.TextSourceCharacterIndex != currentPosition) + { + if(i + 1 < _indexedTextRuns.Count) + { + i++; - currentPosition += currentRun.Length; + currentIndexedRun = _indexedTextRuns[i]; + } - remainingLength -= currentRun.Length; + break; + } - i--; - } + return currentIndexedRun; + } - currentDistance += rightToLeftWidth; - } - } + double GetPreceedingDistance(int firstIndex) + { + var distance = 0.0; - if (currentPosition + currentRun.Length >= characterIndex && - TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _)) - { - return Math.Max(0, currentDistance + distance); - } + for (var i = 0; i < firstIndex; i++) + { + var currentRun = _textRuns[i]; if (currentRun is DrawableTextRun drawableTextRun) { - currentDistance += drawableTextRun.Size.Width; + distance += drawableTextRun.Size.Width; } - - //No hit hit found so we add the full width - - currentPosition += currentRun.Length; - remainingLength -= currentRun.Length; } + + return distance; } - else + + TextRun? currentTextRun = null; + var currentIndexedRun = FindIndexedRun(); + + while (currentPosition < FirstTextSourceIndex + Length) { - currentDistance += WidthIncludingTrailingWhitespace; + currentTextRun = currentIndexedRun.TextRun; - for (var index = _textRuns.Length - 1; index >= 0; index--) + if (currentTextRun == null) { - var currentRun = _textRuns[index]; + break; + } - if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, - flowDirection, out var distance, out var currentGlyphRun)) + if (currentIndexedRun.TextSourceCharacterIndex + currentTextRun.Length <= characterHit.FirstCharacterIndex) + { + if (currentPosition + currentTextRun.Length < FirstTextSourceIndex + Length) { - if (currentGlyphRun != null) - { - currentDistance -= currentGlyphRun.Bounds.Width; - } + currentPosition += currentTextRun.Length; - return currentDistance + distance; - } + currentIndexedRun = FindIndexedRun(); - if (currentRun is DrawableTextRun drawableTextRun) - { - currentDistance -= drawableTextRun.Size.Width; + continue; } - - //No hit hit found so we add the full width - currentPosition += currentRun.Length; - remainingLength -= currentRun.Length; } + + break; } - return Math.Max(0, currentDistance); - } + if (currentTextRun == null) + { + return 0; + } - private static bool TryGetDistanceFromCharacterHit( - TextRun currentRun, - CharacterHit characterHit, - int currentPosition, - int remainingLength, - FlowDirection flowDirection, - out double distance, - out GlyphRun? currentGlyphRun) - { - var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - var isTrailingHit = characterHit.TrailingLength > 0; + var directionalWidth = 0.0; + var firstRunIndex = currentIndexedRun.RunIndex; + var lastRunIndex = firstRunIndex; - distance = 0; - currentGlyphRun = null; + var currentDirection = GetDirection(currentTextRun, _resolvedFlowDirection); - switch (currentRun) + var currentX = Start + GetPreceedingDistance(currentIndexedRun.RunIndex); + + if (currentTextRun is DrawableTextRun currentDrawable) { - case ShapedTextRun shapedTextCharacters: - { - currentGlyphRun = shapedTextCharacters.GlyphRun; + directionalWidth = currentDrawable.Size.Width; + } - if (currentPosition + remainingLength <= currentPosition + currentRun.Length) - { - characterHit = new CharacterHit(currentPosition + remainingLength); + if (currentTextRun is not TextEndOfLine) + { + if (currentDirection == FlowDirection.LeftToRight) + { + // Find consecutive runs of same direction + for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++) + { + var nextRun = _textRuns[lastRunIndex + 1]; - distance = currentGlyphRun.GetDistanceFromCharacterHit(characterHit); + var nextDirection = GetDirection(nextRun, currentDirection); - return true; + if (currentDirection != nextDirection) + { + break; } - if (currentPosition + remainingLength == currentPosition + currentRun.Length && isTrailingHit) + if (nextRun is DrawableTextRun nextDrawable) { - if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft) - { - distance = currentGlyphRun.Bounds.Width; - } - - return true; + directionalWidth += nextDrawable.Size.Width; } - - break; } - case DrawableTextRun drawableTextRun: + } + else + { + // Find consecutive runs of same direction + for (; firstRunIndex - 1 > 0; firstRunIndex--) { - if (characterIndex == currentPosition) + var previousRun = _textRuns[firstRunIndex - 1]; + + var previousDirection = GetDirection(previousRun, currentDirection); + + if (currentDirection != previousDirection) { - return true; + break; } - if (characterIndex == currentPosition + currentRun.Length) + if (previousRun is DrawableTextRun previousDrawable) { - distance = drawableTextRun.Size.Width; - - return true; + directionalWidth += previousDrawable.Size.Width; + currentX -= previousDrawable.Size.Width; } + } + } + } - break; + switch (currentDirection) + { + case FlowDirection.RightToLeft: + { + return GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, characterIndex, + currentPosition, 1, out _, out _).Rectangle.Right; } default: { - return false; + return GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, characterIndex, + currentPosition, 1, out _, out _).Rectangle.Left; } } - - return false; } /// @@ -585,7 +576,7 @@ namespace Avalonia.Media.TextFormatting public override IReadOnlyList GetTextBounds(int firstTextSourceIndex, int textLength) { - if (_textRuns.Length == 0) + if (_indexedTextRuns is null || _indexedTextRuns.Count == 0) { return Array.Empty(); } @@ -607,303 +598,156 @@ namespace Avalonia.Media.TextFormatting return currentDirection; } - if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight) + IndexedTextRun FindIndexedRun() { - var currentX = Start; + var i = 0; - for (int i = 0; i < _textRuns.Length; i++) - { - var currentRun = _textRuns[i]; + IndexedTextRun currentIndexedRun = _indexedTextRuns[i]; - var firstRunIndex = i; - var lastRunIndex = firstRunIndex; - var currentDirection = GetDirection(currentRun, FlowDirection.LeftToRight); - var directionalWidth = 0.0; - - if (currentRun is DrawableTextRun currentDrawable) + while (currentIndexedRun.TextSourceCharacterIndex != currentPosition) + { + if (i + 1 < _indexedTextRuns.Count) { - directionalWidth = currentDrawable.Size.Width; + i++; + + currentIndexedRun = _indexedTextRuns[i]; } - // Find consecutive runs of same direction - for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++) - { - var nextRun = _textRuns[lastRunIndex + 1]; + break; + } - var nextDirection = GetDirection(nextRun, currentDirection); + return currentIndexedRun; + } - if (currentDirection != nextDirection) - { - break; - } + double GetPreceedingDistance(int firstIndex) + { + var distance = 0.0; - if (nextRun is DrawableTextRun nextDrawable) - { - directionalWidth += nextDrawable.Size.Width; - } - } + for (var i = 0; i < firstIndex; i++) + { + var currentRun = _textRuns[i]; - //Skip runs that are not part of the hit test range - switch (currentDirection) + if (currentRun is DrawableTextRun drawableTextRun) { - case FlowDirection.RightToLeft: - { - for (; lastRunIndex >= firstRunIndex; lastRunIndex--) - { - currentRun = _textRuns[lastRunIndex]; + distance += drawableTextRun.Size.Width; + } + } - if (currentPosition + currentRun.Length > firstTextSourceIndex) - { - break; - } + return distance; + } - currentPosition += currentRun.Length; + while (remainingLength > 0 && currentPosition < FirstTextSourceIndex + Length) + { + var currentIndexedRun = FindIndexedRun(); - if (currentRun is DrawableTextRun drawableTextRun) - { - directionalWidth -= drawableTextRun.Size.Width; - currentX += drawableTextRun.Size.Width; - } + if (currentIndexedRun == null) + { + break; + } - if (lastRunIndex - 1 < 0) - { - break; - } - } + var directionalWidth = 0.0; + var firstRunIndex = currentIndexedRun.RunIndex; + var lastRunIndex = firstRunIndex; + var currentTextRun = currentIndexedRun.TextRun; - break; - } - default: - { - for (; firstRunIndex <= lastRunIndex; firstRunIndex++) - { - currentRun = _textRuns[firstRunIndex]; - - if (currentPosition + currentRun.Length > firstTextSourceIndex) - { - break; - } + if (currentTextRun == null) + { + break; + } - currentPosition += currentRun.Length; + var currentDirection = GetDirection(currentTextRun, _resolvedFlowDirection); - if (currentRun is DrawableTextRun drawableTextRun) - { - currentX += drawableTextRun.Size.Width; - directionalWidth -= drawableTextRun.Size.Width; - } + if (currentIndexedRun.TextSourceCharacterIndex + currentTextRun.Length <= firstTextSourceIndex) + { + currentPosition += currentTextRun.Length; - if (firstRunIndex + 1 == _textRuns.Length) - { - break; - } - } + continue; + } - break; - } - } + var currentX = Start + GetPreceedingDistance(currentIndexedRun.RunIndex); - i = lastRunIndex; + if (currentTextRun is DrawableTextRun currentDrawable) + { + directionalWidth = currentDrawable.Size.Width; + } - //Possible overlap at runs of different direction - if (directionalWidth == 0 && i < _textRuns.Length - 1) + if (currentTextRun is not TextEndOfLine) + { + if (currentDirection == FlowDirection.LeftToRight) { - //In case a run only contains a linebreak we don't want to skip it. - if (currentRun is ShapedTextRun shaped) - { - if (currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0) - { - continue; - } - } - else + // Find consecutive runs of same direction + for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++) { - continue; - } - } + var nextRun = _textRuns[lastRunIndex + 1]; - int coveredLength; - TextBounds? textBounds; + var nextDirection = GetDirection(nextRun, currentDirection); - switch (currentDirection) - { - - case FlowDirection.RightToLeft: + if (currentDirection != nextDirection) { - textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex, - currentPosition, remainingLength, out coveredLength, out currentPosition); - - currentX += directionalWidth; - break; } - default: - { - textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, - currentPosition, remainingLength, out coveredLength, out currentPosition); - currentX = textBounds.Rectangle.Right; - - break; + if (nextRun is DrawableTextRun nextDrawable) + { + directionalWidth += nextDrawable.Size.Width; } + } } - - if (coveredLength > 0) - { - result.Add(textBounds); - - remainingLength -= coveredLength; - } - - if (remainingLength <= 0) - { - break; - } - } - } - else - { - var currentX = Start + WidthIncludingTrailingWhitespace; - - for (int i = _textRuns.Length - 1; i >= 0; i--) - { - var currentRun = _textRuns[i]; - var firstRunIndex = i; - var lastRunIndex = firstRunIndex; - var currentDirection = GetDirection(currentRun, FlowDirection.RightToLeft); - var directionalWidth = 0.0; - - if (currentRun is DrawableTextRun currentDrawable) - { - directionalWidth = currentDrawable.Size.Width; - } - - // Find consecutive runs of same direction - for (; firstRunIndex - 1 > 0; firstRunIndex--) + else { - var previousRun = _textRuns[firstRunIndex - 1]; - - var previousDirection = GetDirection(previousRun, currentDirection); - - if (currentDirection != previousDirection) + // Find consecutive runs of same direction + for (; firstRunIndex - 1 > 0; firstRunIndex--) { - break; - } + var previousRun = _textRuns[firstRunIndex - 1]; - if (currentRun is DrawableTextRun previousDrawable) - { - directionalWidth += previousDrawable.Size.Width; - } - } + var previousDirection = GetDirection(previousRun, currentDirection); - //Skip runs that are not part of the hit test range - switch (currentDirection) - { - case FlowDirection.RightToLeft: + if (currentDirection != previousDirection) { - for (; lastRunIndex >= firstRunIndex; lastRunIndex--) - { - currentRun = _textRuns[lastRunIndex]; - - if (currentPosition + currentRun.Length <= firstTextSourceIndex) - { - currentPosition += currentRun.Length; - - if (currentRun is DrawableTextRun drawableTextRun) - { - currentX -= drawableTextRun.Size.Width; - directionalWidth -= drawableTextRun.Size.Width; - } - - continue; - } - - break; - } - break; } - default: - { - for (; firstRunIndex <= lastRunIndex; firstRunIndex++) - { - currentRun = _textRuns[firstRunIndex]; - - if (currentPosition + currentRun.Length <= firstTextSourceIndex) - { - currentPosition += currentRun.Length; - - if (currentRun is DrawableTextRun drawableTextRun) - { - currentX += drawableTextRun.Size.Width; - directionalWidth -= drawableTextRun.Size.Width; - } - continue; - } - - break; - } + if (previousRun is DrawableTextRun previousDrawable) + { + directionalWidth += previousDrawable.Size.Width; - break; + currentX -= previousDrawable.Size.Width; } + } } + } - i = firstRunIndex; + int coveredLength; + TextBounds? textBounds; - //Possible overlap at runs of different direction - if (directionalWidth == 0 && i > 0) - { - //In case a run only contains a linebreak we don't want to skip it. - if (currentRun is ShapedTextRun shaped) + switch (currentDirection) + { + case FlowDirection.RightToLeft: { - if (currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0) - { - continue; - } + textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex, + currentPosition, remainingLength, out coveredLength, out currentPosition); + + break; } - else + default: { - continue; - } - } - - int coveredLength; - TextBounds? textBounds; - - switch (currentDirection) - { - case FlowDirection.LeftToRight: - { - textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX - directionalWidth, firstTextSourceIndex, - currentPosition, remainingLength, out coveredLength, out currentPosition); - - currentX -= directionalWidth; - - break; - } - default: - { - textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, - currentPosition, remainingLength, out coveredLength, out currentPosition); - - currentX = textBounds.Rectangle.Left; + textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, + currentPosition, remainingLength, out coveredLength, out currentPosition); - break; - } - } + break; + } + } - //Visual order is always left to right so we need to insert - result.Insert(0, textBounds); + if (coveredLength > 0) + { + result.Add(textBounds); remainingLength -= coveredLength; - - if (remainingLength <= 0) - { - break; - } } } + result.Sort(TextBoundsComparer); + return result; } @@ -1164,7 +1008,7 @@ namespace Avalonia.Media.TextFormatting _textLineBreak = new TextLineBreak(textEndOfLine); } - BidiReorderer.Instance.BidiReorder(_textRuns, _resolvedFlowDirection); + _indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex); } /// @@ -1211,13 +1055,6 @@ namespace Avalonia.Media.TextFormatting return true; } - //var characterIndex = codepointIndex - shapedRun.Text.Start; - - //if (characterIndex < 0 && shapedRun.ShapedBuffer.IsLeftToRight) - //{ - // foundCharacterHit = new CharacterHit(foundCharacterHit.FirstCharacterIndex); - //} - nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ? foundCharacterHit : new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength); @@ -1556,8 +1393,8 @@ namespace Avalonia.Media.TextFormatting TrailingWhitespaceLength = trailingWhitespaceLength, Width = width, WidthIncludingTrailingWhitespace = widthIncludingWhitespace, - OverhangLeading= overhangLeading, - OverhangTrailing= overhangTrailing, + OverhangLeading = overhangLeading, + OverhangTrailing = overhangTrailing, OverhangAfter = overhangAfter }; } @@ -1615,8 +1452,7 @@ namespace Avalonia.Media.TextFormatting return Math.Max(0, start); case TextAlignment.Right: - return Math.Max(0, _paragraphWidth - width); - + return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace); default: return 0; } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index aa5d707d0f..1d07e780e6 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -1071,6 +1071,55 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact] + public void Should_GetTextBounds_BiDi() + { + var text = "אבגדה 12345 ABCDEF אבגדה"; + + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + var textSource = new SingleBufferTextSource(text, defaultProperties, true); + + var formatter = new TextFormatterImpl(); + + var textLine = + formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left, + true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0)); + + var bounds = textLine.GetTextBounds(6, 1); + + Assert.Equal(1, bounds.Count); + + Assert.Equal(0, bounds[0].Rectangle.Left); + + bounds = textLine.GetTextBounds(5, 1); + + Assert.Equal(1, bounds.Count); + + Assert.Equal(36.005859374999993, bounds[0].Rectangle.Left); + + bounds = textLine.GetTextBounds(0, 1); + + Assert.Equal(1, bounds.Count); + + Assert.Equal(71.165859375, bounds[0].Rectangle.Right); + + bounds = textLine.GetTextBounds(11, 1); + + Assert.Equal(1, bounds.Count); + + Assert.Equal(71.165859375, bounds[0].Rectangle.Left); + + bounds = textLine.GetTextBounds(0, 25); + + Assert.Equal(5, bounds.Count); + + Assert.Equal(textLine.WidthIncludingTrailingWhitespace, bounds.Last().Rectangle.Right); + } + } + private class FixedRunsTextSource : ITextSource { private readonly IReadOnlyList _textRuns; From 40515fc79846e68a3615959418ba6cc55ca15f9a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 May 2023 14:49:45 +0200 Subject: [PATCH 84/93] Clarify/fix unit test. Now all of the other popup tests pass as well. --- .../Primitives/PopupTests.cs | 82 +++++++++++-------- .../MockWindowingPlatform.cs | 11 +-- 2 files changed, 53 insertions(+), 40 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index e307a80441..888162cfee 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -1080,6 +1080,10 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void GetPosition_On_Control_In_Popup_Called_From_Parent_Should_Return_Valid_Coordinates() { + // This test only applies when using a PopupRoot host and not an overlay popup. + if (UsePopupHost) + return; + using (CreateServices()) { var popupContent = new Border() { Height = 100, Width = 100, Background = Brushes.Red }; @@ -1088,18 +1092,18 @@ namespace Avalonia.Controls.UnitTests.Primitives var popupParent = new Border { Child = popup }; var root = PreparedWindow(popupParent); - var raised = 0; - - root.LayoutManager.ExecuteInitialLayoutPass(); popup.Open(); - root.LayoutManager.ExecuteLayoutPass(); - - var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); - var pts = popupContent.PointToScreen(new Point(10, 10)); + // Verify that the popup is positioned at 40,40 as descibed by the Horizontal/ + // VerticalOffset: 10,10 becomes 50,50 in screen coordinates. + Assert.Equal(new PixelPoint(50, 50), popupContent.PointToScreen(new Point(10, 10))); - Assert.Equal(new PixelPoint(50, 50), pts); + // The popup parent is positioned at 0,0 in screen coordinates so client and + // screen coordinates are the same. + Assert.Equal(new PixelPoint(10, 10), popupParent.PointToScreen(new Point(10, 10))); + // The event will be raised on the popup content at 50,50 (90,90 in screen coordinates) + var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); var ev = new PointerPressedEventArgs( popupContent, pointer, @@ -1109,19 +1113,28 @@ namespace Avalonia.Controls.UnitTests.Primitives new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), KeyModifiers.None); - Point pointRelativeToWindowContent = default; + var contentRaised = 0; + var parentRaised = 0; - popupParent.AddHandler(Button.PointerPressedEvent, (s, e) => + // The event is raised on the popup content in popup coordinates. + popupContent.AddHandler(Button.PointerPressedEvent, (s, e) => { - ++raised; + ++contentRaised; + Assert.Equal(new Point(50, 50), e.GetPosition(popupContent)); + }); - pointRelativeToWindowContent = e.GetPosition(popupParent); + // The event is raised on the parent in root coordinates (which in this case are + // the same as screen coordinates). + popupParent.AddHandler(Button.PointerPressedEvent, (s, e) => + { + ++parentRaised; + Assert.Equal(new Point(90, 90), e.GetPosition(popupParent)); }); popupContent.RaiseEvent(ev); - Assert.Equal(1, raised); - Assert.Equal(new Point(90, 90), pointRelativeToWindowContent); + Assert.Equal(1, contentRaised); + Assert.Equal(1, parentRaised); } } @@ -1146,28 +1159,16 @@ namespace Avalonia.Controls.UnitTests.Primitives private IDisposable CreateServices() { - return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: - new MockWindowingPlatform(() => MockWindowingPlatform.CreateWindowMock().Object, - x => - { - if(UsePopupHost) - return null; - return MockWindowingPlatform.CreatePopupMock(x).Object; - }))); + return UnitTestApplication.Start(TestServices.StyledWindow.With( + windowingPlatform: CreateMockWindowingPlatform())); } private IDisposable CreateServicesWithFocus() { - return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: - new MockWindowingPlatform(null, - x => - { - if (UsePopupHost) - return null; - return MockWindowingPlatform.CreatePopupMock(x).Object; - }), - focusManager: new FocusManager(), - keyboardDevice: () => new KeyboardDevice())); + return UnitTestApplication.Start(TestServices.StyledWindow.With( + windowingPlatform: CreateMockWindowingPlatform(), + focusManager: new FocusManager(), + keyboardDevice: () => new KeyboardDevice())); } @@ -1184,6 +1185,23 @@ namespace Avalonia.Controls.UnitTests.Primitives KeyModifiers.None); } + private MockWindowingPlatform CreateMockWindowingPlatform() + { + return new MockWindowingPlatform(() => + { + var mock = MockWindowingPlatform.CreateWindowMock(); + + mock.Setup(x => x.CreatePopup()).Returns(() => + { + if (UsePopupHost) + return null; + return MockWindowingPlatform.CreatePopupMock(mock.Object).Object; + }); + + return mock.Object; + }, null); + } + private static Window PreparedWindow(object content = null) { var w = new Window { Content = content }; diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 65612e45a0..5451773da4 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -24,7 +24,6 @@ namespace Avalonia.UnitTests public static Mock CreateWindowMock(double initialWidth = 800, double initialHeight = 600) { var windowImpl = new Mock(); - var position = new PixelPoint(); var clientSize = new Size(initialWidth, initialHeight); windowImpl.SetupAllProperties(); @@ -35,7 +34,6 @@ namespace Avalonia.UnitTests windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object); - windowImpl.Setup(x => x.Position).Returns(() => position); windowImpl.Setup(r => r.TryGetFeature(It.IsAny())).Returns(null); @@ -51,15 +49,15 @@ namespace Avalonia.UnitTests windowImpl.Setup(x => x.Move(It.IsAny())).Callback(x => { - position = x; + windowImpl.Setup(x => x.Position).Returns(x); windowImpl.Object.PositionChanged?.Invoke(x); }); windowImpl.Setup(x => x.PointToScreen(It.IsAny())) - .Returns((point) => PixelPoint.FromPoint(point, 1) + position); + .Returns((point) => PixelPoint.FromPoint(point, 1) + windowImpl.Object.Position); windowImpl.Setup(x => x.PointToClient(It.IsAny())) - .Returns(point => (point - position).ToPoint(1)); + .Returns(point => (point - windowImpl.Object.Position).ToPoint(1)); windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny())) .Callback((x, y) => @@ -79,9 +77,6 @@ namespace Avalonia.UnitTests windowImpl.Object.Activated?.Invoke(); }); - windowImpl.Setup(x => x.PointToScreen(It.IsAny())) - .Returns((Point p) => PixelPoint.FromPoint(p, 1D) + position); - return windowImpl; } From 3070e8960c16684139136fd9734b99d4c16ee456 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 26 May 2023 15:20:47 +0200 Subject: [PATCH 85/93] Fix FindIndexedRun --- src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index ae2dbe0c8c..c2ec78e187 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -379,8 +379,6 @@ namespace Avalonia.Media.TextFormatting currentIndexedRun = _indexedTextRuns[i]; } - - break; } return currentIndexedRun; @@ -612,8 +610,6 @@ namespace Avalonia.Media.TextFormatting currentIndexedRun = _indexedTextRuns[i]; } - - break; } return currentIndexedRun; From ea110cd8076817f85084214e561d2e551c39af17 Mon Sep 17 00:00:00 2001 From: aldelaro5 Date: Sun, 23 Apr 2023 05:04:03 -0400 Subject: [PATCH 86/93] Fix mouse wheel events being eaten on ComboBox on browser --- src/Avalonia.Controls/ComboBox.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 1234b66383..f5587a4c34 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -354,19 +354,6 @@ namespace Avalonia.Controls _subscriptionsOnOpen.Clear(); - var toplevel = TopLevel.GetTopLevel(this); - if (toplevel != null) - { - toplevel.AddDisposableHandler(PointerWheelChangedEvent, (s, ev) => - { - //eat wheel scroll event outside dropdown popup while it's open - if (IsDropDownOpen && (ev.Source as Visual)?.GetVisualRoot() == toplevel) - { - ev.Handled = true; - } - }, Interactivity.RoutingStrategies.Tunnel).DisposeWith(_subscriptionsOnOpen); - } - this.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen); foreach (var parent in this.GetVisualAncestors().OfType()) From 68134c925fb3ff9acf78725b0cdc4a3cb77554e8 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 27 May 2023 19:48:07 -0400 Subject: [PATCH 87/93] Switch RangeBase.ValueChanged from generic RoutedPropertyChangedEventArgs to RangeBaseValueChangedEventArgs This follows the WinUI pattern and makes a bit more sense. It is also similar to what is done with TextBox events as well. --- src/Avalonia.Controls/Primitives/RangeBase.cs | 8 ++-- .../RangeBaseValueChangedEventArgs.cs | 47 +++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 src/Avalonia.Controls/Primitives/RangeBaseValueChangedEventArgs.cs diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs index ebf7879412..33ab78b66f 100644 --- a/src/Avalonia.Controls/Primitives/RangeBase.cs +++ b/src/Avalonia.Controls/Primitives/RangeBase.cs @@ -45,14 +45,14 @@ namespace Avalonia.Controls.Primitives /// /// Defines the event. /// - public static readonly RoutedEvent> ValueChangedEvent = - RoutedEvent.Register>( + public static readonly RoutedEvent ValueChangedEvent = + RoutedEvent.Register( nameof(ValueChanged), RoutingStrategies.Bubble); /// /// Occurs when the property changes. /// - public event EventHandler>? ValueChanged + public event EventHandler? ValueChanged { add => AddHandler(ValueChangedEvent, value); remove => RemoveHandler(ValueChangedEvent, value); @@ -163,7 +163,7 @@ namespace Avalonia.Controls.Primitives } else if (change.Property == ValueProperty) { - var valueChangedEventArgs = new RoutedPropertyChangedEventArgs( + var valueChangedEventArgs = new RangeBaseValueChangedEventArgs( change.GetOldValue(), change.GetNewValue(), ValueChangedEvent); diff --git a/src/Avalonia.Controls/Primitives/RangeBaseValueChangedEventArgs.cs b/src/Avalonia.Controls/Primitives/RangeBaseValueChangedEventArgs.cs new file mode 100644 index 0000000000..ce1ac90bc6 --- /dev/null +++ b/src/Avalonia.Controls/Primitives/RangeBaseValueChangedEventArgs.cs @@ -0,0 +1,47 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Controls.Primitives +{ + /// + /// Provides data specific to a event. + /// + public class RangeBaseValueChangedEventArgs : RoutedEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The old property value. + /// The new property value. + /// The routed event associated with these event args. + public RangeBaseValueChangedEventArgs(double oldValue, double newValue, RoutedEvent? routedEvent) + : base(routedEvent) + { + OldValue = oldValue; + NewValue = newValue; + } + + /// + /// Initializes a new instance of the class. + /// + /// The old property value. + /// The new property value. + /// The routed event associated with these event args. + /// The source object that raised the routed event. + public RangeBaseValueChangedEventArgs(double oldValue, double newValue, RoutedEvent? routedEvent, object? source) + : base(routedEvent, source) + { + OldValue = oldValue; + NewValue = newValue; + } + + /// + /// Gets the old value of the property. + /// + public double OldValue { get; init; } + + /// + /// Gets the new value of the property. + /// + public double NewValue { get; init; } + } +} From 545e9051bd89083f9d272703a5df475fbad89920 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 27 May 2023 19:48:55 -0400 Subject: [PATCH 88/93] Remove generic RoutedPropertyChangedEventArgs --- .../RoutedPropertyChangedEventArgs.cs | 46 ------------------- 1 file changed, 46 deletions(-) delete mode 100644 src/Avalonia.Base/Interactivity/RoutedPropertyChangedEventArgs.cs diff --git a/src/Avalonia.Base/Interactivity/RoutedPropertyChangedEventArgs.cs b/src/Avalonia.Base/Interactivity/RoutedPropertyChangedEventArgs.cs deleted file mode 100644 index 22134b518d..0000000000 --- a/src/Avalonia.Base/Interactivity/RoutedPropertyChangedEventArgs.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Avalonia.Interactivity -{ - /// - /// Provides both old and new property values with a routed event. - /// - /// The type of values. - public class RoutedPropertyChangedEventArgs : RoutedEventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The old property value. - /// The new property value. - /// The routed event associated with these event args. - public RoutedPropertyChangedEventArgs(T oldValue, T newValue, RoutedEvent? routedEvent) - : base(routedEvent) - { - OldValue = oldValue; - NewValue = newValue; - } - - /// - /// Initializes a new instance of the class. - /// - /// The old property value. - /// The new property value. - /// The routed event associated with these event args. - /// The source object that raised the routed event. - public RoutedPropertyChangedEventArgs(T oldValue, T newValue, RoutedEvent? routedEvent, object? source) - : base(routedEvent, source) - { - OldValue = oldValue; - NewValue = newValue; - } - - /// - /// Gets the old value of the property. - /// - public T OldValue { get; init; } - - /// - /// Gets the new value of the property. - /// - public T NewValue { get; init; } - } -} From 0d1ef8f7b62c037dafd53d9afdcec6a7d5c9f869 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 27 May 2023 19:49:19 -0400 Subject: [PATCH 89/93] Add comments to TextChangedEventArgs and TextChangingEventArgs --- src/Avalonia.Controls/TextChangedEventArgs.cs | 11 ++++++++++- src/Avalonia.Controls/TextChangingEventArgs.cs | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TextChangedEventArgs.cs b/src/Avalonia.Controls/TextChangedEventArgs.cs index 1418154256..f614d2d760 100644 --- a/src/Avalonia.Controls/TextChangedEventArgs.cs +++ b/src/Avalonia.Controls/TextChangedEventArgs.cs @@ -3,15 +3,24 @@ namespace Avalonia.Controls { /// - /// Provides data specific to a TextChanged event. + /// Provides data specific to a event. /// public class TextChangedEventArgs : RoutedEventArgs { + /// + /// Initializes a new instance of the class. + /// + /// The routed event associated with these event args. public TextChangedEventArgs(RoutedEvent? routedEvent) : base (routedEvent) { } + /// + /// Initializes a new instance of the class. + /// + /// The routed event associated with these event args. + /// The source object that raised the routed event. public TextChangedEventArgs(RoutedEvent? routedEvent, Interactive? source) : base(routedEvent, source) { diff --git a/src/Avalonia.Controls/TextChangingEventArgs.cs b/src/Avalonia.Controls/TextChangingEventArgs.cs index 09e7d5b258..6ec5afd030 100644 --- a/src/Avalonia.Controls/TextChangingEventArgs.cs +++ b/src/Avalonia.Controls/TextChangingEventArgs.cs @@ -3,15 +3,24 @@ namespace Avalonia.Controls { /// - /// Provides data specific to a TextChanging event. + /// Provides data specific to a event. /// public class TextChangingEventArgs : RoutedEventArgs { + /// + /// Initializes a new instance of the class. + /// + /// The routed event associated with these event args. public TextChangingEventArgs(RoutedEvent? routedEvent) : base (routedEvent) { } + /// + /// Initializes a new instance of the class. + /// + /// The routed event associated with these event args. + /// The source object that raised the routed event. public TextChangingEventArgs(RoutedEvent? routedEvent, Interactive? source) : base(routedEvent, source) { From 4bcf08a9dc70efe18cc48fd6ca01679195bec683 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 27 May 2023 20:00:37 -0400 Subject: [PATCH 90/93] Update RangeBaseValueChangedEventArgs comments --- .../Primitives/RangeBaseValueChangedEventArgs.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/RangeBaseValueChangedEventArgs.cs b/src/Avalonia.Controls/Primitives/RangeBaseValueChangedEventArgs.cs index ce1ac90bc6..480e82e4fc 100644 --- a/src/Avalonia.Controls/Primitives/RangeBaseValueChangedEventArgs.cs +++ b/src/Avalonia.Controls/Primitives/RangeBaseValueChangedEventArgs.cs @@ -10,8 +10,8 @@ namespace Avalonia.Controls.Primitives /// /// Initializes a new instance of the class. /// - /// The old property value. - /// The new property value. + /// The old value of the range value property. + /// The new value of the range value property. /// The routed event associated with these event args. public RangeBaseValueChangedEventArgs(double oldValue, double newValue, RoutedEvent? routedEvent) : base(routedEvent) @@ -23,8 +23,8 @@ namespace Avalonia.Controls.Primitives /// /// Initializes a new instance of the class. /// - /// The old property value. - /// The new property value. + /// The old value of the range value property. + /// The new value of the range value property. /// The routed event associated with these event args. /// The source object that raised the routed event. public RangeBaseValueChangedEventArgs(double oldValue, double newValue, RoutedEvent? routedEvent, object? source) @@ -35,12 +35,12 @@ namespace Avalonia.Controls.Primitives } /// - /// Gets the old value of the property. + /// Gets the old value of the range value property. /// public double OldValue { get; init; } /// - /// Gets the new value of the property. + /// Gets the new value of the range value property. /// public double NewValue { get; init; } } From 94fd79f7d9c9bfaf144cdc472d20ad2641d4bfae Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 27 May 2023 21:02:09 -0400 Subject: [PATCH 91/93] Obsolete Border line dashes --- src/Avalonia.Controls/Border.cs | 54 +++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index 78ba23c1dd..3c0802c013 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -50,24 +50,28 @@ namespace Avalonia.Controls /// /// Defines the property. /// + [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] public static readonly StyledProperty BorderDashOffsetProperty = AvaloniaProperty.Register(nameof(BorderDashOffset)); /// /// Defines the property. /// + [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] public static readonly StyledProperty?> BorderDashArrayProperty = AvaloniaProperty.Register?>(nameof(BorderDashArray)); /// /// Defines the property. /// + [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] public static readonly StyledProperty BorderLineCapProperty = AvaloniaProperty.Register(nameof(BorderLineCap), PenLineCap.Flat); /// /// Defines the property. /// + [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] public static readonly StyledProperty BorderLineJoinProperty = AvaloniaProperty.Register(nameof(BorderLineJoin), PenLineJoin.Miter); @@ -86,10 +90,6 @@ namespace Avalonia.Controls BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty, - BorderDashArrayProperty, - BorderLineCapProperty, - BorderLineJoinProperty, - BorderDashOffsetProperty, BoxShadowProperty); AffectsMeasure(BorderThicknessProperty); } @@ -115,8 +115,8 @@ namespace Avalonia.Controls /// public IBrush? Background { - get { return GetValue(BackgroundProperty); } - set { SetValue(BackgroundProperty, value); } + get => GetValue(BackgroundProperty); + set => SetValue(BackgroundProperty, value); } /// @@ -124,17 +124,18 @@ namespace Avalonia.Controls /// public IBrush? BorderBrush { - get { return GetValue(BorderBrushProperty); } - set { SetValue(BorderBrushProperty, value); } + get => GetValue(BorderBrushProperty); + set => SetValue(BorderBrushProperty, value); } /// /// Gets or sets a collection of values that indicate the pattern of dashes and gaps that is used to outline shapes. /// + [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] public AvaloniaList? BorderDashArray { - get { return GetValue(BorderDashArrayProperty); } - set { SetValue(BorderDashArrayProperty, value); } + get => GetValue(BorderDashArrayProperty); + set => SetValue(BorderDashArrayProperty, value); } /// @@ -142,35 +143,38 @@ namespace Avalonia.Controls /// public Thickness BorderThickness { - get { return GetValue(BorderThicknessProperty); } - set { SetValue(BorderThicknessProperty, value); } + get => GetValue(BorderThicknessProperty); + set => SetValue(BorderThicknessProperty, value); } /// /// Gets or sets a value that specifies the distance within the dash pattern where a dash begins. /// + [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] public double BorderDashOffset { - get { return GetValue(BorderDashOffsetProperty); } - set { SetValue(BorderDashOffsetProperty, value); } + get => GetValue(BorderDashOffsetProperty); + set => SetValue(BorderDashOffsetProperty, value); } /// /// Gets or sets a enumeration value that describes the shape at the ends of a line. /// + [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] public PenLineCap BorderLineCap { - get { return GetValue(BorderLineCapProperty); } - set { SetValue(BorderLineCapProperty, value); } + get => GetValue(BorderLineCapProperty); + set => SetValue(BorderLineCapProperty, value); } /// /// Gets or sets a enumeration value that specifies the type of join that is used at the vertices of a Shape. /// + [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] public PenLineJoin BorderLineJoin { - get { return GetValue(BorderLineJoinProperty); } - set { SetValue(BorderLineJoinProperty, value); } + get => GetValue(BorderLineJoinProperty); + set => SetValue(BorderLineJoinProperty, value); } /// @@ -178,8 +182,8 @@ namespace Avalonia.Controls /// public CornerRadius CornerRadius { - get { return GetValue(CornerRadiusProperty); } - set { SetValue(CornerRadiusProperty, value); } + get => GetValue(CornerRadiusProperty); + set => SetValue(CornerRadiusProperty, value); } /// @@ -227,8 +231,14 @@ namespace Avalonia.Controls /// The drawing context. public sealed override void Render(DrawingContext context) { - _borderRenderHelper.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush, - BoxShadow, BorderDashOffset, BorderLineCap, BorderLineJoin, BorderDashArray); + _borderRenderHelper.Render( + context, + Bounds.Size, + LayoutThickness, + CornerRadius, + Background, + BorderBrush, + BoxShadow); } /// From a857c92a46df1ca52589a181bcace2e8cafc281c Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 27 May 2023 21:02:46 -0400 Subject: [PATCH 92/93] Update property get/set syntax in Shape --- src/Avalonia.Controls/Shapes/Shape.cs | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 461dc1c947..a65ac774ef 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -126,8 +126,8 @@ namespace Avalonia.Controls.Shapes /// public IBrush? Fill { - get { return GetValue(FillProperty); } - set { SetValue(FillProperty, value); } + get => GetValue(FillProperty); + set => SetValue(FillProperty, value); } /// @@ -135,8 +135,8 @@ namespace Avalonia.Controls.Shapes /// public Stretch Stretch { - get { return GetValue(StretchProperty); } - set { SetValue(StretchProperty, value); } + get => GetValue(StretchProperty); + set => SetValue(StretchProperty, value); } /// @@ -144,8 +144,8 @@ namespace Avalonia.Controls.Shapes /// public IBrush? Stroke { - get { return GetValue(StrokeProperty); } - set { SetValue(StrokeProperty, value); } + get => GetValue(StrokeProperty); + set => SetValue(StrokeProperty, value); } /// @@ -153,8 +153,8 @@ namespace Avalonia.Controls.Shapes /// public AvaloniaList? StrokeDashArray { - get { return GetValue(StrokeDashArrayProperty); } - set { SetValue(StrokeDashArrayProperty, value); } + get => GetValue(StrokeDashArrayProperty); + set => SetValue(StrokeDashArrayProperty, value); } /// @@ -162,8 +162,8 @@ namespace Avalonia.Controls.Shapes /// public double StrokeDashOffset { - get { return GetValue(StrokeDashOffsetProperty); } - set { SetValue(StrokeDashOffsetProperty, value); } + get => GetValue(StrokeDashOffsetProperty); + set => SetValue(StrokeDashOffsetProperty, value); } /// @@ -171,8 +171,8 @@ namespace Avalonia.Controls.Shapes /// public double StrokeThickness { - get { return GetValue(StrokeThicknessProperty); } - set { SetValue(StrokeThicknessProperty, value); } + get => GetValue(StrokeThicknessProperty); + set => SetValue(StrokeThicknessProperty, value); } /// @@ -180,8 +180,8 @@ namespace Avalonia.Controls.Shapes /// public PenLineCap StrokeLineCap { - get { return GetValue(StrokeLineCapProperty); } - set { SetValue(StrokeLineCapProperty, value); } + get => GetValue(StrokeLineCapProperty); + set => SetValue(StrokeLineCapProperty, value); } /// @@ -189,8 +189,8 @@ namespace Avalonia.Controls.Shapes /// public PenLineJoin StrokeJoin { - get { return GetValue(StrokeJoinProperty); } - set { SetValue(StrokeJoinProperty, value); } + get => GetValue(StrokeJoinProperty); + set => SetValue(StrokeJoinProperty, value); } public sealed override void Render(DrawingContext context) From e18d9364b8286e5d72329d732fe8a09c8e979ad8 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 28 May 2023 12:32:39 -0400 Subject: [PATCH 93/93] Completely remove Border line dash properties --- src/Avalonia.Controls/Border.cs | 68 --------------------------------- 1 file changed, 68 deletions(-) diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index 3c0802c013..e7373a813e 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -47,34 +47,6 @@ namespace Avalonia.Controls public static readonly StyledProperty BoxShadowProperty = AvaloniaProperty.Register(nameof(BoxShadow)); - /// - /// Defines the property. - /// - [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] - public static readonly StyledProperty BorderDashOffsetProperty = - AvaloniaProperty.Register(nameof(BorderDashOffset)); - - /// - /// Defines the property. - /// - [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] - public static readonly StyledProperty?> BorderDashArrayProperty = - AvaloniaProperty.Register?>(nameof(BorderDashArray)); - - /// - /// Defines the property. - /// - [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] - public static readonly StyledProperty BorderLineCapProperty = - AvaloniaProperty.Register(nameof(BorderLineCap), PenLineCap.Flat); - - /// - /// Defines the property. - /// - [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] - public static readonly StyledProperty BorderLineJoinProperty = - AvaloniaProperty.Register(nameof(BorderLineJoin), PenLineJoin.Miter); - private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper(); private Thickness? _layoutThickness; private double _scale; @@ -128,16 +100,6 @@ namespace Avalonia.Controls set => SetValue(BorderBrushProperty, value); } - /// - /// Gets or sets a collection of values that indicate the pattern of dashes and gaps that is used to outline shapes. - /// - [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] - public AvaloniaList? BorderDashArray - { - get => GetValue(BorderDashArrayProperty); - set => SetValue(BorderDashArrayProperty, value); - } - /// /// Gets or sets the thickness of the border. /// @@ -147,36 +109,6 @@ namespace Avalonia.Controls set => SetValue(BorderThicknessProperty, value); } - /// - /// Gets or sets a value that specifies the distance within the dash pattern where a dash begins. - /// - [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] - public double BorderDashOffset - { - get => GetValue(BorderDashOffsetProperty); - set => SetValue(BorderDashOffsetProperty, value); - } - - /// - /// Gets or sets a enumeration value that describes the shape at the ends of a line. - /// - [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] - public PenLineCap BorderLineCap - { - get => GetValue(BorderLineCapProperty); - set => SetValue(BorderLineCapProperty, value); - } - - /// - /// Gets or sets a enumeration value that specifies the type of join that is used at the vertices of a Shape. - /// - [Obsolete("Dashed lines on Border are no longer supported. Use Shapes directly instead.")] - public PenLineJoin BorderLineJoin - { - get => GetValue(BorderLineJoinProperty); - set => SetValue(BorderLineJoinProperty, value); - } - /// /// Gets or sets the radius of the border rounded corners. ///