From 3eddf5cac4d081481675412ad7bdf0cc500e0b60 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Fri, 11 Mar 2022 14:03:42 +0200 Subject: [PATCH 01/32] first work --- samples/ControlCatalog/MainView.xaml | 8 +++ samples/ControlCatalog/MainView.xaml.cs | 9 +++ src/Avalonia.Controls/Control.cs | 74 +++++++++++++++++++++++++ src/Avalonia.Controls/TextBlock.cs | 2 + src/Avalonia.Controls/TextBox.cs | 2 + 5 files changed, 95 insertions(+) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index facce2aa82..35fe45a25f 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -187,6 +187,14 @@ Mica + + + LeftToRight + RightToLeft + + diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 79cf07c8d9..e8ea39abbb 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -76,6 +76,15 @@ namespace ControlCatalog } }; + var flowDirections = this.Find("FlowDirection"); + flowDirections.SelectionChanged += (sender, e) => + { + if (flowDirections.SelectedItem is FlowDirection flowDirection) + { + this.FlowDirection = flowDirection; + } + }; + var decorations = this.Find("Decorations"); decorations.SelectionChanged += (sender, e) => { diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index cec662aad8..265f46be5e 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -309,5 +309,79 @@ namespace Avalonia.Controls } } } + + static Control() + { + AffectsArrange(FlowDirectionProperty); + } + + private bool _mirrorApplied; + + protected virtual bool ShouldBeMirroredIfRightToLeft() + { + if (Parent is Control parent) + { + return parent.ShouldBeMirroredIfRightToLeft(); + } + else + { + return true; + } + } + + protected override void ArrangeCore(Rect finalRect) + { + base.ArrangeCore(finalRect); + + FlowDirection parentFD = FlowDirection.LeftToRight; + FlowDirection thisFD = FlowDirection; + bool shouldBeMirroredIfRightToLeft = ShouldBeMirroredIfRightToLeft(); + + if (Parent is Control control) + { + parentFD = control.FlowDirection; + } + + bool shouldMirror; + if (shouldBeMirroredIfRightToLeft) + { + shouldMirror = ShuoldApplyMirrorTransform(parentFD, thisFD); + if (this is PopupRoot && thisFD == FlowDirection.RightToLeft) + { + shouldMirror = true; + } + } + else + { + shouldMirror = ShuoldApplyMirrorTransform(parentFD, FlowDirection.LeftToRight); + } + + if (shouldMirror) + { + ApplyMirrorTransform(); + } + else + { + //RenderTransform = null; + } + } + + private void ApplyMirrorTransform() + { + if (_mirrorApplied) + { + return; + } + + var transform = new MatrixTransform(new Avalonia.Matrix(-1, 0, 0, 1, 0.0, 0.0)); + RenderTransform = transform; + _mirrorApplied = true; + } + + internal static bool ShuoldApplyMirrorTransform(FlowDirection parentFD, FlowDirection thisFD) + { + return ((parentFD == FlowDirection.LeftToRight && thisFD == FlowDirection.RightToLeft) || + (parentFD == FlowDirection.RightToLeft && thisFD == FlowDirection.LeftToRight)); + } } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 09f22612de..d29f094c38 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -612,5 +612,7 @@ namespace Avalonia.Controls { InvalidateTextLayout(); } + + protected override bool ShouldBeMirroredIfRightToLeft() => false; } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 4d71717776..76f7a185fe 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1504,5 +1504,7 @@ namespace Avalonia.Controls } } } + + protected override bool ShouldBeMirroredIfRightToLeft() => false; } } From baf77db493e29c0e880e37dd686dde693cddc015 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Sat, 12 Mar 2022 20:02:19 +0200 Subject: [PATCH 02/32] Merge branch 'master' into feature/flowDirectionImpl --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 ++ src/Avalonia.Controls/Control.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index caad8b0854..1fc37d903d 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -78,5 +78,7 @@ namespace ControlCatalog.Pages return new FormattedText(textToFormat, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Typeface.Default, 12, Brushes.Green); } + + protected override bool ShouldBeMirroredIfRightToLeft() => false; } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 265f46be5e..16d6c3f4c5 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -346,7 +346,7 @@ namespace Avalonia.Controls if (shouldBeMirroredIfRightToLeft) { shouldMirror = ShuoldApplyMirrorTransform(parentFD, thisFD); - if (this is PopupRoot && thisFD == FlowDirection.RightToLeft) + if (Parent is Popup && thisFD == FlowDirection.RightToLeft) { shouldMirror = true; } From d5fd84ebc018927aceaf365808efa3a49bb87fbe Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 15 Mar 2022 13:58:27 +0200 Subject: [PATCH 03/32] continue working --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Control.cs | 170 ++++++++++++++---- .../Presenters/TextPresenter.cs | 2 + src/Avalonia.Controls/TextBlock.cs | 2 +- src/Avalonia.Controls/TextBox.cs | 2 - src/Avalonia.Controls/TopLevel.cs | 2 + .../Controls/CheckBox.xaml | 1 + src/Avalonia.Visuals/Visual.cs | 2 +- 8 files changed, 142 insertions(+), 41 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 1fc37d903d..f65566a1e9 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -79,6 +79,6 @@ namespace ControlCatalog.Pages Typeface.Default, 12, Brushes.Green); } - protected override bool ShouldBeMirroredIfRightToLeft() => false; + protected override bool ShouldGetMirrored() => false; } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 16d6c3f4c5..9b41e9ee64 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -67,10 +67,17 @@ namespace Avalonia.Controls /// public static readonly AttachedProperty FlowDirectionProperty = AvaloniaProperty.RegisterAttached(nameof(FlowDirection), inherits: true); - + + /// + /// Defines the property. + /// + public static new readonly StyledProperty RenderTransformProperty = + Visual.RenderTransformProperty.AddOwner(); + private DataTemplates? _dataTemplates; private IControl? _focusAdorner; private AutomationPeer? _automationPeer; + private bool _hasMirrorTransform; /// /// Gets or sets the control's focus adorner. @@ -126,6 +133,21 @@ namespace Avalonia.Controls set => SetValue(FlowDirectionProperty, value); } + /// + public override ITransform? RenderTransform + { + get => base.RenderTransform; + set + { + if (_hasMirrorTransform) + { + value = MargeTransforms(MirrorTrasform(), value); + } + + base.RenderTransform = value; + } + } + /// /// Occurs when the user has completed a context input gesture, such as a right-click. /// @@ -312,76 +334,152 @@ namespace Avalonia.Controls static Control() { - AffectsArrange(FlowDirectionProperty); + //var m = new StyledPropertyMetadata(coerce: (s, e) => null); + //RenderTransformProperty.OverrideMetadata(m); + + //AffectsRender(FlowDirectionProperty); + //FlowDirectionProperty.Changed.AddClassHandler((s, e) => + //{ + // s.InvalidateFlowDirection(); + // foreach (var logical in LogicalTree.LogicalExtensions.GetLogicalDescendants(s)) + // { + // if (logical is Control control) + // { + // //if (control) + // //control.InvalidateFlowDirection(); + // } + // } + //}); } - private bool _mirrorApplied; - - protected virtual bool ShouldBeMirroredIfRightToLeft() + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - if (Parent is Control parent) - { - return parent.ShouldBeMirroredIfRightToLeft(); - } - else + base.OnPropertyChanged(change); + + if (change.Property == FlowDirectionProperty) { - return true; + // Avoid inherit value change to invoke this method + if (!GetBaseValue(FlowDirectionProperty, change.Priority).HasValue) + { + return; + } + + InvalidateFlowDirection(); } } - protected override void ArrangeCore(Rect finalRect) + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - base.ArrangeCore(finalRect); + base.OnAttachedToVisualTree(e); + + InvalidateFlowDirection(); + } + protected override void OnAttachedToLogicalTree(LogicalTree.LogicalTreeAttachmentEventArgs e) + { + base.OnAttachedToLogicalTree(e); + //InvalidateFlowDirection(); + } + + protected virtual bool ShouldGetMirrored() => true; + + private void InvalidateFlowDirection() + { FlowDirection parentFD = FlowDirection.LeftToRight; FlowDirection thisFD = FlowDirection; - bool shouldBeMirroredIfRightToLeft = ShouldBeMirroredIfRightToLeft(); - if (Parent is Control control) + bool parentShouldGetMirrored = true; + bool thisShouldGetMirrored = ShouldGetMirrored(); + + if (((Visual)this).GetVisualParent() is Control control) { parentFD = control.FlowDirection; + parentShouldGetMirrored = control.ShouldGetMirrored(); } - - bool shouldMirror; - if (shouldBeMirroredIfRightToLeft) + else if (Parent is Control logicalControl) { - shouldMirror = ShuoldApplyMirrorTransform(parentFD, thisFD); - if (Parent is Popup && thisFD == FlowDirection.RightToLeft) - { - shouldMirror = true; - } + parentFD = logicalControl.FlowDirection; + parentShouldGetMirrored = logicalControl.ShouldGetMirrored(); + } + + bool shouldBeMirrored = thisFD == FlowDirection.RightToLeft && thisShouldGetMirrored; + bool parentMirrored = parentFD == FlowDirection.RightToLeft && parentShouldGetMirrored; + + bool shouldApplyMirrorTransform = shouldBeMirrored != parentMirrored; + + if (shouldApplyMirrorTransform) + { + AddMirrorTransform(); } else { - shouldMirror = ShuoldApplyMirrorTransform(parentFD, FlowDirection.LeftToRight); + RemoveMirrorTransform(); } - if (shouldMirror) + foreach (var visual in VisualChildren) { - ApplyMirrorTransform(); + if (visual is Control child) + { + child.InvalidateFlowDirection(); + } } - else + } + + private void AddMirrorTransform() + { + if (_hasMirrorTransform) { - //RenderTransform = null; + return; } + + var mirrorTransform = MirrorTrasform(); + + ITransform? finalTransform = mirrorTransform; + if (RenderTransform != null) + { + finalTransform = MargeTransforms(RenderTransform, mirrorTransform); + } + + RenderTransform = finalTransform; + _hasMirrorTransform = true; } - private void ApplyMirrorTransform() + private void RemoveMirrorTransform() { - if (_mirrorApplied) + if (!_hasMirrorTransform) { return; } - var transform = new MatrixTransform(new Avalonia.Matrix(-1, 0, 0, 1, 0.0, 0.0)); - RenderTransform = transform; - _mirrorApplied = true; + var mirrorTransform = MirrorTrasform(); + + ITransform? finalTransform = MargeTransforms(RenderTransform, mirrorTransform); + if (finalTransform!.Value == Matrix.Identity) + { + finalTransform = null; + } + + _hasMirrorTransform = false; + RenderTransform = finalTransform; } - internal static bool ShuoldApplyMirrorTransform(FlowDirection parentFD, FlowDirection thisFD) + static ITransform? MargeTransforms(ITransform? iTransform1, ITransform? iTransform2) { - return ((parentFD == FlowDirection.LeftToRight && thisFD == FlowDirection.RightToLeft) || - (parentFD == FlowDirection.RightToLeft && thisFD == FlowDirection.LeftToRight)); + // don't know how to marge ITransform + if (iTransform1 is Transform transform1 && iTransform2 is Transform transform2) + { + TransformGroup groupTransform = new TransformGroup(); + + groupTransform.Children.Add(transform1); + groupTransform.Children.Add(transform2); + + return groupTransform; + } + + return iTransform1; } + + static ITransform MirrorTrasform() => + new MatrixTransform(new Avalonia.Matrix(-1, 0, 0, 1, 0.0, 0.0)); } } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 10ce31088a..858544a872 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -798,5 +798,7 @@ namespace Avalonia.Controls.Presenters } } } + + protected override bool ShouldGetMirrored() => false; } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index d29f094c38..a5b1d44dc8 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -613,6 +613,6 @@ namespace Avalonia.Controls InvalidateTextLayout(); } - protected override bool ShouldBeMirroredIfRightToLeft() => false; + protected override bool ShouldGetMirrored() => false; } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 76f7a185fe..4d71717776 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1504,7 +1504,5 @@ namespace Avalonia.Controls } } } - - protected override bool ShouldBeMirroredIfRightToLeft() => false; } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index a4fe154515..da85824457 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -529,5 +529,7 @@ namespace Avalonia.Controls ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod; + + protected override bool ShouldGetMirrored() => false; } } diff --git a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml index ef28593711..66dc17a417 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml @@ -152,6 +152,7 @@ + diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 324b253a0f..fcb4298895 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -222,7 +222,7 @@ namespace Avalonia /// /// Gets or sets the render transform of the control. /// - public ITransform? RenderTransform + public virtual ITransform? RenderTransform { get { return GetValue(RenderTransformProperty); } set { SetValue(RenderTransformProperty, value); } From 86a4ee86fb7d6022e4386e69d1e923f530027a9a Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 15 Mar 2022 17:24:57 +0200 Subject: [PATCH 04/32] continure working --- src/Avalonia.Controls/Control.cs | 52 ++++++++------------------------ 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 9b41e9ee64..0b74516c33 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -332,39 +332,17 @@ namespace Avalonia.Controls } } - static Control() - { - //var m = new StyledPropertyMetadata(coerce: (s, e) => null); - //RenderTransformProperty.OverrideMetadata(m); - - //AffectsRender(FlowDirectionProperty); - //FlowDirectionProperty.Changed.AddClassHandler((s, e) => - //{ - // s.InvalidateFlowDirection(); - // foreach (var logical in LogicalTree.LogicalExtensions.GetLogicalDescendants(s)) - // { - // if (logical is Control control) - // { - // //if (control) - // //control.InvalidateFlowDirection(); - // } - // } - //}); - } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == FlowDirectionProperty) { - // Avoid inherit value change to invoke this method - if (!GetBaseValue(FlowDirectionProperty, change.Priority).HasValue) + // Avoid inherited value change to call this method + if (GetBaseValue(FlowDirectionProperty, change.Priority).HasValue) { - return; + InvalidateFlowDirection(); } - - InvalidateFlowDirection(); } } @@ -375,14 +353,6 @@ namespace Avalonia.Controls InvalidateFlowDirection(); } - protected override void OnAttachedToLogicalTree(LogicalTree.LogicalTreeAttachmentEventArgs e) - { - base.OnAttachedToLogicalTree(e); - //InvalidateFlowDirection(); - } - - protected virtual bool ShouldGetMirrored() => true; - private void InvalidateFlowDirection() { FlowDirection parentFD = FlowDirection.LeftToRight; @@ -391,7 +361,7 @@ namespace Avalonia.Controls bool parentShouldGetMirrored = true; bool thisShouldGetMirrored = ShouldGetMirrored(); - if (((Visual)this).GetVisualParent() is Control control) + if (this.GetVisualParent() is Control control) { parentFD = control.FlowDirection; parentShouldGetMirrored = control.ShouldGetMirrored(); @@ -433,14 +403,15 @@ namespace Avalonia.Controls } var mirrorTransform = MirrorTrasform(); + var rendertransform = RenderTransform; ITransform? finalTransform = mirrorTransform; - if (RenderTransform != null) + if (rendertransform != null) { - finalTransform = MargeTransforms(RenderTransform, mirrorTransform); + finalTransform = MargeTransforms(rendertransform, mirrorTransform); } - RenderTransform = finalTransform; + base.RenderTransform = finalTransform; _hasMirrorTransform = true; } @@ -452,17 +423,20 @@ namespace Avalonia.Controls } var mirrorTransform = MirrorTrasform(); + var rendertransform = RenderTransform; - ITransform? finalTransform = MargeTransforms(RenderTransform, mirrorTransform); + ITransform? finalTransform = MargeTransforms(rendertransform, mirrorTransform); if (finalTransform!.Value == Matrix.Identity) { finalTransform = null; } _hasMirrorTransform = false; - RenderTransform = finalTransform; + base.RenderTransform = finalTransform; } + protected virtual bool ShouldGetMirrored() => true; + static ITransform? MargeTransforms(ITransform? iTransform1, ITransform? iTransform2) { // don't know how to marge ITransform From 031ff2f49888076424636c6c8473eb039f86c1a0 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 15 Mar 2022 17:34:09 +0200 Subject: [PATCH 05/32] continue --- src/Avalonia.Controls/Image.cs | 2 ++ src/Avalonia.Themes.Default/Controls/CheckBox.xaml | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 3d67880638..cbde2bd338 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -127,5 +127,7 @@ namespace Avalonia.Controls return new Size(); } } + + protected override bool ShouldGetMirrored() => false; } } diff --git a/src/Avalonia.Themes.Default/Controls/CheckBox.xaml b/src/Avalonia.Themes.Default/Controls/CheckBox.xaml index 75d6f853be..6cb991ba1b 100644 --- a/src/Avalonia.Themes.Default/Controls/CheckBox.xaml +++ b/src/Avalonia.Themes.Default/Controls/CheckBox.xaml @@ -26,6 +26,7 @@ Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center" + FlowDirection="LeftToRight" Data="M 1145.607177734375,430 C1145.607177734375,430 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1138,434.5538330078125 1138,434.5538330078125 1138,434.5538330078125 1141.482177734375,438 1141.482177734375,438 1141.482177734375,438 1141.96875,437.9375 1141.96875,437.9375 1141.96875,437.9375 1147,431.34619140625 1147,431.34619140625 1147,431.34619140625 1145.607177734375,430 1145.607177734375,430 z"/> Date: Wed, 16 Mar 2022 11:07:04 +0200 Subject: [PATCH 06/32] avoid re-calling the method --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Control.cs | 30 +++++++++++-------- src/Avalonia.Controls/Image.cs | 2 +- .../Presenters/TextPresenter.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 2 +- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index f65566a1e9..14e9587ca4 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -79,6 +79,6 @@ namespace ControlCatalog.Pages Typeface.Default, 12, Brushes.Green); } - protected override bool ShouldGetMirrored() => false; + protected override bool ShouldGetInvertedIfRightToLeft() => false; } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 0b74516c33..a1b5ced48b 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -338,10 +338,12 @@ namespace Avalonia.Controls if (change.Property == FlowDirectionProperty) { - // Avoid inherited value change to call this method + // A change in value inherited should be prevented from calling this method + // Because it will be handled from here by NotifyDescendantFlowDirection if (GetBaseValue(FlowDirectionProperty, change.Priority).HasValue) { InvalidateFlowDirection(); + NotifyDescendantFlowDirection(); } } } @@ -359,17 +361,13 @@ namespace Avalonia.Controls FlowDirection thisFD = FlowDirection; bool parentShouldGetMirrored = true; - bool thisShouldGetMirrored = ShouldGetMirrored(); + bool thisShouldGetMirrored = ShouldGetInvertedIfRightToLeft(); - if (this.GetVisualParent() is Control control) + var parent = this.FindAncestorOfType(); + if (parent != null) { - parentFD = control.FlowDirection; - parentShouldGetMirrored = control.ShouldGetMirrored(); - } - else if (Parent is Control logicalControl) - { - parentFD = logicalControl.FlowDirection; - parentShouldGetMirrored = logicalControl.ShouldGetMirrored(); + parentFD = parent.FlowDirection; + parentShouldGetMirrored = parent.ShouldGetInvertedIfRightToLeft(); } bool shouldBeMirrored = thisFD == FlowDirection.RightToLeft && thisShouldGetMirrored; @@ -385,8 +383,11 @@ namespace Avalonia.Controls { RemoveMirrorTransform(); } + } - foreach (var visual in VisualChildren) + private void NotifyDescendantFlowDirection() + { + foreach (var visual in this.GetVisualDescendants()) { if (visual is Control child) { @@ -435,7 +436,12 @@ namespace Avalonia.Controls base.RenderTransform = finalTransform; } - protected virtual bool ShouldGetMirrored() => true; + + /// + /// Determines whether the element should be inverted if the + /// flow direction is RightToLeft + /// + protected virtual bool ShouldGetInvertedIfRightToLeft() => true; static ITransform? MargeTransforms(ITransform? iTransform1, ITransform? iTransform2) { diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index cbde2bd338..621bf61a1d 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -128,6 +128,6 @@ namespace Avalonia.Controls } } - protected override bool ShouldGetMirrored() => false; + protected override bool ShouldGetInvertedIfRightToLeft() => false; } } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 858544a872..6d566a710c 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -799,6 +799,6 @@ namespace Avalonia.Controls.Presenters } } - protected override bool ShouldGetMirrored() => false; + protected override bool ShouldGetInvertedIfRightToLeft() => false; } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 45915b134a..376665a5cd 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -613,6 +613,6 @@ namespace Avalonia.Controls InvalidateTextLayout(); } - protected override bool ShouldGetMirrored() => false; + protected override bool ShouldGetInvertedIfRightToLeft() => false; } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index da85824457..4a66b8d6e6 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -530,6 +530,6 @@ namespace Avalonia.Controls ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod; - protected override bool ShouldGetMirrored() => false; + protected override bool ShouldGetInvertedIfRightToLeft() => false; } } From 0f5e78d70f2486edfa2b1d2798afcb2322439ede Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Wed, 16 Mar 2022 14:11:55 +0200 Subject: [PATCH 07/32] improve algorithm and typo fixes --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Control.cs | 38 +++++++++---------- src/Avalonia.Controls/Image.cs | 2 +- .../Presenters/TextPresenter.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 2 +- 6 files changed, 22 insertions(+), 26 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 14e9587ca4..66e0ae6846 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -79,6 +79,6 @@ namespace ControlCatalog.Pages Typeface.Default, 12, Brushes.Green); } - protected override bool ShouldGetInvertedIfRightToLeft() => false; + protected override bool ShouldApplyMirrorTransform() => false; } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index a1b5ced48b..88e14adff1 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -141,7 +141,7 @@ namespace Avalonia.Controls { if (_hasMirrorTransform) { - value = MargeTransforms(MirrorTrasform(), value); + value = MergeTransforms(MirrorTrasform(), value); } base.RenderTransform = value; @@ -357,23 +357,16 @@ namespace Avalonia.Controls private void InvalidateFlowDirection() { - FlowDirection parentFD = FlowDirection.LeftToRight; - FlowDirection thisFD = FlowDirection; - - bool parentShouldGetMirrored = true; - bool thisShouldGetMirrored = ShouldGetInvertedIfRightToLeft(); + bool parentShouldGetMirrored = false; + bool thisShouldGetMirrored = ShouldPresentedMirrored(); var parent = this.FindAncestorOfType(); if (parent != null) { - parentFD = parent.FlowDirection; - parentShouldGetMirrored = parent.ShouldGetInvertedIfRightToLeft(); + parentShouldGetMirrored = parent.ShouldPresentedMirrored(); } - bool shouldBeMirrored = thisFD == FlowDirection.RightToLeft && thisShouldGetMirrored; - bool parentMirrored = parentFD == FlowDirection.RightToLeft && parentShouldGetMirrored; - - bool shouldApplyMirrorTransform = shouldBeMirrored != parentMirrored; + bool shouldApplyMirrorTransform = thisShouldGetMirrored != parentShouldGetMirrored; if (shouldApplyMirrorTransform) { @@ -404,12 +397,12 @@ namespace Avalonia.Controls } var mirrorTransform = MirrorTrasform(); - var rendertransform = RenderTransform; + var renderTransform = RenderTransform; ITransform? finalTransform = mirrorTransform; - if (rendertransform != null) + if (renderTransform != null) { - finalTransform = MargeTransforms(rendertransform, mirrorTransform); + finalTransform = MergeTransforms(renderTransform, mirrorTransform); } base.RenderTransform = finalTransform; @@ -424,9 +417,9 @@ namespace Avalonia.Controls } var mirrorTransform = MirrorTrasform(); - var rendertransform = RenderTransform; + var renderTransform = RenderTransform; - ITransform? finalTransform = MargeTransforms(rendertransform, mirrorTransform); + ITransform? finalTransform = MergeTransforms(renderTransform, mirrorTransform); if (finalTransform!.Value == Matrix.Identity) { finalTransform = null; @@ -438,12 +431,15 @@ namespace Avalonia.Controls /// - /// Determines whether the element should be inverted if the - /// flow direction is RightToLeft + /// Determines whether the element should be presented mirrored + /// by FlowDirection system /// - protected virtual bool ShouldGetInvertedIfRightToLeft() => true; + protected virtual bool ShouldPresentedMirrored() + { + return FlowDirection == FlowDirection.RightToLeft; + } - static ITransform? MargeTransforms(ITransform? iTransform1, ITransform? iTransform2) + static ITransform? MergeTransforms(ITransform? iTransform1, ITransform? iTransform2) { // don't know how to marge ITransform if (iTransform1 is Transform transform1 && iTransform2 is Transform transform2) diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 621bf61a1d..2630c55d84 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -128,6 +128,6 @@ namespace Avalonia.Controls } } - protected override bool ShouldGetInvertedIfRightToLeft() => false; + protected override bool ShouldApplyMirrorTransform() => false; } } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 6d566a710c..bb31dcae10 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -799,6 +799,6 @@ namespace Avalonia.Controls.Presenters } } - protected override bool ShouldGetInvertedIfRightToLeft() => false; + protected override bool ShouldApplyMirrorTransform() => false; } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 376665a5cd..cc20850673 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -613,6 +613,6 @@ namespace Avalonia.Controls InvalidateTextLayout(); } - protected override bool ShouldGetInvertedIfRightToLeft() => false; + protected override bool ShouldApplyMirrorTransform() => false; } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 4a66b8d6e6..1e1433e20e 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -530,6 +530,6 @@ namespace Avalonia.Controls ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod; - protected override bool ShouldGetInvertedIfRightToLeft() => false; + protected override bool ShouldApplyMirrorTransform() => false; } } From feec27e39fd1b8d1af3bd5dbbdee84713438d66c Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Wed, 16 Mar 2022 14:17:56 +0200 Subject: [PATCH 08/32] some fixes --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Image.cs | 2 +- src/Avalonia.Controls/Presenters/TextPresenter.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 66e0ae6846..d9fda2e70a 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -79,6 +79,6 @@ namespace ControlCatalog.Pages Typeface.Default, 12, Brushes.Green); } - protected override bool ShouldApplyMirrorTransform() => false; + protected override bool ShouldPresentedMirrored() => false; } } diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 2630c55d84..b3275f6369 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -128,6 +128,6 @@ namespace Avalonia.Controls } } - protected override bool ShouldApplyMirrorTransform() => false; + protected override bool ShouldPresentedMirrored() => false; } } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index bb31dcae10..e125acbcfa 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -799,6 +799,6 @@ namespace Avalonia.Controls.Presenters } } - protected override bool ShouldApplyMirrorTransform() => false; + protected override bool ShouldPresentedMirrored() => false; } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index cc20850673..624f0b671a 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -613,6 +613,6 @@ namespace Avalonia.Controls InvalidateTextLayout(); } - protected override bool ShouldApplyMirrorTransform() => false; + protected override bool ShouldPresentedMirrored() => false; } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 1e1433e20e..71b8c09e28 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -530,6 +530,6 @@ namespace Avalonia.Controls ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod; - protected override bool ShouldApplyMirrorTransform() => false; + protected override bool ShouldPresentedMirrored() => false; } } From 343163d3e4befe48bafe4df4f63c9651fed2959b Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Wed, 16 Mar 2022 14:25:20 +0200 Subject: [PATCH 09/32] typo --- src/Avalonia.Controls/Control.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 88e14adff1..9d22eaff18 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -432,7 +432,7 @@ namespace Avalonia.Controls /// /// Determines whether the element should be presented mirrored - /// by FlowDirection system + /// if FlowDirection is RightToLeft /// protected virtual bool ShouldPresentedMirrored() { From fb5f6d25465c1e1490e240add7514d64753624d0 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Wed, 16 Mar 2022 16:28:53 +0200 Subject: [PATCH 10/32] implement mergeTransforms --- src/Avalonia.Controls/Control.cs | 33 +++++++------------ src/Avalonia.Visuals/ApiCompatBaseline.txt | 3 +- .../Media/TransformExtensions.cs | 18 ++++++++++ src/Avalonia.Visuals/Media/TransformGroup.cs | 18 ++++++++-- 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 9d22eaff18..fd2f28f361 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -141,7 +141,14 @@ namespace Avalonia.Controls { if (_hasMirrorTransform) { - value = MergeTransforms(MirrorTrasform(), value); + if (value == null) + { + value = MirrorTrasform(); + } + else + { + value = MirrorTrasform().MergeTransforms(value); + } } base.RenderTransform = value; @@ -402,7 +409,7 @@ namespace Avalonia.Controls ITransform? finalTransform = mirrorTransform; if (renderTransform != null) { - finalTransform = MergeTransforms(renderTransform, mirrorTransform); + finalTransform = mirrorTransform.MergeTransforms(renderTransform); } base.RenderTransform = finalTransform; @@ -416,10 +423,10 @@ namespace Avalonia.Controls return; } - var mirrorTransform = MirrorTrasform(); - var renderTransform = RenderTransform; + ITransform mirrorTransform = MirrorTrasform(); + ITransform renderTransform = RenderTransform!; - ITransform? finalTransform = MergeTransforms(renderTransform, mirrorTransform); + ITransform? finalTransform = mirrorTransform.MergeTransforms(renderTransform); if (finalTransform!.Value == Matrix.Identity) { finalTransform = null; @@ -439,22 +446,6 @@ namespace Avalonia.Controls return FlowDirection == FlowDirection.RightToLeft; } - static ITransform? MergeTransforms(ITransform? iTransform1, ITransform? iTransform2) - { - // don't know how to marge ITransform - if (iTransform1 is Transform transform1 && iTransform2 is Transform transform2) - { - TransformGroup groupTransform = new TransformGroup(); - - groupTransform.Children.Add(transform1); - groupTransform.Children.Add(transform2); - - return groupTransform; - } - - return iTransform1; - } - static ITransform MirrorTrasform() => new MatrixTransform(new Avalonia.Matrix(-1, 0, 0, 1, 0.0, 0.0)); } diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index b725993b44..b9b3d1906d 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -176,4 +176,5 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphR MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Rendering.RendererBase.RenderFps(Avalonia.Platform.IDrawingContextImpl, Avalonia.Rect, System.Nullable)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice..ctor(System.ReadOnlyMemory, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract. -Total Issues: 177 +CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Media.Transforms' does not inherit from base type 'Avalonia.Collections.AvaloniaList' in the implementation but it does in the contract. +Total Issues: 178 diff --git a/src/Avalonia.Visuals/Media/TransformExtensions.cs b/src/Avalonia.Visuals/Media/TransformExtensions.cs index ccf2231ce2..faea406c6d 100644 --- a/src/Avalonia.Visuals/Media/TransformExtensions.cs +++ b/src/Avalonia.Visuals/Media/TransformExtensions.cs @@ -22,5 +22,23 @@ namespace Avalonia.Media return (transform as Transform)?.ToImmutable() ?? new ImmutableTransform(transform.Value); } + + /// + /// Merge two transforms to an one transform. + /// + /// The original transform. + /// The additional transform to be added. + /// + /// One ITransform that includes both. + /// + public static ITransform MergeTransforms(this ITransform srcTransform, ITransform additionalTransform) + { + TransformGroup groupTransform = new TransformGroup(); + + groupTransform.Children.Add(srcTransform); + groupTransform.Children.Add(additionalTransform); + + return groupTransform; + } } } diff --git a/src/Avalonia.Visuals/Media/TransformGroup.cs b/src/Avalonia.Visuals/Media/TransformGroup.cs index 0465efd5a5..0c31d3fdcb 100644 --- a/src/Avalonia.Visuals/Media/TransformGroup.cs +++ b/src/Avalonia.Visuals/Media/TransformGroup.cs @@ -18,8 +18,20 @@ namespace Avalonia.Media Children.CollectionChanged += delegate { Children.ForEachItem( - (tr) => tr.Changed += ChildTransform_Changed, - (tr) => tr.Changed -= ChildTransform_Changed, + (tr) => + { + if (tr is IMutableTransform mutTr) + { + mutTr.Changed += ChildTransform_Changed; + } + }, + (tr) => + { + if (tr is IMutableTransform mutTr) + { + mutTr.Changed -= ChildTransform_Changed; + } + }, () => { }); }; } @@ -61,7 +73,7 @@ namespace Avalonia.Media } } - public sealed class Transforms : AvaloniaList + public sealed class Transforms : AvaloniaList { } } From b025186af0dd59b867cccbd1c9bb772d561170a8 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Thu, 17 Mar 2022 09:40:58 +0200 Subject: [PATCH 11/32] refactor mirror engine --- src/Avalonia.Controls/Control.cs | 85 ++----------------- src/Avalonia.Visuals/ApiCompatBaseline.txt | 5 +- .../Media/TransformExtensions.cs | 18 ---- src/Avalonia.Visuals/Media/TransformGroup.cs | 18 +--- .../Rendering/SceneGraph/SceneBuilder.cs | 6 ++ src/Avalonia.Visuals/Visual.cs | 23 ++++- src/Avalonia.Visuals/VisualExtensions.cs | 6 ++ src/Avalonia.Visuals/VisualTree/IVisual.cs | 5 ++ 8 files changed, 51 insertions(+), 115 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index fd2f28f361..f0d5b342e7 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -67,17 +67,10 @@ namespace Avalonia.Controls /// public static readonly AttachedProperty FlowDirectionProperty = AvaloniaProperty.RegisterAttached(nameof(FlowDirection), inherits: true); - - /// - /// Defines the property. - /// - public static new readonly StyledProperty RenderTransformProperty = - Visual.RenderTransformProperty.AddOwner(); private DataTemplates? _dataTemplates; private IControl? _focusAdorner; private AutomationPeer? _automationPeer; - private bool _hasMirrorTransform; /// /// Gets or sets the control's focus adorner. @@ -133,28 +126,6 @@ namespace Avalonia.Controls set => SetValue(FlowDirectionProperty, value); } - /// - public override ITransform? RenderTransform - { - get => base.RenderTransform; - set - { - if (_hasMirrorTransform) - { - if (value == null) - { - value = MirrorTrasform(); - } - else - { - value = MirrorTrasform().MergeTransforms(value); - } - } - - base.RenderTransform = value; - } - } - /// /// Occurs when the user has completed a context input gesture, such as a right-click. /// @@ -375,13 +346,15 @@ namespace Avalonia.Controls bool shouldApplyMirrorTransform = thisShouldGetMirrored != parentShouldGetMirrored; + if (this is IRenderRoot) shouldApplyMirrorTransform = false; + if (shouldApplyMirrorTransform) { - AddMirrorTransform(); + IsMirrorTransform = true; } else { - RemoveMirrorTransform(); + IsMirrorTransform = false; } } @@ -396,57 +369,15 @@ namespace Avalonia.Controls } } - private void AddMirrorTransform() - { - if (_hasMirrorTransform) - { - return; - } - - var mirrorTransform = MirrorTrasform(); - var renderTransform = RenderTransform; - - ITransform? finalTransform = mirrorTransform; - if (renderTransform != null) - { - finalTransform = mirrorTransform.MergeTransforms(renderTransform); - } - - base.RenderTransform = finalTransform; - _hasMirrorTransform = true; - } - - private void RemoveMirrorTransform() - { - if (!_hasMirrorTransform) - { - return; - } - - ITransform mirrorTransform = MirrorTrasform(); - ITransform renderTransform = RenderTransform!; - - ITransform? finalTransform = mirrorTransform.MergeTransforms(renderTransform); - if (finalTransform!.Value == Matrix.Identity) - { - finalTransform = null; - } - - _hasMirrorTransform = false; - base.RenderTransform = finalTransform; - } - - /// - /// Determines whether the element should be presented mirrored - /// if FlowDirection is RightToLeft + /// Determines whether the element should be presented mirrored, this + /// method related to FlowDirection system and as return true if FlowDirection + /// is RightToLeft. For controls that want to avoid this behavior, it is + /// possible to override this method and return false. /// protected virtual bool ShouldPresentedMirrored() { return FlowDirection == FlowDirection.RightToLeft; } - - static ITransform MirrorTrasform() => - new MatrixTransform(new Avalonia.Matrix(-1, 0, 0, 1, 0.0, 0.0)); } } diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index b9b3d1906d..300603b297 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -176,5 +176,6 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphR MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Rendering.RendererBase.RenderFps(Avalonia.Platform.IDrawingContextImpl, Avalonia.Rect, System.Nullable)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice..ctor(System.ReadOnlyMemory, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract. -CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Media.Transforms' does not inherit from base type 'Avalonia.Collections.AvaloniaList' in the implementation but it does in the contract. -Total Issues: 178 +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.VisualTree.IVisual.IsMirrorTransform' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.VisualTree.IVisual.IsMirrorTransform.get()' is present in the implementation but not in the contract. +Total Issues: 179 diff --git a/src/Avalonia.Visuals/Media/TransformExtensions.cs b/src/Avalonia.Visuals/Media/TransformExtensions.cs index faea406c6d..ccf2231ce2 100644 --- a/src/Avalonia.Visuals/Media/TransformExtensions.cs +++ b/src/Avalonia.Visuals/Media/TransformExtensions.cs @@ -22,23 +22,5 @@ namespace Avalonia.Media return (transform as Transform)?.ToImmutable() ?? new ImmutableTransform(transform.Value); } - - /// - /// Merge two transforms to an one transform. - /// - /// The original transform. - /// The additional transform to be added. - /// - /// One ITransform that includes both. - /// - public static ITransform MergeTransforms(this ITransform srcTransform, ITransform additionalTransform) - { - TransformGroup groupTransform = new TransformGroup(); - - groupTransform.Children.Add(srcTransform); - groupTransform.Children.Add(additionalTransform); - - return groupTransform; - } } } diff --git a/src/Avalonia.Visuals/Media/TransformGroup.cs b/src/Avalonia.Visuals/Media/TransformGroup.cs index 0c31d3fdcb..0465efd5a5 100644 --- a/src/Avalonia.Visuals/Media/TransformGroup.cs +++ b/src/Avalonia.Visuals/Media/TransformGroup.cs @@ -18,20 +18,8 @@ namespace Avalonia.Media Children.CollectionChanged += delegate { Children.ForEachItem( - (tr) => - { - if (tr is IMutableTransform mutTr) - { - mutTr.Changed += ChildTransform_Changed; - } - }, - (tr) => - { - if (tr is IMutableTransform mutTr) - { - mutTr.Changed -= ChildTransform_Changed; - } - }, + (tr) => tr.Changed += ChildTransform_Changed, + (tr) => tr.Changed -= ChildTransform_Changed, () => { }); }; } @@ -73,7 +61,7 @@ namespace Avalonia.Media } } - public sealed class Transforms : AvaloniaList + public sealed class Transforms : AvaloniaList { } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 63c22efc3f..4eb1c0528f 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -195,6 +195,12 @@ namespace Avalonia.Rendering.SceneGraph renderTransform = (-offset) * visual.RenderTransform.Value * (offset); } + if (visual.IsMirrorTransform) + { + var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); + renderTransform *= mirrorMatrix; + } + m = renderTransform * m; using (contextImpl.BeginUpdate(node)) diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index fcb4298895..80adfd31a8 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -68,6 +68,12 @@ namespace Avalonia public static readonly StyledProperty OpacityMaskProperty = AvaloniaProperty.Register(nameof(OpacityMask)); + /// + /// Defines the property. + /// + public static readonly DirectProperty IsMirrorTransformProperty = + AvaloniaProperty.RegisterDirect(nameof(IsMirrorTransform), o => o.IsMirrorTransform); + /// /// Defines the property. /// @@ -96,6 +102,7 @@ namespace Avalonia private TransformedBounds? _transformedBounds; private IRenderRoot? _visualRoot; private IVisual? _visualParent; + private bool _isMirrorTransform; /// /// Initializes static members of the class. @@ -107,7 +114,8 @@ namespace Avalonia ClipProperty, ClipToBoundsProperty, IsVisibleProperty, - OpacityProperty); + OpacityProperty, + IsMirrorTransformProperty); RenderTransformProperty.Changed.Subscribe(RenderTransformChanged); ZIndexProperty.Changed.Subscribe(ZIndexChanged); } @@ -119,7 +127,7 @@ namespace Avalonia { // Disable transitions until we're added to the visual tree. DisableTransitions(); - + var visualChildren = new AvaloniaList(); visualChildren.ResetBehavior = ResetBehavior.Remove; visualChildren.Validate = visual => ValidateVisualChild(visual); @@ -219,10 +227,19 @@ namespace Avalonia set { SetValue(OpacityMaskProperty, value); } } + /// + /// Gets or sets a value indicating whether this control presented as mirror. + /// + public bool IsMirrorTransform + { + get { return _isMirrorTransform; } + protected set { SetAndRaise(IsMirrorTransformProperty, ref _isMirrorTransform, value); } + } + /// /// Gets or sets the render transform of the control. /// - public virtual ITransform? RenderTransform + public ITransform? RenderTransform { get { return GetValue(RenderTransformProperty); } set { SetValue(RenderTransformProperty, value); } diff --git a/src/Avalonia.Visuals/VisualExtensions.cs b/src/Avalonia.Visuals/VisualExtensions.cs index ff8a515db3..b726c1f547 100644 --- a/src/Avalonia.Visuals/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualExtensions.cs @@ -110,6 +110,12 @@ namespace Avalonia result *= renderTransform; } + if (v.IsMirrorTransform) + { + var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, v.Bounds.Width, 0); + result *= mirrorMatrix; + } + var topLeft = v.Bounds.TopLeft; if (topLeft != default) diff --git a/src/Avalonia.Visuals/VisualTree/IVisual.cs b/src/Avalonia.Visuals/VisualTree/IVisual.cs index 97c4554de6..95fe665e89 100644 --- a/src/Avalonia.Visuals/VisualTree/IVisual.cs +++ b/src/Avalonia.Visuals/VisualTree/IVisual.cs @@ -75,6 +75,11 @@ namespace Avalonia.VisualTree /// IBrush? OpacityMask { get; set; } + /// + /// Gets a value indicating whether this control presented as mirror. + /// + bool IsMirrorTransform { get; } + /// /// Gets or sets the render transform of the control. /// From ce6c8297aac39f7aef2545393860a71fb61b2965 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Thu, 17 Mar 2022 09:46:33 +0200 Subject: [PATCH 12/32] typo --- src/Avalonia.Controls/Control.cs | 2 +- src/Avalonia.Visuals/Visual.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index f0d5b342e7..e237eed67b 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls /// public static readonly AttachedProperty FlowDirectionProperty = AvaloniaProperty.RegisterAttached(nameof(FlowDirection), inherits: true); - + private DataTemplates? _dataTemplates; private IControl? _focusAdorner; private AutomationPeer? _automationPeer; diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 80adfd31a8..d266fbd42c 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -127,7 +127,7 @@ namespace Avalonia { // Disable transitions until we're added to the visual tree. DisableTransitions(); - + var visualChildren = new AvaloniaList(); visualChildren.ResetBehavior = ResetBehavior.Remove; visualChildren.Validate = visual => ValidateVisualChild(visual); From fea93b99ccf316fec5b04673503b0d79b410b298 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Thu, 17 Mar 2022 09:48:50 +0200 Subject: [PATCH 13/32] typo --- src/Avalonia.Controls/Control.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index e237eed67b..91713f8ac4 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls /// public static readonly AttachedProperty FlowDirectionProperty = AvaloniaProperty.RegisterAttached(nameof(FlowDirection), inherits: true); - + private DataTemplates? _dataTemplates; private IControl? _focusAdorner; private AutomationPeer? _automationPeer; @@ -371,7 +371,7 @@ namespace Avalonia.Controls /// /// Determines whether the element should be presented mirrored, this - /// method related to FlowDirection system and as return true if FlowDirection + /// method related to FlowDirection system and returns true if FlowDirection /// is RightToLeft. For controls that want to avoid this behavior, it is /// possible to override this method and return false. /// From 37370d0cc6b9c2099496c820bcc28bf30233cb6e Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Thu, 17 Mar 2022 09:50:21 +0200 Subject: [PATCH 14/32] more fixes --- src/Avalonia.Controls/Control.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 91713f8ac4..8660e0a3af 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -346,8 +346,6 @@ namespace Avalonia.Controls bool shouldApplyMirrorTransform = thisShouldGetMirrored != parentShouldGetMirrored; - if (this is IRenderRoot) shouldApplyMirrorTransform = false; - if (shouldApplyMirrorTransform) { IsMirrorTransform = true; From aa6531b53c7ece44ffca20d2c23d3a4b88c37644 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Thu, 17 Mar 2022 09:52:51 +0200 Subject: [PATCH 15/32] typo --- src/Avalonia.Controls/Control.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 8660e0a3af..bd0ae14b6d 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls /// public static readonly AttachedProperty FlowDirectionProperty = AvaloniaProperty.RegisterAttached(nameof(FlowDirection), inherits: true); - + private DataTemplates? _dataTemplates; private IControl? _focusAdorner; private AutomationPeer? _automationPeer; From 0653521a103089cdba15c4dff867c67243d39788 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Thu, 17 Mar 2022 10:33:02 +0200 Subject: [PATCH 16/32] some fixes --- src/Avalonia.Controls/Control.cs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index bd0ae14b6d..18ca1437f8 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls /// public static readonly AttachedProperty FlowDirectionProperty = AvaloniaProperty.RegisterAttached(nameof(FlowDirection), inherits: true); - + private DataTemplates? _dataTemplates; private IControl? _focusAdorner; private AutomationPeer? _automationPeer; @@ -335,25 +335,18 @@ namespace Avalonia.Controls private void InvalidateFlowDirection() { - bool parentShouldGetMirrored = false; - bool thisShouldGetMirrored = ShouldPresentedMirrored(); + bool parentShouldPresentedMirrored = false; + bool thisShouldPresentedMirrored = ShouldPresentedMirrored(); var parent = this.FindAncestorOfType(); if (parent != null) { - parentShouldGetMirrored = parent.ShouldPresentedMirrored(); + parentShouldPresentedMirrored = parent.ShouldPresentedMirrored(); } - bool shouldApplyMirrorTransform = thisShouldGetMirrored != parentShouldGetMirrored; - - if (shouldApplyMirrorTransform) - { - IsMirrorTransform = true; - } - else - { - IsMirrorTransform = false; - } + bool shouldApplyMirrorTransform = thisShouldPresentedMirrored != parentShouldPresentedMirrored; + + IsMirrorTransform = shouldApplyMirrorTransform; } private void NotifyDescendantFlowDirection() From d78f8e495e7813efeaadbb28ba20a4d487cde1bd Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Thu, 17 Mar 2022 10:34:07 +0200 Subject: [PATCH 17/32] typo --- src/Avalonia.Controls/Control.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 18ca1437f8..99bf77cf8c 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls /// public static readonly AttachedProperty FlowDirectionProperty = AvaloniaProperty.RegisterAttached(nameof(FlowDirection), inherits: true); - + private DataTemplates? _dataTemplates; private IControl? _focusAdorner; private AutomationPeer? _automationPeer; @@ -345,7 +345,7 @@ namespace Avalonia.Controls } bool shouldApplyMirrorTransform = thisShouldPresentedMirrored != parentShouldPresentedMirrored; - + IsMirrorTransform = shouldApplyMirrorTransform; } From 1cc2de198482b5679df70fdcdadd999077f873f3 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Thu, 17 Mar 2022 12:15:57 +0200 Subject: [PATCH 18/32] calling InvalidateVisual() only when needed --- src/Avalonia.Controls/Control.cs | 2 ++ src/Avalonia.Visuals/Visual.cs | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 99bf77cf8c..852b6e2580 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -322,6 +322,8 @@ namespace Avalonia.Controls { InvalidateFlowDirection(); NotifyDescendantFlowDirection(); + + InvalidateVisual(); } } } diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index d266fbd42c..8d06f3c65d 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -114,8 +114,7 @@ namespace Avalonia ClipProperty, ClipToBoundsProperty, IsVisibleProperty, - OpacityProperty, - IsMirrorTransformProperty); + OpacityProperty); RenderTransformProperty.Changed.Subscribe(RenderTransformChanged); ZIndexProperty.Changed.Subscribe(ZIndexChanged); } From 8e9ec700b2da48038317652f33f784c57f0fc949 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Mon, 21 Mar 2022 10:32:07 +0200 Subject: [PATCH 19/32] fix ComboBox and start working Popup --- src/Avalonia.Controls/ComboBox.cs | 19 +++++++++ src/Avalonia.Controls/Control.cs | 40 +++++++++---------- src/Avalonia.Controls/MenuItem.cs | 31 ++++++++++++++ src/Avalonia.Controls/TopLevel.cs | 4 ++ .../Controls/MenuItem.xaml | 2 +- src/Avalonia.Visuals/Visual.cs | 2 +- src/Avalonia.Visuals/VisualTree/IVisual.cs | 2 +- 7 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index c5410ae9b0..f209a07a31 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -13,6 +13,7 @@ using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.VisualTree; +using Avalonia.LogicalTree; namespace Avalonia.Controls { @@ -182,6 +183,24 @@ namespace Avalonia.Controls this.UpdateSelectionBoxItem(SelectedItem); } + // Because the SelectedItem isn't connected to the visual tree + public override void InvalidateFlowDirection() + { + if (SelectedItem is Control selectedControl) + { + selectedControl.InvalidateFlowDirection(); + foreach (var logical in selectedControl.GetLogicalDescendants()) + { + if (logical is Control childControl) + { + childControl.InvalidateFlowDirection(); + } + } + } + + base.InvalidateFlowDirection(); + } + /// protected override void OnKeyDown(KeyEventArgs e) { diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 852b6e2580..70ba24c5bd 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -190,6 +190,7 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTreeCore(e); + InvalidateFlowDirection(); InitializeIfNeeded(); } @@ -317,25 +318,29 @@ namespace Avalonia.Controls if (change.Property == FlowDirectionProperty) { // A change in value inherited should be prevented from calling this method - // Because it will be handled from here by NotifyDescendantFlowDirection + // because it will be handled from here if (GetBaseValue(FlowDirectionProperty, change.Priority).HasValue) { InvalidateFlowDirection(); - NotifyDescendantFlowDirection(); + + foreach (var visual in this.GetVisualDescendants()) + { + if (visual is Control child) + { + child.InvalidateFlowDirection(); + } + } InvalidateVisual(); } } } - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - InvalidateFlowDirection(); - } - - private void InvalidateFlowDirection() + /// + /// Computes the value according to the + /// + /// + public virtual void InvalidateFlowDirection() { bool parentShouldPresentedMirrored = false; bool thisShouldPresentedMirrored = ShouldPresentedMirrored(); @@ -345,23 +350,16 @@ namespace Avalonia.Controls { parentShouldPresentedMirrored = parent.ShouldPresentedMirrored(); } + else if (this.Parent is Control logicalParent) + { + parentShouldPresentedMirrored = logicalParent.ShouldPresentedMirrored(); + } bool shouldApplyMirrorTransform = thisShouldPresentedMirrored != parentShouldPresentedMirrored; IsMirrorTransform = shouldApplyMirrorTransform; } - private void NotifyDescendantFlowDirection() - { - foreach (var visual in this.GetVisualDescendants()) - { - if (visual is Control child) - { - child.InvalidateFlowDirection(); - } - } - } - /// /// Determines whether the element should be presented mirrored, this /// method related to FlowDirection system and returns true if FlowDirection diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 34215e9713..b35fe818aa 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -14,6 +14,8 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.VisualTree; +using Avalonia.Media; +using Avalonia.Controls.Primitives.PopupPositioning; namespace Avalonia.Controls { @@ -409,6 +411,33 @@ namespace Avalonia.Controls } } + public override void InvalidateFlowDirection() + { + if (_popup != null && Parent is Menu) + { + var popupAnchor = FlowDirection == FlowDirection.LeftToRight ? + PopupAnchor.BottomLeft : PopupAnchor.BottomRight; + + var popupGravity = FlowDirection == FlowDirection.LeftToRight ? + PopupGravity.BottomRight : PopupGravity.BottomLeft; + + var placement = FlowDirection == FlowDirection.LeftToRight ? + PlacementMode.Right : PlacementMode.Left; + + _popup.PlacementAnchor = popupAnchor; + _popup.PlacementGravity = popupGravity; + } + else if (_popup != null) + { + var placement = FlowDirection == FlowDirection.LeftToRight ? + PlacementMode.Right : PlacementMode.Left; + + _popup.PlacementMode = placement; + } + + base.InvalidateFlowDirection(); + } + /// /// Called when the is clicked. /// @@ -493,6 +522,8 @@ namespace Avalonia.Controls _popup.Opened += PopupOpened; _popup.Closed += PopupClosed; } + + InvalidateFlowDirection(); } protected override AutomationPeer OnCreateAutomationPeer() diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 71b8c09e28..f1b311dc52 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -530,6 +530,10 @@ namespace Avalonia.Controls ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod; + public override void InvalidateFlowDirection() + { + } + protected override bool ShouldPresentedMirrored() => false; } } diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml index 831537f578..45949fee92 100644 --- a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml @@ -111,7 +111,6 @@ @@ -160,6 +159,7 @@ - /// Gets or sets a value indicating whether this control presented as mirror. + /// Gets or sets a value indicating whether to apply mirror transform on this control. /// public bool IsMirrorTransform { diff --git a/src/Avalonia.Visuals/VisualTree/IVisual.cs b/src/Avalonia.Visuals/VisualTree/IVisual.cs index 95fe665e89..58c6cfa0e1 100644 --- a/src/Avalonia.Visuals/VisualTree/IVisual.cs +++ b/src/Avalonia.Visuals/VisualTree/IVisual.cs @@ -76,7 +76,7 @@ namespace Avalonia.VisualTree IBrush? OpacityMask { get; set; } /// - /// Gets a value indicating whether this control presented as mirror. + /// Gets a value indicating whether to apply mirror transform on this control. /// bool IsMirrorTransform { get; } From 780ea035a3fae86d7887cd624ae294db684e3f81 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Mon, 21 Mar 2022 11:49:14 +0200 Subject: [PATCH 20/32] >implement FlowDirection on Popup --- src/Avalonia.Controls/MenuItem.cs | 29 ------------------ .../Primitives/OverlayPopupHost.cs | 2 +- .../PopupPositioning/IPopupPositioner.cs | 30 ++++++++++++++++++- src/Avalonia.Controls/Primitives/PopupRoot.cs | 2 +- .../Controls/MenuItem.xaml | 2 +- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index b35fe818aa..adb2e077d5 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -411,33 +411,6 @@ namespace Avalonia.Controls } } - public override void InvalidateFlowDirection() - { - if (_popup != null && Parent is Menu) - { - var popupAnchor = FlowDirection == FlowDirection.LeftToRight ? - PopupAnchor.BottomLeft : PopupAnchor.BottomRight; - - var popupGravity = FlowDirection == FlowDirection.LeftToRight ? - PopupGravity.BottomRight : PopupGravity.BottomLeft; - - var placement = FlowDirection == FlowDirection.LeftToRight ? - PlacementMode.Right : PlacementMode.Left; - - _popup.PlacementAnchor = popupAnchor; - _popup.PlacementGravity = popupGravity; - } - else if (_popup != null) - { - var placement = FlowDirection == FlowDirection.LeftToRight ? - PlacementMode.Right : PlacementMode.Left; - - _popup.PlacementMode = placement; - } - - base.InvalidateFlowDirection(); - } - /// /// Called when the is clicked. /// @@ -522,8 +495,6 @@ namespace Avalonia.Controls _popup.Opened += PopupOpened; _popup.Closed += PopupClosed; } - - InvalidateFlowDirection(); } protected override AutomationPeer OnCreateAutomationPeer() diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index 6251d5cda7..6ac544e0fe 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -76,7 +76,7 @@ namespace Avalonia.Controls.Primitives Rect? rect = null) { _positionerParameters.ConfigurePosition((TopLevel)_overlayLayer.GetVisualRoot()!, target, placement, offset, anchor, - gravity, constraintAdjustment, rect); + gravity, constraintAdjustment, rect, FlowDirection); UpdatePosition(); } diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index 340076a407..8daf1ac68a 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -46,6 +46,7 @@ Copyright © 2019 Nikita Tsukanov using System; using Avalonia.VisualTree; +using Avalonia.Media; namespace Avalonia.Controls.Primitives.PopupPositioning { @@ -444,7 +445,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning TopLevel topLevel, IVisual target, PlacementMode placement, Point offset, PopupAnchor anchor, PopupGravity gravity, - PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect) + PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect, + FlowDirection flowDirection) { // We need a better way for tracking the last pointer position #pragma warning disable CS0618 // Type or member is obsolete @@ -503,6 +505,32 @@ namespace Avalonia.Controls.Primitives.PopupPositioning else throw new InvalidOperationException("Invalid value for Popup.PlacementMode"); } + + // Invert coordinate system if FlowDirection is RTL + if (flowDirection == FlowDirection.RightToLeft) + { + if ((positionerParameters.Anchor & PopupAnchor.Right) == PopupAnchor.Right) + { + positionerParameters.Anchor ^= PopupAnchor.Right; + positionerParameters.Anchor |= PopupAnchor.Left; + } + else if ((positionerParameters.Anchor & PopupAnchor.Left) == PopupAnchor.Left) + { + positionerParameters.Anchor ^= PopupAnchor.Left; + positionerParameters.Anchor |= PopupAnchor.Right; + } + + if ((positionerParameters.Gravity & PopupGravity.Right) == PopupGravity.Right) + { + positionerParameters.Gravity ^= PopupGravity.Right; + positionerParameters.Gravity |= PopupGravity.Left; + } + else if ((positionerParameters.Gravity & PopupGravity.Left) == PopupGravity.Left) + { + positionerParameters.Gravity ^= PopupGravity.Left; + positionerParameters.Gravity |= PopupGravity.Right; + } + } } } diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 2f6df862cf..ac652fa53f 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -92,7 +92,7 @@ namespace Avalonia.Controls.Primitives Rect? rect = null) { _positionerParameters.ConfigurePosition(_parent, target, - placement, offset, anchor, gravity, constraintAdjustment, rect); + placement, offset, anchor, gravity, constraintAdjustment, rect, FlowDirection); if (_positionerParameters.Size != default) UpdatePosition(); diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml index 45949fee92..831537f578 100644 --- a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml @@ -111,6 +111,7 @@ @@ -159,7 +160,6 @@ Date: Mon, 21 Mar 2022 12:03:08 +0200 Subject: [PATCH 21/32] forget something --- src/Avalonia.Controls/MenuItem.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index adb2e077d5..34215e9713 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -14,8 +14,6 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.VisualTree; -using Avalonia.Media; -using Avalonia.Controls.Primitives.PopupPositioning; namespace Avalonia.Controls { From f5a21547b8ef79478a30d7a63da24d7d12714d42 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 22 Mar 2022 07:12:32 +0200 Subject: [PATCH 22/32] typo --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Control.cs | 14 +++++++------- src/Avalonia.Controls/Image.cs | 2 +- src/Avalonia.Controls/Presenters/TextPresenter.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index d9fda2e70a..3cd0521481 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -79,6 +79,6 @@ namespace ControlCatalog.Pages Typeface.Default, 12, Brushes.Green); } - protected override bool ShouldPresentedMirrored() => false; + protected override bool ShouldPresentMirrored() => false; } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 70ba24c5bd..16c3b1d5dd 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -342,31 +342,31 @@ namespace Avalonia.Controls /// public virtual void InvalidateFlowDirection() { - bool parentShouldPresentedMirrored = false; - bool thisShouldPresentedMirrored = ShouldPresentedMirrored(); + bool parentShouldPresentMirrored = false; + bool thisShouldPresentMirrored = ShouldPresentMirrored(); var parent = this.FindAncestorOfType(); if (parent != null) { - parentShouldPresentedMirrored = parent.ShouldPresentedMirrored(); + parentShouldPresentMirrored = parent.ShouldPresentMirrored(); } else if (this.Parent is Control logicalParent) { - parentShouldPresentedMirrored = logicalParent.ShouldPresentedMirrored(); + parentShouldPresentMirrored = logicalParent.ShouldPresentMirrored(); } - bool shouldApplyMirrorTransform = thisShouldPresentedMirrored != parentShouldPresentedMirrored; + bool shouldApplyMirrorTransform = thisShouldPresentMirrored != parentShouldPresentMirrored; IsMirrorTransform = shouldApplyMirrorTransform; } /// - /// Determines whether the element should be presented mirrored, this + /// Determines whether the element should be present mirrored, this /// method related to FlowDirection system and returns true if FlowDirection /// is RightToLeft. For controls that want to avoid this behavior, it is /// possible to override this method and return false. /// - protected virtual bool ShouldPresentedMirrored() + protected virtual bool ShouldPresentMirrored() { return FlowDirection == FlowDirection.RightToLeft; } diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index b3275f6369..fa654f8ad1 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -128,6 +128,6 @@ namespace Avalonia.Controls } } - protected override bool ShouldPresentedMirrored() => false; + protected override bool ShouldPresentMirrored() => false; } } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index e125acbcfa..9b77b6ade1 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -799,6 +799,6 @@ namespace Avalonia.Controls.Presenters } } - protected override bool ShouldPresentedMirrored() => false; + protected override bool ShouldPresentMirrored() => false; } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 624f0b671a..bf510014b5 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -613,6 +613,6 @@ namespace Avalonia.Controls InvalidateTextLayout(); } - protected override bool ShouldPresentedMirrored() => false; + protected override bool ShouldPresentMirrored() => false; } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index f1b311dc52..a54bcf3bff 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -534,6 +534,6 @@ namespace Avalonia.Controls { } - protected override bool ShouldPresentedMirrored() => false; + protected override bool ShouldPresentMirrored() => false; } } From 61ffbcc687bb6c44107a01474a7a371aec8d2e0f Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 22 Mar 2022 11:23:09 +0200 Subject: [PATCH 23/32] naming more like wpf --- samples/ControlCatalog/Pages/ScreenPage.cs | 4 +- src/Avalonia.Controls/ComboBox.cs | 16 +++++-- src/Avalonia.Controls/Control.cs | 42 +++++++++++-------- src/Avalonia.Controls/Image.cs | 4 +- .../Presenters/TextPresenter.cs | 4 +- src/Avalonia.Controls/TextBlock.cs | 4 +- src/Avalonia.Controls/TopLevel.cs | 12 +++--- 7 files changed, 52 insertions(+), 34 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 3cd0521481..3d3ed00aac 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -13,6 +13,8 @@ namespace ControlCatalog.Pages { private double _leftMost; + protected override bool BypassFlowDirectionPolicies => true; + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -78,7 +80,5 @@ namespace ControlCatalog.Pages return new FormattedText(textToFormat, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Typeface.Default, 12, Brushes.Green); } - - protected override bool ShouldPresentMirrored() => false; } } diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index f209a07a31..cc2f79f574 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -183,12 +183,24 @@ namespace Avalonia.Controls this.UpdateSelectionBoxItem(SelectedItem); } - // Because the SelectedItem isn't connected to the visual tree + // Because the SelectedItem and his children sometimes isn't connected + // to the visual tree and sometimes not to logical tree public override void InvalidateFlowDirection() { + base.InvalidateFlowDirection(); + if (SelectedItem is Control selectedControl) { selectedControl.InvalidateFlowDirection(); + + foreach (var visual in selectedControl.GetVisualDescendants()) + { + if (visual is Control childControl) + { + childControl.InvalidateFlowDirection(); + } + } + foreach (var logical in selectedControl.GetLogicalDescendants()) { if (logical is Control childControl) @@ -197,8 +209,6 @@ namespace Avalonia.Controls } } } - - base.InvalidateFlowDirection(); } /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 16c3b1d5dd..ab8d17fbdf 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -140,6 +140,17 @@ namespace Avalonia.Controls /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; + /// + /// Gets a value indicating whether control bypass FlowDirecton policies. + /// + /// + /// Related to FlowDirection system and returns false as default, so if + /// is RTL then control will get a mirror presentation. + /// For controls that want to avoid mirror presentation, it is possible to override + /// this property and return true. + /// + protected virtual bool BypassFlowDirectionPolicies => false; + /// void ISetterValue.Initialize(ISetter setter) { @@ -338,37 +349,34 @@ namespace Avalonia.Controls /// /// Computes the value according to the - /// + /// and /// public virtual void InvalidateFlowDirection() { - bool parentShouldPresentMirrored = false; - bool thisShouldPresentMirrored = ShouldPresentMirrored(); + FlowDirection thisFD = this.FlowDirection; + FlowDirection parentFD = FlowDirection.LeftToRight; + + bool thisBypassFlowDirectionPolicies = BypassFlowDirectionPolicies; + bool parentBypassFlowDirectionPolicies = false; var parent = this.FindAncestorOfType(); if (parent != null) { - parentShouldPresentMirrored = parent.ShouldPresentMirrored(); + parentFD = parent.FlowDirection; + parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies; } else if (this.Parent is Control logicalParent) { - parentShouldPresentMirrored = logicalParent.ShouldPresentMirrored(); + parentFD = logicalParent.FlowDirection; + parentBypassFlowDirectionPolicies = logicalParent.BypassFlowDirectionPolicies; } - bool shouldApplyMirrorTransform = thisShouldPresentMirrored != parentShouldPresentMirrored; + bool thisShouldBeMirrored = thisFD == FlowDirection.RightToLeft && !BypassFlowDirectionPolicies; + bool parentShouldBeMirrored = parentFD == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies; - IsMirrorTransform = shouldApplyMirrorTransform; - } + bool shouldApplyMirrorTransform = thisShouldBeMirrored != parentShouldBeMirrored; - /// - /// Determines whether the element should be present mirrored, this - /// method related to FlowDirection system and returns true if FlowDirection - /// is RightToLeft. For controls that want to avoid this behavior, it is - /// possible to override this method and return false. - /// - protected virtual bool ShouldPresentMirrored() - { - return FlowDirection == FlowDirection.RightToLeft; + IsMirrorTransform = shouldApplyMirrorTransform; } } } diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index fa654f8ad1..7408bff902 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -66,6 +66,8 @@ namespace Avalonia.Controls set { SetValue(StretchDirectionProperty, value); } } + protected override bool BypassFlowDirectionPolicies => true; + /// /// Renders the control. /// @@ -127,7 +129,5 @@ namespace Avalonia.Controls return new Size(); } } - - protected override bool ShouldPresentMirrored() => false; } } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 9b77b6ade1..5539afad46 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -281,6 +281,8 @@ namespace Avalonia.Controls.Presenters } } + protected override bool BypassFlowDirectionPolicies => true; + /// /// Creates the used to render the text. /// @@ -798,7 +800,5 @@ namespace Avalonia.Controls.Presenters } } } - - protected override bool ShouldPresentMirrored() => false; } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index bf510014b5..49800e5760 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -329,6 +329,8 @@ namespace Avalonia.Controls set => SetValue(TextDecorationsProperty, value); } + protected override bool BypassFlowDirectionPolicies => true; + /// /// Gets the value of the attached on a control. /// @@ -612,7 +614,5 @@ namespace Avalonia.Controls { InvalidateTextLayout(); } - - protected override bool ShouldPresentMirrored() => false; } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index a54bcf3bff..9bc96805a5 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -348,6 +348,12 @@ namespace Avalonia.Controls /// protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this); + public override void InvalidateFlowDirection() + { + } + + protected override bool BypassFlowDirectionPolicies => true; + /// /// Handles a paint notification from . /// @@ -529,11 +535,5 @@ namespace Avalonia.Controls ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod; - - public override void InvalidateFlowDirection() - { - } - - protected override bool ShouldPresentMirrored() => false; } } From 8f1720b22cc9f6c5e76bdfd9fa2e1cf26fa4b704 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 22 Mar 2022 11:27:14 +0200 Subject: [PATCH 24/32] typo --- src/Avalonia.Controls/Control.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index ab8d17fbdf..c8149fec10 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -146,8 +146,7 @@ namespace Avalonia.Controls /// /// Related to FlowDirection system and returns false as default, so if /// is RTL then control will get a mirror presentation. - /// For controls that want to avoid mirror presentation, it is possible to override - /// this property and return true. + /// For controls that want to avoid this behavior, override this property and return true. /// protected virtual bool BypassFlowDirectionPolicies => false; From 610a138ca704f825665c40f34379310cfc00a25d Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 22 Mar 2022 22:33:58 +0200 Subject: [PATCH 25/32] some cleanup --- src/Avalonia.Controls/Control.cs | 19 ++++++------------- src/Avalonia.Visuals/Visual.cs | 3 ++- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index c8149fec10..8c6ac0718a 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -324,24 +324,17 @@ namespace Avalonia.Controls protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); - + if (change.Property == FlowDirectionProperty) { - // A change in value inherited should be prevented from calling this method - // because it will be handled from here - if (GetBaseValue(FlowDirectionProperty, change.Priority).HasValue) + InvalidateFlowDirection(); + + foreach (var visual in VisualChildren) { - InvalidateFlowDirection(); - - foreach (var visual in this.GetVisualDescendants()) + if (visual is Control child) { - if (visual is Control child) - { - child.InvalidateFlowDirection(); - } + child.InvalidateFlowDirection(); } - - InvalidateVisual(); } } } diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index b72b4e1f45..42e0ad3b88 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -114,7 +114,8 @@ namespace Avalonia ClipProperty, ClipToBoundsProperty, IsVisibleProperty, - OpacityProperty); + OpacityProperty, + IsMirrorTransformProperty); RenderTransformProperty.Changed.Subscribe(RenderTransformChanged); ZIndexProperty.Changed.Subscribe(ZIndexChanged); } From 69559267749f48578207cde63999467f09e54b0d Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Tue, 22 Mar 2022 22:54:00 +0200 Subject: [PATCH 26/32] some fixes --- src/Avalonia.Controls/ComboBox.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index cc2f79f574..19d3bc9382 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -183,8 +183,7 @@ namespace Avalonia.Controls this.UpdateSelectionBoxItem(SelectedItem); } - // Because the SelectedItem and his children sometimes isn't connected - // to the visual tree and sometimes not to logical tree + // Because the SelectedItem isn't connected to the visual tree public override void InvalidateFlowDirection() { base.InvalidateFlowDirection(); @@ -200,14 +199,6 @@ namespace Avalonia.Controls childControl.InvalidateFlowDirection(); } } - - foreach (var logical in selectedControl.GetLogicalDescendants()) - { - if (logical is Control childControl) - { - childControl.InvalidateFlowDirection(); - } - } } } From 9da03a1421fa1dda4eedfd00ea8b941c4639638e Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Wed, 23 Mar 2022 17:42:20 +0200 Subject: [PATCH 27/32] cleanup --- src/Avalonia.Controls/ComboBox.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 19d3bc9382..4927f17172 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -13,7 +13,6 @@ using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.VisualTree; -using Avalonia.LogicalTree; namespace Avalonia.Controls { From 28f66403d303018376cdbb42e618b24cb9bd1015 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Mon, 28 Mar 2022 09:55:15 +0300 Subject: [PATCH 28/32] etc --- src/Avalonia.Controls/Control.cs | 9 ++++++++- src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 8c6ac0718a..8bb0774655 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -200,7 +200,6 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTreeCore(e); - InvalidateFlowDirection(); InitializeIfNeeded(); } @@ -210,6 +209,14 @@ namespace Avalonia.Controls base.OnDetachedFromVisualTreeCore(e); } + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + InvalidateFlowDirection(); + } + /// protected override void OnGotFocus(GotFocusEventArgs e) { diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 23016de148..d92919884b 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -285,6 +285,12 @@ namespace Avalonia.Rendering renderTransform = (-offset) * visual.RenderTransform.Value * (offset); } + if (visual.IsMirrorTransform) + { + var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); + renderTransform *= mirrorMatrix; + } + m = renderTransform * m; if (clipToBounds) From d8530e4baa5734089374de1f0b2080d1d5f95346 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Mon, 4 Apr 2022 22:07:30 +0300 Subject: [PATCH 29/32] oops.. --- src/Avalonia.Controls/TextBlock.cs | 40 ------------------------------ 1 file changed, 40 deletions(-) diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 47d55757d0..51069abe8f 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -320,46 +320,6 @@ namespace Avalonia.Controls protected override bool BypassFlowDirectionPolicies => true; - /// - /// Gets the value of the attached on a control. - /// - /// The control. - /// The font family. - public static FontFamily GetFontFamily(Control control) - { - return control.GetValue(FontFamilyProperty); - } - - /// - /// Gets the value of the attached on a control. - /// - /// The control. - /// The font size. - public static double GetFontSize(Control control) - { - return control.GetValue(FontSizeProperty); - } - - /// - /// Gets the value of the attached on a control. - /// - /// The control. - /// The font style. - public static FontStyle GetFontStyle(Control control) - { - return control.GetValue(FontStyleProperty); - } - - /// - /// Gets the value of the attached on a control. - /// - /// The control. - /// The font weight. - public static FontWeight GetFontWeight(Control control) - { - return control.GetValue(FontWeightProperty); - } - /// /// The BaselineOffset property provides an adjustment to baseline offset /// From ac18fb35978fe3c6237ce934eb10157c49e237f1 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Mon, 4 Apr 2022 22:09:28 +0300 Subject: [PATCH 30/32] same --- src/Avalonia.Controls/TextBlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 51069abe8f..703b851c79 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -317,7 +317,7 @@ namespace Avalonia.Controls get => GetValue(TextDecorationsProperty); set => SetValue(TextDecorationsProperty, value); } - + protected override bool BypassFlowDirectionPolicies => true; /// From dd8787c98adcb95687075f4587a81d0de2f36896 Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Wed, 6 Apr 2022 15:25:54 +0300 Subject: [PATCH 31/32] some fixes --- src/Avalonia.Controls/Control.cs | 20 +++++++++---------- src/Avalonia.Visuals/ApiCompatBaseline.txt | 4 ++-- .../Rendering/ImmediateRenderer.cs | 2 +- .../Rendering/SceneGraph/SceneBuilder.cs | 2 +- src/Avalonia.Visuals/Visual.cs | 16 +++++++-------- src/Avalonia.Visuals/VisualExtensions.cs | 2 +- src/Avalonia.Visuals/VisualTree/IVisual.cs | 2 +- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 5337915dae..817ab1c8e6 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -367,35 +367,35 @@ namespace Avalonia.Controls } /// - /// Computes the value according to the + /// Computes the value according to the /// and /// public virtual void InvalidateFlowDirection() { - FlowDirection thisFD = this.FlowDirection; - FlowDirection parentFD = FlowDirection.LeftToRight; + var flowDirection = this.FlowDirection; + var parentFlowDirection = FlowDirection.LeftToRight; - bool thisBypassFlowDirectionPolicies = BypassFlowDirectionPolicies; + bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies; bool parentBypassFlowDirectionPolicies = false; var parent = this.FindAncestorOfType(); if (parent != null) { - parentFD = parent.FlowDirection; + parentFlowDirection = parent.FlowDirection; parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies; } - else if (this.Parent is Control logicalParent) + else if (Parent is Control logicalParent) { - parentFD = logicalParent.FlowDirection; + parentFlowDirection = logicalParent.FlowDirection; parentBypassFlowDirectionPolicies = logicalParent.BypassFlowDirectionPolicies; } - bool thisShouldBeMirrored = thisFD == FlowDirection.RightToLeft && !BypassFlowDirectionPolicies; - bool parentShouldBeMirrored = parentFD == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies; + bool thisShouldBeMirrored = flowDirection == FlowDirection.RightToLeft && !bypassFlowDirectionPolicies; + bool parentShouldBeMirrored = parentFlowDirection == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies; bool shouldApplyMirrorTransform = thisShouldBeMirrored != parentShouldBeMirrored; - IsMirrorTransform = shouldApplyMirrorTransform; + HasMirrorTransform = shouldApplyMirrorTransform; } } } diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index cfe2dc2d52..0e82ef7367 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -182,7 +182,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.TextFo InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' is present in the contract but not in the implementation. MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Rendering.RendererBase.RenderFps(Avalonia.Platform.IDrawingContextImpl, Avalonia.Rect, System.Nullable)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.VisualTree.IVisual.IsMirrorTransform' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.VisualTree.IVisual.IsMirrorTransform.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.VisualTree.IVisual.HasMirrorTransform' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.VisualTree.IVisual.HasMirrorTransform.get()' is present in the implementation but not in the contract. MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice..ctor(System.ReadOnlyMemory, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract. Total Issues: 186 diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index d92919884b..2c0298affa 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -285,7 +285,7 @@ namespace Avalonia.Rendering renderTransform = (-offset) * visual.RenderTransform.Value * (offset); } - if (visual.IsMirrorTransform) + if (visual.HasMirrorTransform) { var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); renderTransform *= mirrorMatrix; diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 4eb1c0528f..019c3e0e9b 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -195,7 +195,7 @@ namespace Avalonia.Rendering.SceneGraph renderTransform = (-offset) * visual.RenderTransform.Value * (offset); } - if (visual.IsMirrorTransform) + if (visual.HasMirrorTransform) { var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); renderTransform *= mirrorMatrix; diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 42e0ad3b88..26a7d5d3b8 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -69,10 +69,10 @@ namespace Avalonia AvaloniaProperty.Register(nameof(OpacityMask)); /// - /// Defines the property. + /// Defines the property. /// - public static readonly DirectProperty IsMirrorTransformProperty = - AvaloniaProperty.RegisterDirect(nameof(IsMirrorTransform), o => o.IsMirrorTransform); + public static readonly DirectProperty HasMirrorTransformProperty = + AvaloniaProperty.RegisterDirect(nameof(HasMirrorTransform), o => o.HasMirrorTransform); /// /// Defines the property. @@ -102,7 +102,7 @@ namespace Avalonia private TransformedBounds? _transformedBounds; private IRenderRoot? _visualRoot; private IVisual? _visualParent; - private bool _isMirrorTransform; + private bool _hasMirrorTransform; /// /// Initializes static members of the class. @@ -115,7 +115,7 @@ namespace Avalonia ClipToBoundsProperty, IsVisibleProperty, OpacityProperty, - IsMirrorTransformProperty); + HasMirrorTransformProperty); RenderTransformProperty.Changed.Subscribe(RenderTransformChanged); ZIndexProperty.Changed.Subscribe(ZIndexChanged); } @@ -230,10 +230,10 @@ namespace Avalonia /// /// Gets or sets a value indicating whether to apply mirror transform on this control. /// - public bool IsMirrorTransform + public bool HasMirrorTransform { - get { return _isMirrorTransform; } - protected set { SetAndRaise(IsMirrorTransformProperty, ref _isMirrorTransform, value); } + get { return _hasMirrorTransform; } + protected set { SetAndRaise(HasMirrorTransformProperty, ref _hasMirrorTransform, value); } } /// diff --git a/src/Avalonia.Visuals/VisualExtensions.cs b/src/Avalonia.Visuals/VisualExtensions.cs index b726c1f547..3a3c2693d0 100644 --- a/src/Avalonia.Visuals/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualExtensions.cs @@ -110,7 +110,7 @@ namespace Avalonia result *= renderTransform; } - if (v.IsMirrorTransform) + if (v.HasMirrorTransform) { var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, v.Bounds.Width, 0); result *= mirrorMatrix; diff --git a/src/Avalonia.Visuals/VisualTree/IVisual.cs b/src/Avalonia.Visuals/VisualTree/IVisual.cs index 58c6cfa0e1..b1251618c4 100644 --- a/src/Avalonia.Visuals/VisualTree/IVisual.cs +++ b/src/Avalonia.Visuals/VisualTree/IVisual.cs @@ -78,7 +78,7 @@ namespace Avalonia.VisualTree /// /// Gets a value indicating whether to apply mirror transform on this control. /// - bool IsMirrorTransform { get; } + bool HasMirrorTransform { get; } /// /// Gets or sets the render transform of the control. From e4c4c40807e58bc7eb3ddb9ecd94c84c167dc7de Mon Sep 17 00:00:00 2001 From: daniel mayost Date: Wed, 6 Apr 2022 17:49:48 +0300 Subject: [PATCH 32/32] same --- src/Avalonia.Controls/ComboBox.cs | 8 ++++---- src/Avalonia.Controls/Control.cs | 8 ++++---- src/Avalonia.Controls/TopLevel.cs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index b60f596a5c..a6dee5cfaa 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -185,19 +185,19 @@ namespace Avalonia.Controls } // Because the SelectedItem isn't connected to the visual tree - public override void InvalidateFlowDirection() + public override void InvalidateMirrorTransform() { - base.InvalidateFlowDirection(); + base.InvalidateMirrorTransform(); if (SelectedItem is Control selectedControl) { - selectedControl.InvalidateFlowDirection(); + selectedControl.InvalidateMirrorTransform(); foreach (var visual in selectedControl.GetVisualDescendants()) { if (visual is Control childControl) { - childControl.InvalidateFlowDirection(); + childControl.InvalidateMirrorTransform(); } } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 817ab1c8e6..2c696c8f74 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -234,7 +234,7 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTree(e); - InvalidateFlowDirection(); + InvalidateMirrorTransform(); } /// @@ -354,13 +354,13 @@ namespace Avalonia.Controls if (change.Property == FlowDirectionProperty) { - InvalidateFlowDirection(); + InvalidateMirrorTransform(); foreach (var visual in VisualChildren) { if (visual is Control child) { - child.InvalidateFlowDirection(); + child.InvalidateMirrorTransform(); } } } @@ -370,7 +370,7 @@ namespace Avalonia.Controls /// Computes the value according to the /// and /// - public virtual void InvalidateFlowDirection() + public virtual void InvalidateMirrorTransform() { var flowDirection = this.FlowDirection; var parentFlowDirection = FlowDirection.LeftToRight; diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 073aa67c05..55202dd20d 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -350,7 +350,7 @@ namespace Avalonia.Controls /// protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this); - public override void InvalidateFlowDirection() + public override void InvalidateMirrorTransform() { }