From b04a1f78646099fa6f2a3dd4026ff2e7697d61cb Mon Sep 17 00:00:00 2001 From: Anton Mitsengendler Date: Sun, 26 Jul 2020 22:12:39 +0300 Subject: [PATCH 01/10] Changed resource name for a default placeholder foreground Added template binding for the placeholder foreground property to make it customizable from user code --- src/Avalonia.Themes.Default/ComboBox.xaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Default/ComboBox.xaml b/src/Avalonia.Themes.Default/ComboBox.xaml index 8ee818bad2..0fd6b144a4 100644 --- a/src/Avalonia.Themes.Default/ComboBox.xaml +++ b/src/Avalonia.Themes.Default/ComboBox.xaml @@ -25,7 +25,7 @@ - + Date: Tue, 28 Jul 2020 10:09:30 +0800 Subject: [PATCH 02/10] add missing text content alignment in fluent textbox theme --- src/Avalonia.Themes.Fluent/TextBox.xaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/TextBox.xaml b/src/Avalonia.Themes.Fluent/TextBox.xaml index 0327e776e3..9ae0ea0b31 100644 --- a/src/Avalonia.Themes.Fluent/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/TextBox.xaml @@ -79,7 +79,9 @@ PasswordChar="{TemplateBinding PasswordChar}" SelectionBrush="{TemplateBinding SelectionBrush}" SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" - CaretBrush="{TemplateBinding CaretBrush}"/> + CaretBrush="{TemplateBinding CaretBrush}" + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> From 493b6ab29e7993ccd0b7f4c0c9ec2f1959e351b3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 29 Jul 2020 14:28:08 -0300 Subject: [PATCH 03/10] add failing unit test. --- .../ToolTipTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs index 9d7bc6af74..67df6343af 100644 --- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs @@ -64,6 +64,40 @@ namespace Avalonia.Controls.UnitTests Assert.False(ToolTip.GetIsOpen(target)); } } + + [Fact] + public void Should_Close_When_Tip_Is_Changed() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = new Window(); + + var panel = new Panel(); + + var target = new Decorator() + { + [ToolTip.TipProperty] = "Tip", + [ToolTip.ShowDelayProperty] = 0 + }; + + panel.Children.Add(target); + + window.Content = panel; + + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + + Assert.True((target as IVisual).IsAttachedToVisualTree); + + _mouseHelper.Enter(target); + + Assert.True(ToolTip.GetIsOpen(target)); + + ToolTip.SetTip(target, ""); + + Assert.False(ToolTip.GetIsOpen(target)); + } + } [Fact] public void Should_Open_On_Pointer_Enter() From d5e0634cc7a654931ef8da338c93cccb317d7c9b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 29 Jul 2020 14:28:52 -0300 Subject: [PATCH 04/10] force tooltip to close if the datacontext and therefore the tip property change. --- src/Avalonia.Controls/ToolTipService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index 569697304f..587bbb6aa6 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -29,6 +29,7 @@ namespace Avalonia.Controls control.PointerEnter -= ControlPointerEnter; control.PointerLeave -= ControlPointerLeave; control.DetachedFromVisualTree -= ControlDetaching; + Close(control); } if (e.NewValue != null) From 78188c55175ecb8bf75e10b5bb9583f675c6ba28 Mon Sep 17 00:00:00 2001 From: Anton Mitsengendler Date: Wed, 29 Jul 2020 21:17:25 +0300 Subject: [PATCH 05/10] Added template binding for ComboBox placeholder foreground in Fluent Theme --- src/Avalonia.Themes.Fluent/ComboBox.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Themes.Fluent/ComboBox.xaml b/src/Avalonia.Themes.Fluent/ComboBox.xaml index 2788344842..97f36169ed 100644 --- a/src/Avalonia.Themes.Fluent/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/ComboBox.xaml @@ -79,6 +79,7 @@ VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}" Text="{TemplateBinding PlaceholderText}" + Foreground="{TemplateBinding PlaceholderForeground}" IsVisible="{TemplateBinding SelectionBoxItem, Converter={x:Static ObjectConverters.IsNull}}" /> Date: Thu, 30 Jul 2020 09:05:41 +0800 Subject: [PATCH 06/10] add hca/vca to watermark --- src/Avalonia.Themes.Fluent/TextBox.xaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/TextBox.xaml b/src/Avalonia.Themes.Fluent/TextBox.xaml index 9ae0ea0b31..a72c7586e9 100644 --- a/src/Avalonia.Themes.Fluent/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/TextBox.xaml @@ -66,7 +66,9 @@ Text="{TemplateBinding Watermark}" TextAlignment="{TemplateBinding TextAlignment}" TextWrapping="{TemplateBinding TextWrapping}" - IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"/> + IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}" + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> Date: Thu, 30 Jul 2020 10:48:29 -0300 Subject: [PATCH 07/10] add unit test for specific scenario where datacontext changes tip before remvoed from visual tree. --- .../ToolTipTests.cs | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs index 67df6343af..e52e7a487b 100644 --- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Disposables; +using Avalonia.Markup.Xaml; using Avalonia.Platform; using Avalonia.Threading; using Avalonia.UnitTests; @@ -66,34 +67,33 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Should_Close_When_Tip_Is_Changed() + public void Should_Close_When_Tip_Is_Opened_And_Detached_From_Visual_Tree() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window(); - - var panel = new Panel(); - - var target = new Decorator() - { - [ToolTip.TipProperty] = "Tip", - [ToolTip.ShowDelayProperty] = 0 - }; + var xaml = @" + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); - panel.Children.Add(target); - - window.Content = panel; - + window.DataContext = new ToolTipViewModel(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); + var target = window.Find("PART_target"); + var panel = window.Find("PART_panel"); + Assert.True((target as IVisual).IsAttachedToVisualTree); _mouseHelper.Enter(target); Assert.True(ToolTip.GetIsOpen(target)); - - ToolTip.SetTip(target, ""); + + panel.Children.Remove(target); Assert.False(ToolTip.GetIsOpen(target)); } @@ -242,4 +242,9 @@ namespace Avalonia.Controls.UnitTests } } } + + internal class ToolTipViewModel + { + public string Tip => "Tip"; + } } From eb132105e43adef9ce6d2feb4675dbab30b1904d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 30 Jul 2020 10:52:47 -0300 Subject: [PATCH 08/10] restore tooltipservice. failing unit test. --- src/Avalonia.Controls/ToolTipService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index 587bbb6aa6..569697304f 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -29,7 +29,6 @@ namespace Avalonia.Controls control.PointerEnter -= ControlPointerEnter; control.PointerLeave -= ControlPointerLeave; control.DetachedFromVisualTree -= ControlDetaching; - Close(control); } if (e.NewValue != null) From 49e41b959438fa4ccab96fcc4b18cef696b859c9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 30 Jul 2020 13:00:05 -0300 Subject: [PATCH 09/10] prevent tooltipgetting stuck. --- src/Avalonia.Controls/ToolTip.cs | 1 + src/Avalonia.Controls/ToolTipService.cs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index b458b15c64..cf0652247f 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -66,6 +66,7 @@ namespace Avalonia.Controls static ToolTip() { TipProperty.Changed.Subscribe(ToolTipService.Instance.TipChanged); + IsOpenProperty.Changed.Subscribe(ToolTipService.Instance.TipOpenChanged); IsOpenProperty.Changed.Subscribe(IsOpenChanged); } diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index 569697304f..e2a0f9e50c 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -28,20 +28,33 @@ namespace Avalonia.Controls { control.PointerEnter -= ControlPointerEnter; control.PointerLeave -= ControlPointerLeave; - control.DetachedFromVisualTree -= ControlDetaching; } if (e.NewValue != null) { control.PointerEnter += ControlPointerEnter; control.PointerLeave += ControlPointerLeave; + } + } + + internal void TipOpenChanged(AvaloniaPropertyChangedEventArgs e) + { + var control = (Control)e.Sender; + + if (e.OldValue is false && e.NewValue is true) + { control.DetachedFromVisualTree += ControlDetaching; } + else if(e.OldValue is true && e.NewValue is false) + { + control.DetachedFromVisualTree -= ControlDetaching; + } } private void ControlDetaching(object sender, VisualTreeAttachmentEventArgs e) { var control = (Control)sender; + control.DetachedFromVisualTree -= ControlDetaching; Close(control); } From 151ea1b1819cccd6b3f184b72e7d8c8283192e37 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 30 Jul 2020 23:59:29 +0200 Subject: [PATCH 10/10] Limit amount of reentrant dispatcher calls. --- .../Threading/AvaloniaScheduler.cs | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs b/src/Avalonia.Base/Threading/AvaloniaScheduler.cs index 0bfc713ba0..397826df53 100644 --- a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs +++ b/src/Avalonia.Base/Threading/AvaloniaScheduler.cs @@ -9,6 +9,16 @@ namespace Avalonia.Threading /// public class AvaloniaScheduler : LocalScheduler { + /// + /// Users can schedule actions on the dispatcher thread while being on the correct thread already. + /// We are optimizing this case by invoking user callback immediately which can lead to stack overflows in certain cases. + /// To prevent this we are limiting amount of reentrant calls to before we will + /// schedule on a dispatcher anyway. + /// + private const int MaxReentrantSchedules = 32; + + private int _reentrancyGuard; + /// /// The instance of the . /// @@ -24,31 +34,58 @@ namespace Avalonia.Threading /// public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { - var composite = new CompositeDisposable(2); + IDisposable PostOnDispatcher() + { + var composite = new CompositeDisposable(2); + + var cancellation = new CancellationDisposable(); + + Dispatcher.UIThread.Post(() => + { + if (!cancellation.Token.IsCancellationRequested) + { + composite.Add(action(this, state)); + } + }, DispatcherPriority.DataBind); + + composite.Add(cancellation); + + return composite; + } + if (dueTime == TimeSpan.Zero) { if (!Dispatcher.UIThread.CheckAccess()) { - var cancellation = new CancellationDisposable(); - Dispatcher.UIThread.Post(() => - { - if (!cancellation.Token.IsCancellationRequested) - { - composite.Add(action(this, state)); - } - }, DispatcherPriority.DataBind); - composite.Add(cancellation); + return PostOnDispatcher(); } else { - return action(this, state); + if (_reentrancyGuard >= MaxReentrantSchedules) + { + return PostOnDispatcher(); + } + + try + { + _reentrancyGuard++; + + return action(this, state); + } + finally + { + _reentrancyGuard--; + } } } else { + var composite = new CompositeDisposable(2); + composite.Add(DispatcherTimer.RunOnce(() => composite.Add(action(this, state)), dueTime)); + + return composite; } - return composite; } } }