Browse Source

Merge branch 'master' into fixes/fix-missed-resources

pull/4411/head
Max Katz 6 years ago
committed by GitHub
parent
commit
29f631b1ca
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 61
      src/Avalonia.Base/Threading/AvaloniaScheduler.cs
  2. 1
      src/Avalonia.Controls/ToolTip.cs
  3. 15
      src/Avalonia.Controls/ToolTipService.cs
  4. 3
      src/Avalonia.Themes.Default/ComboBox.xaml
  5. 1
      src/Avalonia.Themes.Fluent/ComboBox.xaml
  6. 8
      src/Avalonia.Themes.Fluent/TextBox.xaml
  7. 39
      tests/Avalonia.Controls.UnitTests/ToolTipTests.cs

61
src/Avalonia.Base/Threading/AvaloniaScheduler.cs

@ -9,6 +9,16 @@ namespace Avalonia.Threading
/// </summary>
public class AvaloniaScheduler : LocalScheduler
{
/// <summary>
/// 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 <see cref="Schedule{TState}"/> before we will
/// schedule on a dispatcher anyway.
/// </summary>
private const int MaxReentrantSchedules = 32;
private int _reentrancyGuard;
/// <summary>
/// The instance of the <see cref="AvaloniaScheduler"/>.
/// </summary>
@ -24,31 +34,58 @@ namespace Avalonia.Threading
/// <inheritdoc/>
public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> 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;
}
}
}

1
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);
}

15
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);
}

3
src/Avalonia.Themes.Default/ComboBox.xaml

@ -25,7 +25,7 @@
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="4" />
<Setter Property="MinHeight" Value="20" />
<Setter Property="PlaceholderForeground" Value="{DynamicResource ComboBoxPlaceHolderForeground}" />
<Setter Property="PlaceholderForeground" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="border"
@ -39,6 +39,7 @@
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
Text="{TemplateBinding PlaceholderText}"
Foreground="{TemplateBinding PlaceholderForeground}"
IsVisible="{TemplateBinding SelectionBoxItem, Converter={x:Static ObjectConverters.IsNull}}" />
<ContentControl Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding ItemTemplate}"

1
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}}" />
<ContentControl x:Name="ContentPresenter"
Content="{TemplateBinding SelectionBoxItem}"

8
src/Avalonia.Themes.Fluent/TextBox.xaml

@ -75,7 +75,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}"/>
<!-- TODO eliminate this margin... text layout issue? -->
<TextPresenter Name="PART_TextPresenter"
Margin="0 1 0 0"
@ -88,7 +90,9 @@
PasswordChar="{TemplateBinding PasswordChar}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretBrush="{TemplateBinding CaretBrush}"/>
CaretBrush="{TemplateBinding CaretBrush}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Panel>
</ScrollViewer>
</DataValidationErrors>

39
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;
@ -64,6 +65,39 @@ namespace Avalonia.Controls.UnitTests
Assert.False(ToolTip.GetIsOpen(target));
}
}
[Fact]
public void Should_Close_When_Tip_Is_Opened_And_Detached_From_Visual_Tree()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Panel x:Name='PART_panel'>
<Decorator x:Name='PART_target' ToolTip.Tip='{Binding Tip}' ToolTip.ShowDelay='0' />
</Panel>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
window.DataContext = new ToolTipViewModel();
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
var target = window.Find<Decorator>("PART_target");
var panel = window.Find<Panel>("PART_panel");
Assert.True((target as IVisual).IsAttachedToVisualTree);
_mouseHelper.Enter(target);
Assert.True(ToolTip.GetIsOpen(target));
panel.Children.Remove(target);
Assert.False(ToolTip.GetIsOpen(target));
}
}
[Fact]
public void Should_Open_On_Pointer_Enter()
@ -208,4 +242,9 @@ namespace Avalonia.Controls.UnitTests
}
}
}
internal class ToolTipViewModel
{
public string Tip => "Tip";
}
}

Loading…
Cancel
Save