From 03e01a6e554227613ce5579cf24884e8a94ad629 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Mon, 30 May 2022 13:04:46 +0300 Subject: [PATCH 01/18] initial working --- src/Avalonia.Controls/ComboBox.cs | 21 ++-- src/Avalonia.Controls/Control.cs | 7 +- .../Rendering/SceneGraph/SceneBuilderTests.cs | 33 +++++++ .../ComboBoxTests.cs | 99 +++++++++++++++++++ .../FlowDirectionTests.cs | 41 ++++++++ 5 files changed, 181 insertions(+), 20 deletions(-) create mode 100644 tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index cbf9b35a05..1f46a3b292 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -184,23 +184,10 @@ namespace Avalonia.Controls this.UpdateSelectionBoxItem(SelectedItem); } - // Because the SelectedItem isn't connected to the visual tree public override void InvalidateMirrorTransform() { base.InvalidateMirrorTransform(); - - if (SelectedItem is Control selectedControl) - { - selectedControl.InvalidateMirrorTransform(); - - foreach (var visual in selectedControl.GetVisualDescendants()) - { - if (visual is Control childControl) - { - childControl.InvalidateMirrorTransform(); - } - } - } + UpdateSelectionBoxItem(SelectedItem); } /// @@ -365,6 +352,8 @@ namespace Avalonia.Controls { parent.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen); } + + UpdateSelectionBoxItem(SelectedItem); } private void IsVisibleChanged(bool isVisible) @@ -420,8 +409,12 @@ namespace Avalonia.Controls { control.Measure(Size.Infinity); + var flowDirection = control.IsAttachedToVisualTree ? + (control.VisualParent as Control)!.FlowDirection : FlowDirection.LeftToRight; + SelectionBoxItem = new Rectangle { + FlowDirection = flowDirection, Width = control.DesiredSize.Width, Height = control.DesiredSize.Height, Fill = new VisualBrush diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index d6a5fa0727..16d4ef5c15 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -378,17 +378,12 @@ namespace Avalonia.Controls bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies; bool parentBypassFlowDirectionPolicies = false; - var parent = this.FindAncestorOfType(); + var parent = ((IVisual)this).VisualParent as Control; if (parent != null) { parentFlowDirection = parent.FlowDirection; parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies; } - else if (Parent is Control logicalParent) - { - parentFlowDirection = logicalParent.FlowDirection; - parentBypassFlowDirectionPolicies = logicalParent.BypassFlowDirectionPolicies; - } bool thisShouldBeMirrored = flowDirection == FlowDirection.RightToLeft && !bypassFlowDirectionPolicies; bool parentShouldBeMirrored = parentFlowDirection == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies; diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index 01afe85b8b..5cc9f57c8e 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -349,6 +349,39 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph } } + [Fact] + public void MirrorTransform_For_Control_With_RenderTransform_Should_Be_Correct() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border border; + var tree = new TestRoot + { + Width = 400, + Height = 200, + Child = border = new Border + { + HorizontalAlignment = HorizontalAlignment.Left, + Background = Brushes.Red, + Width = 100, + RenderTransform = new ScaleTransform(0.5, 1), + FlowDirection = FlowDirection.RightToLeft + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var expectedTransform = new Matrix(-1, 0, 0, 1, 100, 0) * Matrix.CreateScale(0.5, 1) * Matrix.CreateTranslation(25, 0); + var borderNode = scene.FindNode(border); + Assert.Equal(expectedTransform, borderNode.Transform); + } + } + [Fact] public void Should_Update_Border_Background_Node() { diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 98695fe88e..0f1925f628 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -336,5 +336,104 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, count); } } + + [Fact] + public void FlowDirection_Of_RectangleContent_Shuold_Be_LeftToRight() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var items = new[] + { + new ComboBoxItem() + { + Content = new Control() + } + }; + var target = new ComboBox + { + Items = items, + Template = GetTemplate() + }; + + var root = new TestRoot(target); + target.ApplyTemplate(); + target.SelectedIndex = 0; + + var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; + + Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection); + } + } + + [Fact] + public void FlowDirection_Of_RectangleContent_Updated_After_Change_ComboBox() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = new[] + { + new ComboBoxItem() + { + Content = new Control() + } + }; + var target = new ComboBox + { + FlowDirection = FlowDirection.RightToLeft, + Items = items, + Template = GetTemplate() + }; + + var root = new TestRoot(target); + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectedIndex = 0; + + var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; + + // need help here, the 'rectangle' isn't connected to visual tree for some reason + + Assert.True(rectangle.HasMirrorTransform); + + target.FlowDirection = FlowDirection.LeftToRight; + + Assert.False(rectangle.HasMirrorTransform); + } + } + + [Fact] + public void FlowDirection_Of_RectangleContent_Updated_After_Content_In_VisualTree() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + Control content; + var items = new[] + { + new ComboBoxItem() + { + Content = content = new Control() + } + }; + var target = new ComboBox + { + FlowDirection = FlowDirection.RightToLeft, + Items = items, + Template = GetTemplate() + }; + + var root = new TestRoot(target); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectedIndex = 0; + + // need help here how to connect 'content' tio visual tree, or how to + + + var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; + + Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs b/tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs new file mode 100644 index 0000000000..6739eff638 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs @@ -0,0 +1,41 @@ +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class FlowDirectionTests + { + [Fact] + public void HasMirrorTransform_Should_Be_True() + { + var target = new Control + { + FlowDirection = FlowDirection.RightToLeft, + }; + + Assert.True(target.HasMirrorTransform); + } + + [Fact] + public void HasMirrorTransform_Of_Children_Is_Updated_After_Change() + { + Control child; + var target = new Decorator + { + FlowDirection = FlowDirection.LeftToRight, + Child = child = new Control() + { + FlowDirection = FlowDirection.LeftToRight, + } + }; + + Assert.False(target.HasMirrorTransform); + Assert.False(child.HasMirrorTransform); + + target.FlowDirection = FlowDirection.RightToLeft; + + Assert.True(target.HasMirrorTransform); + Assert.True(child.HasMirrorTransform); + } + } +} From 05db047d2909b96b264ffded9e882bb62278ba25 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Mon, 30 May 2022 13:09:20 +0300 Subject: [PATCH 02/18] typo --- tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 0f1925f628..905ded193c 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -427,7 +427,7 @@ namespace Avalonia.Controls.UnitTests target.Presenter.ApplyTemplate(); target.SelectedIndex = 0; - // need help here how to connect 'content' tio visual tree, or how to + // need help here how to connect 'content' to visual tree, or how to open popup var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; From ee4c0f97e6e4000f01a46b591ed43afa4eba939b Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Mon, 30 May 2022 14:23:13 +0300 Subject: [PATCH 03/18] remove OnAttachedToVisualTree because bug --- src/Avalonia.Controls/ComboBox.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 1f46a3b292..8a6fb361da 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -178,16 +178,10 @@ namespace Avalonia.Controls ComboBoxItem.ContentTemplateProperty); } - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - this.UpdateSelectionBoxItem(SelectedItem); - } - public override void InvalidateMirrorTransform() { base.InvalidateMirrorTransform(); - UpdateSelectionBoxItem(SelectedItem); + UpdateSelectionBoxItem(SelectedItem); } /// From 7e6edb0f32b753d226aba58889d56c2875b6f282 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Mon, 30 May 2022 23:42:05 +0300 Subject: [PATCH 04/18] fixes bugs --- src/Avalonia.Controls/ComboBox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 8a6fb361da..a3f87f7695 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -403,8 +403,8 @@ namespace Avalonia.Controls { control.Measure(Size.Infinity); - var flowDirection = control.IsAttachedToVisualTree ? - (control.VisualParent as Control)!.FlowDirection : FlowDirection.LeftToRight; + var flowDirection = + (control.VisualParent as Control)?.FlowDirection ?? FlowDirection.LeftToRight; SelectionBoxItem = new Rectangle { From 402790ba86bfb6fc0f9de817cb3032b681175f38 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 31 May 2022 07:53:59 +0300 Subject: [PATCH 05/18] improve UpdateFlowDirection --- src/Avalonia.Controls/ComboBox.cs | 28 +++++++++++++++---- .../ComboBoxTests.cs | 5 ++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index a3f87f7695..fc7feca7f1 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -178,10 +178,16 @@ namespace Avalonia.Controls ComboBoxItem.ContentTemplateProperty); } + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + UpdateSelectionBoxItem(SelectedItem); + } + public override void InvalidateMirrorTransform() { base.InvalidateMirrorTransform(); - UpdateSelectionBoxItem(SelectedItem); + UpdateFlowDirection(); } /// @@ -347,7 +353,7 @@ namespace Avalonia.Controls parent.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen); } - UpdateSelectionBoxItem(SelectedItem); + UpdateFlowDirection(); } private void IsVisibleChanged(bool isVisible) @@ -403,12 +409,8 @@ namespace Avalonia.Controls { control.Measure(Size.Infinity); - var flowDirection = - (control.VisualParent as Control)?.FlowDirection ?? FlowDirection.LeftToRight; - SelectionBoxItem = new Rectangle { - FlowDirection = flowDirection, Width = control.DesiredSize.Width, Height = control.DesiredSize.Height, Fill = new VisualBrush @@ -419,6 +421,8 @@ namespace Avalonia.Controls } }; } + + UpdateFlowDirection(); } else { @@ -426,6 +430,18 @@ namespace Avalonia.Controls } } + private void UpdateFlowDirection() + { + var rectangle = SelectionBoxItem as Rectangle; + if (rectangle != null) + { + var content = (rectangle.Fill as VisualBrush)!.Visual as Control; + var flowDirection = (((IVisual)content!).VisualParent as Control)?.FlowDirection ?? FlowDirection.LeftToRight; + + rectangle.FlowDirection = flowDirection; + } + } + private void SelectFocusedItem() { foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers) diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 905ded193c..0804de3174 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -351,6 +351,7 @@ namespace Avalonia.Controls.UnitTests }; var target = new ComboBox { + FlowDirection = FlowDirection.RightToLeft, Items = items, Template = GetTemplate() }; @@ -368,7 +369,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void FlowDirection_Of_RectangleContent_Updated_After_Change_ComboBox() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + using (UnitTestApplication.Start(TestServices.RealStyler)) { var items = new[] { @@ -385,10 +386,10 @@ namespace Avalonia.Controls.UnitTests }; var root = new TestRoot(target); - target.ApplyTemplate(); target.Presenter.ApplyTemplate(); target.SelectedIndex = 0; + ((ContentPresenter)target.Presenter).UpdateChild(); var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; From c77c500bcd373fa3715cce5d5a851eaab89da408 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 31 May 2022 14:35:55 +0300 Subject: [PATCH 06/18] fixes tests --- src/Avalonia.Controls/ComboBox.cs | 6 +- .../ComboBoxTests.cs | 116 +++++++++--------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index fc7feca7f1..5ba1195159 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -432,11 +432,11 @@ namespace Avalonia.Controls private void UpdateFlowDirection() { - var rectangle = SelectionBoxItem as Rectangle; - if (rectangle != null) + if (SelectionBoxItem is Rectangle rectangle) { var content = (rectangle.Fill as VisualBrush)!.Visual as Control; - var flowDirection = (((IVisual)content!).VisualParent as Control)?.FlowDirection ?? FlowDirection.LeftToRight; + var flowDirection = (((IVisual)content!).VisualParent as Control)?.FlowDirection ?? + FlowDirection.LeftToRight; rectangle.FlowDirection = flowDirection; } diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 0804de3174..70b713d6d0 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -8,7 +8,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Media; -using Avalonia.Threading; +using Avalonia.VisualTree; using Avalonia.UnitTests; using Xunit; @@ -340,80 +340,77 @@ namespace Avalonia.Controls.UnitTests [Fact] public void FlowDirection_Of_RectangleContent_Shuold_Be_LeftToRight() { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + var items = new[] { - var items = new[] - { - new ComboBoxItem() - { - Content = new Control() - } - }; - var target = new ComboBox - { - FlowDirection = FlowDirection.RightToLeft, - Items = items, - Template = GetTemplate() - }; + new ComboBoxItem() + { + Content = new Control() + } + }; + var target = new ComboBox + { + FlowDirection = FlowDirection.RightToLeft, + Items = items, + Template = GetTemplate() + }; - var root = new TestRoot(target); - target.ApplyTemplate(); - target.SelectedIndex = 0; + var root = new TestRoot(target); + target.ApplyTemplate(); + target.SelectedIndex = 0; - var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; + var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; - Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection); - } + Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection); } [Fact] - public void FlowDirection_Of_RectangleContent_Updated_After_Change_ComboBox() + public void FlowDirection_Of_RectangleContent_Updated_After_InvalidateMirrorTransform() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + var parentContent = new Decorator() { - var items = new[] - { - new ComboBoxItem() - { - Content = new Control() - } - }; - var target = new ComboBox + Child = new Control() + }; + var items = new[] + { + new ComboBoxItem() { - FlowDirection = FlowDirection.RightToLeft, - Items = items, - Template = GetTemplate() - }; - - var root = new TestRoot(target); - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - target.SelectedIndex = 0; - ((ContentPresenter)target.Presenter).UpdateChild(); - - var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; - - // need help here, the 'rectangle' isn't connected to visual tree for some reason + Content = parentContent.Child + } + }; + var target = new ComboBox + { + FlowDirection = FlowDirection.RightToLeft, + Items = items, + Template = GetTemplate() + }; - Assert.True(rectangle.HasMirrorTransform); + var root = new TestRoot(target); + target.ApplyTemplate(); + target.SelectedIndex = 0; - target.FlowDirection = FlowDirection.LeftToRight; + var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; + Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection); - Assert.False(rectangle.HasMirrorTransform); - } + parentContent.FlowDirection = FlowDirection.RightToLeft; + target.InvalidateMirrorTransform(); + + Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection); } [Fact] - public void FlowDirection_Of_RectangleContent_Updated_After_Content_In_VisualTree() + public void FlowDirection_Of_RectangleContent_Updated_After_OpenPopup() { - using (UnitTestApplication.Start(TestServices.RealFocus)) + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Control content; - var items = new[] + var parentContent = new Decorator() { + Child = new Control() + }; + var items = new[] + { new ComboBoxItem() { - Content = content = new Control() + Content = parentContent.Child } }; var target = new ComboBox @@ -425,14 +422,17 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); target.SelectedIndex = 0; - // need help here how to connect 'content' to visual tree, or how to open popup - - var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; + Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection); + parentContent.FlowDirection = FlowDirection.RightToLeft; + + var popup = target.GetVisualDescendants().OfType().First(); + popup.PlacementTarget = new Window(); + popup.Open(); + Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection); } } From 5bf28b671d42b5682930e27014d5897fe90bda73 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 31 May 2022 20:42:16 +0300 Subject: [PATCH 07/18] some fixes --- tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 70b713d6d0..aa32af7e51 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -379,7 +379,6 @@ namespace Avalonia.Controls.UnitTests }; var target = new ComboBox { - FlowDirection = FlowDirection.RightToLeft, Items = items, Template = GetTemplate() }; @@ -392,7 +391,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection); parentContent.FlowDirection = FlowDirection.RightToLeft; - target.InvalidateMirrorTransform(); + target.FlowDirection = FlowDirection.RightToLeft; Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection); } From b6e66047f21cec797302c305b487b93881b7fa1c Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Wed, 1 Jun 2022 13:54:50 +0300 Subject: [PATCH 08/18] add fixes --- src/Avalonia.Controls/ComboBox.cs | 11 ++++++----- .../FlowDirectionTests.cs | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 5ba1195159..05be5ad00d 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -434,11 +434,12 @@ namespace Avalonia.Controls { if (SelectionBoxItem is Rectangle rectangle) { - var content = (rectangle.Fill as VisualBrush)!.Visual as Control; - var flowDirection = (((IVisual)content!).VisualParent as Control)?.FlowDirection ?? - FlowDirection.LeftToRight; - - rectangle.FlowDirection = flowDirection; + if ((rectangle.Fill as VisualBrush)?.Visual is Control content) + { + var flowDirection = (((IVisual)content!).VisualParent as Control)?.FlowDirection ?? + FlowDirection.LeftToRight; + rectangle.FlowDirection = flowDirection; + } } } diff --git a/tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs b/tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs index 6739eff638..6c43103ecb 100644 --- a/tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs +++ b/tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs @@ -17,7 +17,23 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void HasMirrorTransform_Of_Children_Is_Updated_After_Change() + public void HasMirrorTransform_Of_LTR_Children_Should_Be_True_For_RTL_Parent() + { + Control child; + var target = new Decorator + { + FlowDirection = FlowDirection.RightToLeft, + Child = child = new Control() + }; + + child.FlowDirection = FlowDirection.LeftToRight; + + Assert.True(target.HasMirrorTransform); + Assert.True(child.HasMirrorTransform); + } + + [Fact] + public void HasMirrorTransform_Of_Children_Is_Updated_After_Parent_Changeed() { Control child; var target = new Decorator From de3720ce77d181cab10be89f2f022c335b666dff Mon Sep 17 00:00:00 2001 From: ahmedmohammedfawzy <42243982+ahmedmohammedfawzy@users.noreply.github.com> Date: Thu, 9 Jun 2022 20:53:26 +0200 Subject: [PATCH 09/18] Update TextBox.cs Added the option to determine whether to ignore changes while the user is inputting or not --- src/Avalonia.Controls/TextBox.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 7652b23162..52e5da95b3 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -53,6 +53,9 @@ namespace Avalonia.Controls public static readonly StyledProperty PasswordCharProperty = AvaloniaProperty.Register(nameof(PasswordChar)); + + public static readonly StyledProperty IgnoreChangesWhileEditingProperty = + AvaloniaProperty.Register(nameof(IgnoreChangesWhileEditing)); public static readonly StyledProperty SelectionBrushProperty = AvaloniaProperty.Register(nameof(SelectionBrush)); @@ -276,6 +279,12 @@ namespace Avalonia.Controls get => GetValue(IsReadOnlyProperty); set => SetValue(IsReadOnlyProperty, value); } + + public bool IgnoreChangesWhileEditing + { + get => GetValue(IgnoreChangesWhileEditingProperty); + set => SetValue(IgnoreChangesWhileEditingProperty, value); + } public char PasswordChar { @@ -1501,7 +1510,9 @@ namespace Avalonia.Controls { try { - _ignoreTextChanges = true; + if (IgnoreChangesWhileEditing == true) + _ignoreTextChanges = true; + SetAndRaise(TextProperty, ref _text, value); } finally From 0868442ec92aa4d427a8e750e9b1b0232c9662ce Mon Sep 17 00:00:00 2001 From: ahmedmohammedfawzy <42243982+ahmedmohammedfawzy@users.noreply.github.com> Date: Thu, 9 Jun 2022 21:04:12 +0200 Subject: [PATCH 10/18] Update TextBox.cs --- src/Avalonia.Controls/TextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 52e5da95b3..77be6bd9ee 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -55,7 +55,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(PasswordChar)); public static readonly StyledProperty IgnoreChangesWhileEditingProperty = - AvaloniaProperty.Register(nameof(IgnoreChangesWhileEditing)); + AvaloniaProperty.Register(nameof(IgnoreChangesWhileEditing), true); public static readonly StyledProperty SelectionBrushProperty = AvaloniaProperty.Register(nameof(SelectionBrush)); From 5ebba8e68c960016c7054e80073ff952a0bc77c1 Mon Sep 17 00:00:00 2001 From: ahmedmohammedfawzy <42243982+ahmedmohammedfawzy@users.noreply.github.com> Date: Fri, 10 Jun 2022 08:55:08 +0200 Subject: [PATCH 11/18] Update TextBox.cs --- src/Avalonia.Controls/TextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 77be6bd9ee..52e5da95b3 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -55,7 +55,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(PasswordChar)); public static readonly StyledProperty IgnoreChangesWhileEditingProperty = - AvaloniaProperty.Register(nameof(IgnoreChangesWhileEditing), true); + AvaloniaProperty.Register(nameof(IgnoreChangesWhileEditing)); public static readonly StyledProperty SelectionBrushProperty = AvaloniaProperty.Register(nameof(SelectionBrush)); From 32e2043ec0f37327eb31c3db42dea9ca7ed661a1 Mon Sep 17 00:00:00 2001 From: Ahmed Fawzy Date: Fri, 10 Jun 2022 18:23:24 +0200 Subject: [PATCH 12/18] removed the _ignoreTextChanges field from the TextBox.cs --- src/Avalonia.Controls/TextBox.cs | 81 ++++++++++---------------------- 1 file changed, 24 insertions(+), 57 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 52e5da95b3..9531f719b9 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -54,9 +54,6 @@ namespace Avalonia.Controls public static readonly StyledProperty PasswordCharProperty = AvaloniaProperty.Register(nameof(PasswordChar)); - public static readonly StyledProperty IgnoreChangesWhileEditingProperty = - AvaloniaProperty.Register(nameof(IgnoreChangesWhileEditing)); - public static readonly StyledProperty SelectionBrushProperty = AvaloniaProperty.Register(nameof(SelectionBrush)); @@ -199,7 +196,6 @@ namespace Avalonia.Controls private TextBoxTextInputMethodClient _imClient = new TextBoxTextInputMethodClient(); private UndoRedoHelper _undoRedoHelper; private bool _isUndoingRedoing; - private bool _ignoreTextChanges; private bool _canCut; private bool _canCopy; private bool _canPaste; @@ -280,12 +276,6 @@ namespace Avalonia.Controls set => SetValue(IsReadOnlyProperty, value); } - public bool IgnoreChangesWhileEditing - { - get => GetValue(IgnoreChangesWhileEditingProperty); - set => SetValue(IgnoreChangesWhileEditingProperty, value); - } - public char PasswordChar { get => GetValue(PasswordCharProperty); @@ -377,21 +367,17 @@ namespace Avalonia.Controls get => _text; set { - if (!_ignoreTextChanges) - { - var caretIndex = CaretIndex; - var selectionStart = SelectionStart; - var selectionEnd = SelectionEnd; + var caretIndex = CaretIndex; + var selectionStart = SelectionStart; + var selectionEnd = SelectionEnd; - CaretIndex = CoerceCaretIndex(caretIndex, value); - SelectionStart = CoerceCaretIndex(selectionStart, value); - SelectionEnd = CoerceCaretIndex(selectionEnd, value); - - if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) - { - _undoRedoHelper.Clear(); - SnapshotUndoRedo(); // so we always have an initial state - } + CaretIndex = CoerceCaretIndex(caretIndex, value); + SelectionStart = CoerceCaretIndex(selectionStart, value); + SelectionEnd = CoerceCaretIndex(selectionEnd, value); + if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) + { + _undoRedoHelper.Clear(); + SnapshotUndoRedo(); // so we always have an initial state } } } @@ -745,32 +731,23 @@ namespace Avalonia.Controls { var oldText = _text; - _ignoreTextChanges = true; - - try - { - DeleteSelection(false); - var caretIndex = CaretIndex; - text = Text ?? string.Empty; - SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); - ClearSelection(); - - if (IsUndoEnabled) - { - _undoRedoHelper.DiscardRedo(); - } - - if (_text != oldText) - { - RaisePropertyChanged(TextProperty, oldText, _text); - } + DeleteSelection(false); + var caretIndex = CaretIndex; + text = Text ?? string.Empty; + SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); + ClearSelection(); - CaretIndex = caretIndex + input.Length; + if (IsUndoEnabled) + { + _undoRedoHelper.DiscardRedo(); } - finally + + if (_text != oldText) { - _ignoreTextChanges = false; + RaisePropertyChanged(TextProperty, oldText, _text); } + + CaretIndex = caretIndex + input.Length; } } @@ -1508,17 +1485,7 @@ namespace Avalonia.Controls { if (raiseTextChanged) { - try - { - if (IgnoreChangesWhileEditing == true) - _ignoreTextChanges = true; - - SetAndRaise(TextProperty, ref _text, value); - } - finally - { - _ignoreTextChanges = false; - } + SetAndRaise(TextProperty, ref _text, value); } else { From 7698505770347044562072fba43e9d73bcbc28cf Mon Sep 17 00:00:00 2001 From: Ahmed Fawzy Date: Fri, 10 Jun 2022 20:43:14 +0200 Subject: [PATCH 13/18] Removed tests which collided with removing the _ignoreTextChanges field --- .../MaskedTextBoxTests.cs | 28 ------------------- .../TextBoxTests.cs | 28 ------------------- 2 files changed, 56 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index af54be61f7..d1fa522206 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -179,34 +179,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int() - { - using (Start()) - { - var source = new Class1(); - var target = new MaskedTextBox - { - DataContext = source, - Template = CreateTemplate(), - }; - - target.ApplyTemplate(); - target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay)); - - Assert.Equal("0", target.Text); - - target.CaretIndex = 1; - target.RaiseEvent(new TextInputEventArgs - { - RoutedEvent = InputElement.TextInputEvent, - Text = "2", - }); - - Assert.Equal("02", target.Text); - } - } - [Fact] public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection() { diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index f15da8e0c5..23a330c96f 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -180,34 +180,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int() - { - using (UnitTestApplication.Start(Services)) - { - var source = new Class1(); - var target = new TextBox - { - DataContext = source, - Template = CreateTemplate(), - }; - - target.ApplyTemplate(); - target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay)); - - Assert.Equal("0", target.Text); - - target.CaretIndex = 1; - target.RaiseEvent(new TextInputEventArgs - { - RoutedEvent = InputElement.TextInputEvent, - Text = "2", - }); - - Assert.Equal("02", target.Text); - } - } - [Fact] public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection() { From 6a5e0055393dd61dede522d533a537be8d6f67d1 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 22 Jun 2022 00:05:28 -0400 Subject: [PATCH 14/18] Add trim_trailing_whitespace to editorconfig --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index 25e0135725..cb589a5ce1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -21,6 +21,7 @@ csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_between_query_expression_clauses = true +trim_trailing_whitespace = true # Indentation preferences csharp_indent_block_contents = true From 64518efc5184d67208a7d0c3b7df9c66a364a562 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Jun 2022 12:59:40 +0200 Subject: [PATCH 15/18] Add failing test for #8372. --- .../AvaloniaObjectTests_SetValue.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs index 954a609315..72162a4d8e 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs @@ -17,6 +17,21 @@ namespace Avalonia.Base.UnitTests Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } + [Fact] + public void ClearValue_Resets_Value_To_Style_value() + { + Class1 target = new Class1(); + + target.SetValue(Class1.FooProperty, "style", BindingPriority.Style); + target.SetValue(Class1.FooProperty, "local"); + + Assert.Equal("local", target.GetValue(Class1.FooProperty)); + + target.ClearValue(Class1.FooProperty); + + Assert.Equal("style", target.GetValue(Class1.FooProperty)); + } + [Fact] public void ClearValue_Raises_PropertyChanged() { From f33d4e881f3b4fb4db9a13279ad8bf1c648b3151 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Jun 2022 13:01:37 +0200 Subject: [PATCH 16/18] Correctly clear local value in PriorityValue. --- src/Avalonia.Base/PropertyStore/PriorityValue.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Base/PropertyStore/PriorityValue.cs b/src/Avalonia.Base/PropertyStore/PriorityValue.cs index 112cf6619f..182b2638c4 100644 --- a/src/Avalonia.Base/PropertyStore/PriorityValue.cs +++ b/src/Avalonia.Base/PropertyStore/PriorityValue.cs @@ -121,6 +121,7 @@ namespace Avalonia.PropertyStore public void ClearLocalValue() { + _localValue = default; UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs( _owner, Property, From c9e10f0d2f88346caeb461b900199b6fb571653d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Jun 2022 15:53:16 +0200 Subject: [PATCH 17/18] Added additional failing test. Exposed by the previous fix for #8372: re-entrancy in `PropertySetterInstance.Dispose()` is causing detaching a style to call `ClearValue` on the property. Previously this wasn't a problem as `ClearValue` didn't work, but now it is. (Also added one passing test which tests the same scenario in `PropertySetterBindingInstance` for future coverage) --- .../Styling/SetterTests.cs | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs index ed4c78aa3e..c684466200 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs @@ -150,13 +150,43 @@ namespace Avalonia.Base.UnitTests.Styling Assert.Equal(BindingPriority.StyleTrigger, control.GetDiagnostic(TextBlock.TagProperty).Priority); } - private IBinding CreateMockBinding(AvaloniaProperty property) + [Fact] + public void Disposing_Setter_Should_Preserve_LocalValue() { - var subject = new Subject(); - var descriptor = InstancedBinding.OneWay(subject); - var binding = Mock.Of(x => - x.Initiate(It.IsAny(), property, null, false) == descriptor); - return binding; + var control = new Canvas(); + var setter = new Setter(TextBlock.TagProperty, "foo"); + + var instance = setter.Instance(control); + instance.Start(true); + instance.Activate(); + + control.Tag = "bar"; + + instance.Dispose(); + + Assert.Equal("bar", control.Tag); + } + + [Fact] + public void Disposing_Binding_Setter_Should_Preserve_LocalValue() + { + var control = new Canvas(); + var source = new { Foo = "foo" }; + var setter = new Setter(TextBlock.TagProperty, new Binding + { + Source = source, + Path = nameof(source.Foo), + }); + + var instance = setter.Instance(control); + instance.Start(true); + instance.Activate(); + + control.Tag = "bar"; + + instance.Dispose(); + + Assert.Equal("bar", control.Tag); } private class TestConverter : IValueConverter From 857bfb5bd2b863825c09fbfb780dc379fba4d345 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Jun 2022 16:00:29 +0200 Subject: [PATCH 18/18] Prevent re-entrancy in PropertySetterInstance.Dispose. The call to `_subscription.Dispose()` causes `BindingEntry.Dispose()` to call `_subscription.Dispose()`, but in this case the `BindingEntry._subscription` instance is the `PropertySetterInstance`! Except now `PropertySetterInstance._subscription` is null, and so `PropertySetterInstance.Dispose` called `ClearValue`, which is obviously wrong. --- .../Styling/PropertySetterInstance.cs | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Base/Styling/PropertySetterInstance.cs b/src/Avalonia.Base/Styling/PropertySetterInstance.cs index c4e8f47e67..9028224cc1 100644 --- a/src/Avalonia.Base/Styling/PropertySetterInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterInstance.cs @@ -18,7 +18,7 @@ namespace Avalonia.Styling private readonly DirectPropertyBase? _directProperty; private readonly T _value; private IDisposable? _subscription; - private bool _isActive; + private State _state; public PropertySetterInstance( IStyleable target, @@ -40,6 +40,8 @@ namespace Avalonia.Styling _value = value; } + private bool IsActive => _state == State.Active; + public void Start(bool hasActivator) { if (hasActivator) @@ -70,31 +72,35 @@ namespace Avalonia.Styling public void Activate() { - if (!_isActive) + if (!IsActive) { - _isActive = true; + _state = State.Active; PublishNext(); } } public void Deactivate() { - if (_isActive) + if (IsActive) { - _isActive = false; + _state = State.Inactive; PublishNext(); } } public override void Dispose() { + if (_state == State.Disposed) + return; + _state = State.Disposed; + if (_subscription is object) { var sub = _subscription; _subscription = null; sub.Dispose(); } - else if (_isActive) + else if (IsActive) { if (_styledProperty is object) { @@ -114,7 +120,14 @@ namespace Avalonia.Styling private void PublishNext() { - PublishNext(_isActive ? new BindingValue(_value) : default); + PublishNext(IsActive ? new BindingValue(_value) : default); + } + + private enum State + { + Inactive, + Active, + Disposed, } } }