Browse Source

Merge branch 'master' into fixes/2518-invalidate-in-sceneinvalidated

pull/2524/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
503d638ac0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitignore
  2. 3
      samples/ControlCatalog.Desktop/Program.cs
  3. 3
      samples/ControlCatalog.NetCore/Program.cs
  4. 2
      samples/ControlCatalog/SideBar.xaml
  5. 2
      samples/RenderDemo/SideBar.xaml
  6. 2
      samples/interop/WindowsInteropTest/Program.cs
  7. 29
      src/Avalonia.Animation/Animatable.cs
  8. 5
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  9. 11
      src/Avalonia.Animation/TransitionInstance.cs
  10. 3
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  11. 20
      src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs
  12. 17
      src/Avalonia.Controls/AppBuilderBase.cs
  13. 205
      src/Avalonia.Controls/Application.cs
  14. 18
      src/Avalonia.Controls/ExitEventArgs.cs
  15. 29
      src/Avalonia.Controls/Expander.cs
  16. 12
      src/Avalonia.Controls/IApplicationLifecycle.cs
  17. 18
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  18. 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  19. 16
      src/Avalonia.Controls/ShutdownMode.cs
  20. 36
      src/Avalonia.Controls/StartupEventArgs.cs
  21. 4
      src/Avalonia.Controls/TopLevel.cs
  22. 18
      src/Avalonia.Controls/Window.cs
  23. 12
      src/Avalonia.Controls/WindowCollection.cs
  24. 27
      src/Avalonia.Input/MouseDevice.cs
  25. 2
      src/Avalonia.Styling/Controls/IResourceProvider.cs
  26. 2
      src/Avalonia.Styling/Controls/ResourceDictionary.cs
  27. 10
      src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs
  28. 19
      src/Avalonia.Styling/StyledElement.cs
  29. 2
      src/Avalonia.Styling/Styling/Style.cs
  30. 2
      src/Avalonia.Styling/Styling/Styles.cs
  31. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  32. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
  33. 2
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  34. 24
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  35. 18
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  36. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  37. 73
      tests/Avalonia.Animation.UnitTests/TransitionsTests.cs
  38. 48
      tests/Avalonia.Controls.UnitTests/ApplicationTests.cs
  39. 2
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  40. 133
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  41. 60
      tests/Avalonia.LeakTests/TransitionTests.cs
  42. 3
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  43. 31
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
  44. 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithCustomProperty.xaml
  45. 79
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
  46. 7
      tests/Avalonia.Styling.UnitTests/StyledElementTests.cs
  47. 23
      tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs
  48. 10
      tests/Avalonia.UnitTests/MockGlobalClock.cs
  49. 6
      tests/Avalonia.UnitTests/TestServices.cs
  50. 2
      tests/Avalonia.UnitTests/UnitTestApplication.cs

2
.gitignore

@ -196,3 +196,5 @@ ModuleCache.noindex/
Build/Intermediates.noindex/
info.plist
build-intermediate
obj-Direct2D1/
obj-Skia/

3
samples/ControlCatalog.Desktop/Program.cs

@ -26,8 +26,7 @@ namespace ControlCatalog
=> AppBuilder.Configure<App>()
.LogToDebug()
.UsePlatformDetect()
.UseReactiveUI()
.UseDataGrid();
.UseReactiveUI();
private static void ConfigureAssetAssembly(AppBuilder builder)
{

3
samples/ControlCatalog.NetCore/Program.cs

@ -47,8 +47,7 @@ namespace ControlCatalog.NetCore
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseSkia()
.UseReactiveUI()
.UseDataGrid();
.UseReactiveUI();
static void ConsoleSilencer()
{

2
samples/ControlCatalog/SideBar.xaml

@ -56,7 +56,7 @@
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
<DoubleTransition Property="Opacity" Duration="0:0:0.150"/>
</Transitions>
</Setter>
</Style>

2
samples/RenderDemo/SideBar.xaml

@ -47,7 +47,7 @@
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
<DoubleTransition Property="Opacity" Duration="0:0:0.150"/>
</Transitions>
</Setter>
</Style>

2
samples/interop/WindowsInteropTest/Program.cs

@ -15,7 +15,7 @@ namespace WindowsInteropTest
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
AppBuilder.Configure<App>().UseWin32().UseDirect2D1().UseDataGrid().SetupWithoutStarting();
AppBuilder.Configure<App>().UseWin32().UseDirect2D1().SetupWithoutStarting();
System.Windows.Forms.Application.Run(new SelectorForm());
}
}

29
src/Avalonia.Animation/Animatable.cs

@ -7,7 +7,7 @@ using System.Linq;
using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
@ -36,13 +36,27 @@ namespace Avalonia.Animation
private Transitions _transitions;
private Dictionary<AvaloniaProperty, IDisposable> _previousTransitions;
/// <summary>
/// Gets or sets the property transitions for the control.
/// </summary>
public Transitions Transitions
{
get { return _transitions ?? (_transitions = new Transitions()); }
set { SetAndRaise(TransitionsProperty, ref _transitions, value); }
get
{
if (_transitions == null)
_transitions = new Transitions();
if (_previousTransitions == null)
_previousTransitions = new Dictionary<AvaloniaProperty, IDisposable>();
return _transitions;
}
set
{
SetAndRaise(TransitionsProperty, ref _transitions, value);
}
}
/// <summary>
@ -52,13 +66,18 @@ namespace Avalonia.Animation
/// <param name="e">The event args.</param>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Priority != BindingPriority.Animation && Transitions != null)
if (e.Priority != BindingPriority.Animation && Transitions != null && _previousTransitions != null)
{
var match = Transitions.FirstOrDefault(x => x.Property == e.Property);
if (match != null)
{
match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue);
if (_previousTransitions.TryGetValue(e.Property, out var dispose))
dispose.Dispose();
var instance = match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue);
_previousTransitions[e.Property] = instance;
}
}
}

5
src/Avalonia.Animation/Properties/AssemblyInfo.cs

@ -3,7 +3,10 @@
using Avalonia.Metadata;
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")]
[assembly: InternalsVisibleTo("Avalonia.LeakTests")]

11
src/Avalonia.Animation/TransitionInstance.cs

@ -30,13 +30,16 @@ namespace Avalonia.Animation
{
var interpVal = (double)t.Ticks / _duration.Ticks;
if (interpVal > 1d || interpVal < 0d)
// Clamp interpolation value.
if (interpVal >= 1d | interpVal < 0d)
{
PublishNext(1d);
PublishCompleted();
return;
}
PublishNext(interpVal);
else
{
PublishNext(interpVal);
}
}
protected override void Unsubscribed()

3
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -150,7 +150,8 @@ namespace Avalonia.Build.Tasks
classType = typeSystem.TargetAssembly.FindType(tn.Text);
if (classType == null)
throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective);
initialRoot.Type = new XamlIlAstClrTypeReference(classDirective, classType, false);
compiler.OverrideRootType(parsed,
new XamlIlAstClrTypeReference(classDirective, classType, false));
initialRoot.Children.Remove(classDirective);
}

20
src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs

@ -1,20 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.Threading;
namespace Avalonia
{
public static class AppBuilderExtensions
{
public static TAppBuilder UseDataGrid<TAppBuilder>(this TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
{
// Portable.Xaml doesn't correctly load referenced assemblies and so doesn't
// find `DataGrid` when loading XAML. Call this method from AppBuilder as a
// temporary workaround until we fix XAML.
return builder;
}
}
}

17
src/Avalonia.Controls/AppBuilderBase.cs

@ -148,13 +148,20 @@ namespace Avalonia.Controls
public delegate void AppMainDelegate(Application app, string[] args);
public void Start()
{
Setup();
BeforeStartCallback(Self);
Instance.Run();
}
public void Start(AppMainDelegate main, string[] args)
{
Setup();
BeforeStartCallback(Self);
main(Instance, args);
}
/// <summary>
/// Sets up the platform-specific services for the application, but does not run it.
/// </summary>
@ -220,13 +227,13 @@ namespace Avalonia.Controls
/// <summary>
/// Sets the shutdown mode of the application.
/// </summary>
/// <param name="exitMode">The shutdown mode.</param>
/// <param name="shutdownMode">The shutdown mode.</param>
/// <returns></returns>
public TAppBuilder SetExitMode(ExitMode exitMode)
public TAppBuilder SetShutdownMode(ShutdownMode shutdownMode)
{
Instance.ExitMode = exitMode;
Instance.ShutdownMode = shutdownMode;
return Self;
}
}
protected virtual bool CheckSetup => true;

205
src/Avalonia.Controls/Application.cs

@ -43,8 +43,8 @@ namespace Avalonia
private readonly Styler _styler = new Styler();
private Styles _styles;
private IResourceDictionary _resources;
private CancellationTokenSource _mainLoopCancellationTokenSource;
private int _exitCode;
/// <summary>
/// Initializes a new instance of the <see cref="Application"/> class.
@ -52,10 +52,14 @@ namespace Avalonia
public Application()
{
Windows = new WindowCollection(this);
OnExit += OnExiting;
}
/// <inheritdoc/>
public event EventHandler<StartupEventArgs> Startup;
/// <inheritdoc/>
public event EventHandler<ExitEventArgs> Exit;
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
@ -164,14 +168,14 @@ namespace Avalonia
IResourceNode IResourceNode.ResourceParent => null;
/// <summary>
/// Gets or sets the <see cref="ExitMode"/>. This property indicates whether the application exits explicitly or implicitly.
/// If <see cref="ExitMode"/> is set to OnExplicitExit the application is only closes if Exit is called.
/// Gets or sets the <see cref="ShutdownMode"/>. This property indicates whether the application is shutdown explicitly or implicitly.
/// If <see cref="ShutdownMode"/> is set to OnExplicitShutdown the application is only closes if Shutdown is called.
/// The default is OnLastWindowClose
/// </summary>
/// <value>
/// The shutdown mode.
/// </value>
public ExitMode ExitMode { get; set; }
public ShutdownMode ShutdownMode { get; set; }
/// <summary>
/// Gets or sets the main window of the application.
@ -190,132 +194,181 @@ namespace Avalonia
public WindowCollection Windows { get; }
/// <summary>
/// Gets or sets a value indicating whether this instance is existing.
/// Gets or sets a value indicating whether this instance is shutting down.
/// </summary>
/// <value>
/// <c>true</c> if this instance is existing; otherwise, <c>false</c>.
/// <c>true</c> if this instance is shutting down; otherwise, <c>false</c>.
/// </value>
internal bool IsExiting { get; set; }
internal bool IsShuttingDown { get; private set; }
/// <summary>
/// Initializes the application by loading XAML etc.
/// </summary>
public virtual void Initialize()
public virtual void Initialize() { }
/// <summary>
/// Runs the application's main loop.
/// </summary>
/// <remarks>
/// This will return when the <see cref="Avalonia.Controls.ShutdownMode"/> condition is met
/// or <see cref="Shutdown(int)"/> was called.
/// </remarks>
/// <returns>The application's exit code that is returned to the operating system on termination.</returns>
public int Run()
{
return Run(new CancellationTokenSource());
}
/// <summary>
/// Runs the application's main loop until the <see cref="ICloseable"/> is closed.
/// Runs the application's main loop.
/// </summary>
/// <param name="closable">The closable to track</param>
public void Run(ICloseable closable)
/// <remarks>
/// This will return when the <see cref="Avalonia.Controls.ShutdownMode"/> condition is met
/// or <see cref="Shutdown(int)"/> was called.
/// This also returns when <see cref="ICloseable"/> is closed.
/// </remarks>
/// <param name="closable">The closable to track.</param>
/// <returns>The application's exit code that is returned to the operating system on termination.</returns>
public int Run(ICloseable closable)
{
if (_mainLoopCancellationTokenSource != null)
closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel();
return Run(new CancellationTokenSource());
}
/// <summary>
/// Runs the application's main loop.
/// </summary>
/// <remarks>
/// This will return when the <see cref="Avalonia.Controls.ShutdownMode"/> condition is met
/// or <see cref="Shutdown(int)"/> was called.
/// </remarks>
/// <param name="mainWindow">The window that is used as <see cref="MainWindow"/>
/// when the <see cref="MainWindow"/> isn't already set.</param>
/// <returns>The application's exit code that is returned to the operating system on termination.</returns>
public int Run(Window mainWindow)
{
if (mainWindow == null)
{
throw new Exception("Run should only called once");
throw new ArgumentNullException(nameof(mainWindow));
}
closable.Closed += (s, e) => Exit();
_mainLoopCancellationTokenSource = new CancellationTokenSource();
if (MainWindow == null)
{
Dispatcher.UIThread.Post(() =>
{
if (!mainWindow.IsVisible)
{
mainWindow.Show();
}
Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token);
MainWindow = mainWindow;
});
}
// Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly
if (!IsExiting)
{
OnExit?.Invoke(this, EventArgs.Empty);
}
return Run(new CancellationTokenSource());
}
/// <summary>
/// Runs the application's main loop until some condition occurs that is specified by ExitMode.
/// Runs the application's main loop.
/// </summary>
/// <param name="mainWindow">The main window</param>
public void Run(Window mainWindow)
/// <remarks>
/// This will return when the <see cref="Avalonia.Controls.ShutdownMode"/> condition is met
/// or <see cref="Shutdown(int)"/> was called.
/// This also returns when the <see cref="CancellationToken"/> is canceled.
/// </remarks>
/// <returns>The application's exit code that is returned to the operating system on termination.</returns>
/// <param name="token">The token to track.</param>
public int Run(CancellationToken token)
{
if (_mainLoopCancellationTokenSource != null)
return Run(CancellationTokenSource.CreateLinkedTokenSource(token));
}
private int Run(CancellationTokenSource tokenSource)
{
if (IsShuttingDown)
{
throw new Exception("Run should only called once");
throw new InvalidOperationException("Application is shutting down.");
}
_mainLoopCancellationTokenSource = new CancellationTokenSource();
if (MainWindow == null)
if (_mainLoopCancellationTokenSource != null)
{
if (mainWindow == null)
{
throw new ArgumentNullException(nameof(mainWindow));
}
throw new InvalidOperationException("Application is already running.");
}
if (!mainWindow.IsVisible)
{
mainWindow.Show();
}
_mainLoopCancellationTokenSource = tokenSource;
MainWindow = mainWindow;
}
Dispatcher.UIThread.Post(() => OnStartup(new StartupEventArgs()), DispatcherPriority.Send);
Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token);
// Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly
if (!IsExiting)
if (!IsShuttingDown)
{
OnExit?.Invoke(this, EventArgs.Empty);
Shutdown(_exitCode);
}
return _exitCode;
}
/// <summary>
/// Runs the application's main loop until the <see cref="CancellationToken"/> is canceled.
/// Raises the <see cref="Startup"/> event.
/// </summary>
/// <param name="token">The token to track</param>
public void Run(CancellationToken token)
/// <param name="e">A <see cref="StartupEventArgs"/> that contains the event data.</param>
protected virtual void OnStartup(StartupEventArgs e)
{
Dispatcher.UIThread.MainLoop(token);
// Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly
if (!IsExiting)
{
OnExit?.Invoke(this, EventArgs.Empty);
}
Startup?.Invoke(this, e);
}
/// <summary>
/// Exits the application
/// Raises the <see cref="Exit"/> event.
/// </summary>
public void Exit()
/// <param name="e">A <see cref="ExitEventArgs"/> that contains the event data.</param>
protected virtual void OnExit(ExitEventArgs e)
{
IsExiting = true;
Exit?.Invoke(this, e);
}
/// <inheritdoc/>
public void Shutdown(int exitCode = 0)
{
if (IsShuttingDown)
{
throw new InvalidOperationException("Application is already shutting down.");
}
_exitCode = exitCode;
IsShuttingDown = true;
Windows.Clear();
OnExit?.Invoke(this, EventArgs.Empty);
try
{
var e = new ExitEventArgs { ApplicationExitCode = _exitCode };
OnExit(e);
_exitCode = e.ApplicationExitCode;
}
finally
{
_mainLoopCancellationTokenSource?.Cancel();
_mainLoopCancellationTokenSource = null;
IsShuttingDown = false;
_mainLoopCancellationTokenSource?.Cancel();
Environment.ExitCode = _exitCode;
}
}
/// <inheritdoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
bool IResourceProvider.TryGetResource(object key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
Styles.TryGetResource(key, out value);
}
/// <summary>
/// Sent when the application is exiting.
/// </summary>
public event EventHandler OnExit;
/// <summary>
/// Called when the application is exiting.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnExiting(object sender, EventArgs e)
{
}
/// <summary>
/// Register's the services needed by Avalonia.
/// </summary>

18
src/Avalonia.Controls/ExitEventArgs.cs

@ -0,0 +1,18 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Contains the arguments for the <see cref="IApplicationLifecycle.Exit"/> event.
/// </summary>
public class ExitEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the exit code that an application returns to the operating system when the application exits.
/// </summary>
public int ApplicationExitCode { get; set; }
}
}

29
src/Avalonia.Controls/Expander.cs

@ -13,18 +13,11 @@ namespace Avalonia.Controls
public class Expander : HeaderedContentControl
{
public static readonly DirectProperty<Expander, IPageTransition> ContentTransitionProperty =
AvaloniaProperty.RegisterDirect<Expander, IPageTransition>(
nameof(ContentTransition),
o => o.ContentTransition,
(o, v) => o.ContentTransition = v);
public static readonly StyledProperty<IPageTransition> ContentTransitionProperty =
AvaloniaProperty.Register<Expander, IPageTransition>(nameof(ContentTransition));
public static readonly DirectProperty<Expander, ExpandDirection> ExpandDirectionProperty =
AvaloniaProperty.RegisterDirect<Expander, ExpandDirection>(
nameof(ExpandDirection),
o => o.ExpandDirection,
(o, v) => o.ExpandDirection = v,
ExpandDirection.Down);
public static readonly StyledProperty<ExpandDirection> ExpandDirectionProperty =
AvaloniaProperty.Register<Expander, ExpandDirection>(nameof(ExpandDirection), ExpandDirection.Down);
public static readonly DirectProperty<Expander, bool> IsExpandedProperty =
AvaloniaProperty.RegisterDirect<Expander, bool>(
@ -33,6 +26,8 @@ namespace Avalonia.Controls
(o, v) => o.IsExpanded = v,
defaultBindingMode: Data.BindingMode.TwoWay);
private bool _isExpanded;
static Expander()
{
PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Down, ":down");
@ -47,14 +42,14 @@ namespace Avalonia.Controls
public IPageTransition ContentTransition
{
get { return _contentTransition; }
set { SetAndRaise(ContentTransitionProperty, ref _contentTransition, value); }
get => GetValue(ContentTransitionProperty);
set => SetValue(ContentTransitionProperty, value);
}
public ExpandDirection ExpandDirection
{
get { return _expandDirection; }
set { SetAndRaise(ExpandDirectionProperty, ref _expandDirection, value); }
get => GetValue(ExpandDirectionProperty);
set => SetValue(ExpandDirectionProperty, value);
}
public bool IsExpanded
@ -79,9 +74,5 @@ namespace Avalonia.Controls
}
}
}
private IPageTransition _contentTransition;
private ExpandDirection _expandDirection;
private bool _isExpanded;
}
}

12
src/Avalonia.Controls/IApplicationLifecycle.cs

@ -7,14 +7,20 @@ namespace Avalonia.Controls
/// </summary>
public interface IApplicationLifecycle
{
/// <summary>
/// Sent when the application is starting up.
/// </summary>
event EventHandler<StartupEventArgs> Startup;
/// <summary>
/// Sent when the application is exiting.
/// </summary>
event EventHandler OnExit;
event EventHandler<ExitEventArgs> Exit;
/// <summary>
/// Exits the application.
/// Shuts down the application and sets the exit code that is returned to the operating system when the application exits.
/// </summary>
void Exit();
/// <param name="exitCode">An integer exit code for an application. The default exit code is 0.</param>
void Shutdown(int exitCode = 0);
}
}

18
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -644,20 +644,20 @@ namespace Avalonia.Controls.Primitives
/// <param name="desired">The desired items.</param>
internal static void SynchronizeItems(IList items, IEnumerable<object> desired)
{
int index = 0;
var index = 0;
foreach (var i in desired)
foreach (object item in desired)
{
if (index < items.Count)
int itemIndex = items.IndexOf(item);
if (itemIndex == -1)
{
if (items[index] != i)
{
items[index] = i;
}
items.Insert(index, item);
}
else
else if(itemIndex != index)
{
items.Add(i);
items.RemoveAt(itemIndex);
items.Insert(index, item);
}
++index;

2
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -357,7 +357,7 @@ namespace Avalonia.Controls.Primitives
if (control.TemplatedParent == this)
{
foreach (IControl child in control.GetVisualChildren())
foreach (IControl child in control.GetLogicalChildren())
{
RegisterNames(child, nameScope);
}

16
src/Avalonia.Controls/ExitMode.cs → src/Avalonia.Controls/ShutdownMode.cs

@ -1,26 +1,26 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia
namespace Avalonia.Controls
{
/// <summary>
/// Enum for ExitMode
/// Describes the possible values for <see cref="Application.ShutdownMode"/>.
/// </summary>
public enum ExitMode
public enum ShutdownMode
{
/// <summary>
/// Indicates an implicit call to Application.Exit when the last window closes.
/// Indicates an implicit call to Application.Shutdown when the last window closes.
/// </summary>
OnLastWindowClose,
/// <summary>
/// Indicates an implicit call to Application.Exit when the main window closes.
/// Indicates an implicit call to Application.Shutdown when the main window closes.
/// </summary>
OnMainWindowClose,
/// <summary>
/// Indicates that the application only exits on an explicit call to Application.Exit.
/// Indicates that the application only exits on an explicit call to Application.Shutdown.
/// </summary>
OnExplicitExit
OnExplicitShutdown
}
}
}

36
src/Avalonia.Controls/StartupEventArgs.cs

@ -0,0 +1,36 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Controls
{
/// <summary>
/// Contains the arguments for the <see cref="IApplicationLifecycle.Startup"/> event.
/// </summary>
public class StartupEventArgs : EventArgs
{
private string[] _args;
/// <summary>
/// Gets the command line arguments that were passed to the application.
/// </summary>
public IReadOnlyList<string> Args => _args ?? (_args = GetArgs());
private static string[] GetArgs()
{
try
{
var args = Environment.GetCommandLineArgs();
return args.Length > 1 ? args.Skip(1).ToArray() : new string[0];
}
catch (NotSupportedException)
{
return new string[0];
}
}
}
}

4
src/Avalonia.Controls/TopLevel.cs

@ -127,7 +127,7 @@ namespace Avalonia.Controls
if (_applicationLifecycle != null)
{
_applicationLifecycle.OnExit += OnApplicationExiting;
_applicationLifecycle.Exit += OnApplicationExiting;
}
if (((IStyleHost)this).StylingParent is IResourceProvider applicationResources)
@ -281,7 +281,7 @@ namespace Avalonia.Controls
Closed?.Invoke(this, EventArgs.Empty);
Renderer?.Dispose();
Renderer = null;
_applicationLifecycle.OnExit -= OnApplicationExiting;
_applicationLifecycle.Exit -= OnApplicationExiting;
}
/// <summary>

18
src/Avalonia.Controls/Window.cs

@ -250,7 +250,7 @@ namespace Avalonia.Controls
/// <summary>
/// Fired before a window is closed.
/// </summary>
public event EventHandler<CancelEventArgs> Closing;
public event EventHandler<CancelEventArgs> Closing;
private static void AddWindow(Window window)
{
@ -330,8 +330,7 @@ namespace Avalonia.Controls
protected virtual bool HandleClosing()
{
var args = new CancelEventArgs();
Closing?.Invoke(this, args);
OnClosing(args);
return args.Cancel;
}
@ -440,7 +439,7 @@ namespace Avalonia.Controls
/// </returns>
public Task<TResult> ShowDialog<TResult>(IWindowImpl owner)
{
if(owner == null)
if (owner == null)
throw new ArgumentNullException(nameof(owner));
if (IsVisible)
@ -576,6 +575,17 @@ namespace Avalonia.Controls
base.HandleResized(clientSize);
}
/// <summary>
/// Raises the <see cref="Closing"/> event.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// A type that derives from <see cref="Window"/> may override <see cref="OnClosing"/>. The
/// overridden method must call <see cref="OnClosing"/> on the base class if the
/// <see cref="Closing"/> event needs to be raised.
/// </remarks>
protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e);
}
}

12
src/Avalonia.Controls/WindowCollection.cs

@ -107,24 +107,24 @@ namespace Avalonia
return;
}
if (_application.IsExiting)
if (_application.IsShuttingDown)
{
return;
}
switch (_application.ExitMode)
switch (_application.ShutdownMode)
{
case ExitMode.OnLastWindowClose:
case ShutdownMode.OnLastWindowClose:
if (Count == 0)
{
_application.Exit();
_application.Shutdown();
}
break;
case ExitMode.OnMainWindowClose:
case ShutdownMode.OnMainWindowClose:
if (window == _application.MainWindow)
{
_application.Exit();
_application.Shutdown();
}
break;

27
src/Avalonia.Input/MouseDevice.cs

@ -108,11 +108,11 @@ namespace Avalonia.Input
{
if (Captured == null)
{
SetPointerOver(this, root, clientPoint);
SetPointerOver(this, root, clientPoint, InputModifiers.None);
}
else
{
SetPointerOver(this, root, Captured);
SetPointerOver(this, root, Captured, InputModifiers.None);
}
}
}
@ -128,7 +128,7 @@ namespace Avalonia.Input
switch (e.Type)
{
case RawMouseEventType.LeaveWindow:
LeaveWindow(mouse, e.Root);
LeaveWindow(mouse, e.Root, e.InputModifiers);
break;
case RawMouseEventType.LeftButtonDown:
case RawMouseEventType.RightButtonDown:
@ -157,12 +157,12 @@ namespace Avalonia.Input
}
}
private void LeaveWindow(IMouseDevice device, IInputRoot root)
private void LeaveWindow(IMouseDevice device, IInputRoot root, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
ClearPointerOver(this, root);
ClearPointerOver(this, root, inputModifiers);
}
private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers)
@ -218,11 +218,11 @@ namespace Avalonia.Input
if (Captured == null)
{
source = SetPointerOver(this, root, p);
source = SetPointerOver(this, root, p, inputModifiers);
}
else
{
SetPointerOver(this, root, Captured);
SetPointerOver(this, root, Captured, inputModifiers);
source = Captured;
}
@ -306,7 +306,7 @@ namespace Avalonia.Input
return Captured ?? root.InputHitTest(p);
}
private void ClearPointerOver(IPointerDevice device, IInputRoot root)
private void ClearPointerOver(IPointerDevice device, IInputRoot root, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -316,6 +316,7 @@ namespace Avalonia.Input
{
RoutedEvent = InputElement.PointerLeaveEvent,
Device = device,
InputModifiers = inputModifiers
};
if (element!=null && !element.IsAttachedToVisualTree)
@ -353,7 +354,7 @@ namespace Avalonia.Input
}
}
private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p)
private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -364,18 +365,18 @@ namespace Avalonia.Input
{
if (element != null)
{
SetPointerOver(device, root, element);
SetPointerOver(device, root, element, inputModifiers);
}
else
{
ClearPointerOver(device, root);
ClearPointerOver(device, root, inputModifiers);
}
}
return element;
}
private void SetPointerOver(IPointerDevice device, IInputRoot root, IInputElement element)
private void SetPointerOver(IPointerDevice device, IInputRoot root, IInputElement element, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -383,7 +384,7 @@ namespace Avalonia.Input
IInputElement branch = null;
var e = new PointerEventArgs { Device = device, };
var e = new PointerEventArgs { Device = device, InputModifiers = inputModifiers };
var el = element;
while (el != null)

2
src/Avalonia.Styling/Controls/IResourceProvider.cs

@ -28,6 +28,6 @@ namespace Avalonia.Controls
/// <returns>
/// True if the resource if found, otherwise false.
/// </returns>
bool TryGetResource(string key, out object value);
bool TryGetResource(object key, out object value);
}
}

2
src/Avalonia.Styling/Controls/ResourceDictionary.cs

@ -69,7 +69,7 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object value)
public bool TryGetResource(object key, out object value)
{
if (TryGetValue(key, out value))
{

10
src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs

@ -11,7 +11,7 @@ namespace Avalonia.Controls
/// <param name="control">The control.</param>
/// <param name="key">The resource key.</param>
/// <returns>The resource, or <see cref="AvaloniaProperty.UnsetValue"/> if not found.</returns>
public static object FindResource(this IResourceNode control, string key)
public static object FindResource(this IResourceNode control, object key)
{
if (control.TryFindResource(key, out var value))
{
@ -28,7 +28,7 @@ namespace Avalonia.Controls
/// <param name="key">The resource key.</param>
/// <param name="value">On return, contains the resource if found, otherwise null.</param>
/// <returns>True if the resource was found; otherwise false.</returns>
public static bool TryFindResource(this IResourceNode control, string key, out object value)
public static bool TryFindResource(this IResourceNode control, object key, out object value)
{
Contract.Requires<ArgumentNullException>(control != null);
Contract.Requires<ArgumentNullException>(key != null);
@ -52,7 +52,7 @@ namespace Avalonia.Controls
return false;
}
public static IObservable<object> GetResourceObservable(this IResourceNode target, string key)
public static IObservable<object> GetResourceObservable(this IResourceNode target, object key)
{
return new ResourceObservable(target, key);
}
@ -60,9 +60,9 @@ namespace Avalonia.Controls
private class ResourceObservable : LightweightObservableBase<object>
{
private readonly IResourceNode _target;
private readonly string _key;
private readonly object _key;
public ResourceObservable(IResourceNode target, string key)
public ResourceObservable(IResourceNode target, object key)
{
_target = target;
_key = key;

19
src/Avalonia.Styling/StyledElement.cs

@ -415,7 +415,7 @@ namespace Avalonia
}
/// <inheritdoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
bool IResourceProvider.TryGetResource(object key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
@ -677,23 +677,6 @@ namespace Avalonia
if (Name != null)
{
_nameScope?.Register(Name, this);
var visualParent = Parent as StyledElement;
if (this is INameScope && visualParent != null)
{
// If we have e.g. a named UserControl in a window then we want that control
// to be findable by name from the Window, so register with both name scopes.
// This differs from WPF's behavior in that XAML manually registers controls
// with name scopes based on the XAML file in which the name attribute appears,
// but we're trying to avoid XAML magic in Avalonia in order to made code-
// created UIs easy. This will cause problems if a UserControl declares a name
// in its XAML and that control is included multiple times in a parent control
// (as the name will be duplicated), however at the moment I'm fine with saying
// "don't do that".
var parentNameScope = NameScope.FindNameScope(visualParent);
parentNameScope?.Register(Name, this);
}
}
}

2
src/Avalonia.Styling/Styling/Style.cs

@ -171,7 +171,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object result)
public bool TryGetResource(object key, out object result)
{
result = null;
return _resources?.TryGetResource(key, out result) ?? false;

2
src/Avalonia.Styling/Styling/Styles.cs

@ -178,7 +178,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object value)
public bool TryGetResource(object key, out object value)
{
if (_resources != null && _resources.TryGetValue(key, out value))
{

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -26,7 +26,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
ResourceKey = resourceKey;
}
public string ResourceKey { get; set; }
public object ResourceKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider) => ProvideTypedValue(serviceProvider);

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs

@ -47,7 +47,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
bool IResourceProvider.HasResources => Loaded.HasResources;
/// <inhertidoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
bool IResourceProvider.TryGetResource(object key, out object value)
{
return Loaded.TryGetResource(key, out value);
}

2
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -86,7 +86,7 @@ namespace Avalonia.Markup.Xaml.Styling
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value);
public bool TryGetResource(object key, out object value) => Loaded.TryGetResource(key, out value);
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)

24
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -112,13 +112,31 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
rootType = new XamlIlAstClrTypeReference(rootObject, overrideRootType, false);
}
rootObject.Type = rootType;
OverrideRootType(parsed, rootType);
Transform(parsed);
Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource);
}
public void OverrideRootType(XamlIlDocument doc, IXamlIlAstTypeReference newType)
{
var root = (XamlIlAstObjectNode)doc.Root;
var oldType = root.Type;
if (oldType.Equals(newType))
return;
root.Type = newType;
foreach (var child in root.Children.OfType<XamlIlAstXamlPropertyValueNode>())
{
if (child.Property is XamlIlAstNamePropertyReference prop)
{
if (prop.DeclaringType.Equals(oldType))
prop.DeclaringType = newType;
if (prop.TargetType.Equals(oldType))
prop.TargetType = newType;
}
}
}
}
}

18
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Data;
using Portable.Xaml;
using Portable.Xaml.Markup;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Global
@ -17,19 +18,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents
.OfType<IResourceNode>().ToList();
return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes));
var rootObject = provider.GetService<IRootObjectProvider>().RootObject;
return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject));
}
class DeferredParentServiceProvider : IAvaloniaXamlIlParentStackProvider, IServiceProvider
class DeferredParentServiceProvider :
IAvaloniaXamlIlParentStackProvider,
IServiceProvider,
IRootObjectProvider
{
private readonly IServiceProvider _parentProvider;
private readonly List<IResourceNode> _parentResourceNodes;
public DeferredParentServiceProvider(IServiceProvider parentProvider, List<IResourceNode> parentResourceNodes)
public DeferredParentServiceProvider(IServiceProvider parentProvider, List<IResourceNode> parentResourceNodes,
object rootObject)
{
_parentProvider = parentProvider;
_parentResourceNodes = parentResourceNodes;
RootObject = rootObject;
}
public IEnumerable<object> Parents => GetParents();
@ -46,8 +52,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this;
if (serviceType == typeof(IRootObjectProvider))
return this;
return _parentProvider?.GetService(serviceType);
}
public object RootObject { get; }
}

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

@ -1 +1 @@
Subproject commit 3b3c1f93a566080d417b9782f9cc4ea67cd62344
Subproject commit 1e3ffc315401f0b2eb96a0e79b25c2fc19a80d78

73
tests/Avalonia.Animation.UnitTests/TransitionsTests.cs

@ -0,0 +1,73 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.Data;
using Xunit;
using Avalonia.Animation.Easings;
namespace Avalonia.Animation.UnitTests
{
public class TransitionsTests
{
[Fact]
public void Check_Transitions_Interpolation_Negative_Bounds_Clamp()
{
var clock = new MockGlobalClock();
using (UnitTestApplication.Start(new TestServices(globalClock: clock)))
{
var border = new Border
{
Transitions =
{
new DoubleTransition
{
Duration = TimeSpan.FromSeconds(1),
Property = Border.OpacityProperty,
}
}
};
border.Opacity = 0;
clock.Pulse(TimeSpan.FromSeconds(0));
clock.Pulse(TimeSpan.FromSeconds(-0.5));
Assert.Equal(0, border.Opacity);
}
}
[Fact]
public void Check_Transitions_Interpolation_Positive_Bounds_Clamp()
{
var clock = new MockGlobalClock();
using (UnitTestApplication.Start(new TestServices(globalClock: clock)))
{
var border = new Border
{
Transitions =
{
new DoubleTransition
{
Duration = TimeSpan.FromSeconds(1),
Property = Border.OpacityProperty,
}
}
};
border.Opacity = 0;
clock.Pulse(TimeSpan.FromSeconds(0));
clock.Pulse(TimeSpan.FromMilliseconds(1001));
Assert.Equal(0, border.Opacity);
}
}
}
}

48
tests/Avalonia.Controls.UnitTests/ApplicationTests.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Xunit;
@ -15,7 +16,11 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
Application.Current.ExitMode = ExitMode.OnMainWindowClose;
Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
var hasExit = false;
Application.Current.Exit += (s, e) => hasExit = true;
var mainWindow = new Window();
@ -29,7 +34,7 @@ namespace Avalonia.Controls.UnitTests
mainWindow.Close();
Assert.True(Application.Current.IsExiting);
Assert.True(hasExit);
}
}
@ -38,7 +43,11 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
Application.Current.ExitMode = ExitMode.OnLastWindowClose;
Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose;
var hasExit = false;
Application.Current.Exit += (s, e) => hasExit = true;
var windowA = new Window();
@ -50,11 +59,11 @@ namespace Avalonia.Controls.UnitTests
windowA.Close();
Assert.False(Application.Current.IsExiting);
Assert.False(hasExit);
windowB.Close();
Assert.True(Application.Current.IsExiting);
Assert.True(hasExit);
}
}
@ -63,7 +72,11 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
Application.Current.ExitMode = ExitMode.OnExplicitExit;
Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
var hasExit = false;
Application.Current.Exit += (s, e) => hasExit = true;
var windowA = new Window();
@ -75,15 +88,15 @@ namespace Avalonia.Controls.UnitTests
windowA.Close();
Assert.False(Application.Current.IsExiting);
Assert.False(hasExit);
windowB.Close();
Assert.False(Application.Current.IsExiting);
Assert.False(hasExit);
Application.Current.Exit();
Application.Current.Shutdown();
Assert.True(Application.Current.IsExiting);
Assert.True(hasExit);
}
}
@ -99,7 +112,7 @@ namespace Avalonia.Controls.UnitTests
window.Show();
}
Application.Current.Exit();
Application.Current.Shutdown();
Assert.Empty(Application.Current.Windows);
}
@ -129,5 +142,18 @@ namespace Avalonia.Controls.UnitTests
Assert.True(raised);
}
}
[Fact]
public void Should_Set_ExitCode_After_Shutdown()
{
using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
{
Application.Current.Shutdown(1337);
var exitCode = Application.Current.Run();
Assert.Equal(1337, exitCode);
}
}
}
}

2
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -215,7 +215,7 @@ namespace Avalonia.Controls.UnitTests
var impl = new Mock<ITopLevelImpl>();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
UnitTestApplication.Current.Exit();
UnitTestApplication.Current.Shutdown();
Assert.True(target.IsClosed);
}
}

133
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -10,6 +10,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.LogicalTree;
using Avalonia.UnitTests;
using Xunit;
@ -425,7 +426,6 @@ namespace Avalonia.Controls.UnitTests
Assert.True(called);
}
[Fact]
public void LogicalChildren_Should_Be_Set()
{
@ -623,6 +623,135 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Pressing_SelectAll_Gesture_Should_Select_All_Nodes()
{
using (UnitTestApplication.Start())
{
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
var rootNode = tree[0];
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var selectAllGesture = keymap.SelectAll.First();
var keyEvent = new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = selectAllGesture.Key,
Modifiers = selectAllGesture.Modifiers
};
target.RaiseEvent(keyEvent);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
}
}
[Fact]
public void Pressing_SelectAll_Gesture_With_Downward_Range_Selected_Should_Select_All_Nodes()
{
using (UnitTestApplication.Start())
{
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
var rootNode = tree[0];
var from = rootNode.Children[0];
var to = rootNode.Children.Last();
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var selectAllGesture = keymap.SelectAll.First();
var keyEvent = new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = selectAllGesture.Key,
Modifiers = selectAllGesture.Modifiers
};
target.RaiseEvent(keyEvent);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
}
}
[Fact]
public void Pressing_SelectAll_Gesture_With_Upward_Range_Selected_Should_Select_All_Nodes()
{
using (UnitTestApplication.Start())
{
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
var rootNode = tree[0];
var from = rootNode.Children.Last();
var to = rootNode.Children[0];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var selectAllGesture = keymap.SelectAll.First();
var keyEvent = new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = selectAllGesture.Key,
Modifiers = selectAllGesture.Modifiers
};
target.RaiseEvent(keyEvent);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
}
}
private void ApplyTemplates(TreeView tree)
{
tree.ApplyTemplate();
@ -765,7 +894,7 @@ namespace Avalonia.Controls.UnitTests
}
}
private class Node : NotifyingBase
private class Node : NotifyingBase
{
private IAvaloniaList<Node> _children;

60
tests/Avalonia.LeakTests/TransitionTests.cs

@ -0,0 +1,60 @@
using System;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.UnitTests;
using JetBrains.dotMemoryUnit;
using Xunit;
using Xunit.Abstractions;
namespace Avalonia.LeakTests
{
[DotMemoryUnit(FailIfRunWithoutSupport = false)]
public class TransitionTests
{
public TransitionTests(ITestOutputHelper atr)
{
DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine);
}
[Fact(Skip = "TODO: Fix this leak")]
public void Transition_On_StyledProperty_Is_Freed()
{
var clock = new MockGlobalClock();
using (UnitTestApplication.Start(new TestServices(globalClock: clock)))
{
Func<Border> run = () =>
{
var border = new Border
{
Transitions =
{
new DoubleTransition
{
Duration = TimeSpan.FromSeconds(1),
Property = Border.OpacityProperty,
}
}
};
border.Opacity = 0;
clock.Pulse(TimeSpan.FromSeconds(0));
clock.Pulse(TimeSpan.FromSeconds(0.5));
Assert.Equal(0.5, border.Opacity);
clock.Pulse(TimeSpan.FromSeconds(1));
Assert.Equal(0, border.Opacity);
return border;
};
var result = run();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TransitionInstance>()).ObjectsCount));
}
}
}
}

3
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -32,7 +32,8 @@
<EmbeddedResource Include="Xaml\Style2.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<AvaloniaResource Include="Xaml\XamlIlClassWithPrecompiledXaml.xaml"/>
<AvaloniaResource Include="Xaml\XamlIlClassWithPrecompiledXaml.xaml" />
<AvaloniaResource Include="Xaml\XamlIlClassWithCustomProperty.xaml" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />
</Project>

31
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs

@ -38,6 +38,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
[Fact]
public void DataTemplate_Can_Contain_Named_UserControl()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=mscorlib'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ItemsControl Name='itemsControl' Items='{Binding}'>
<ItemsControl.ItemTemplate>
<DataTemplate>
<UserControl Name='foo'/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var itemsControl = window.FindControl<ItemsControl>("itemsControl");
window.DataContext = new[] { "item1", "item2" };
window.ApplyTemplate();
itemsControl.ApplyTemplate();
itemsControl.Presenter.ApplyTemplate();
Assert.Equal(2, itemsControl.Presenter.Panel.Children.Count);
}
}
[Fact]
public void Can_Set_DataContext_In_DataTemplate()
{

6
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithCustomProperty.xaml

@ -0,0 +1,6 @@
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class='Avalonia.Markup.Xaml.UnitTests.XamlIlClassWithCustomProperty'
Test="123">
</UserControl>

79
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -5,8 +5,12 @@ using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Data.Converters;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using JetBrains.Annotations;
@ -117,6 +121,81 @@ namespace Avalonia.Markup.Xaml.UnitTests
Assert.Equal(Brushes.Red.Color, ((ISolidColorBrush)canvas.Background).Color);
}
}
[Fact]
public void Event_Handlers_Should_Work_For_Templates()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var w =new XamlIlBugTestsEventHandlerCodeBehind();
w.ApplyTemplate();
w.Show();
Dispatcher.UIThread.RunJobs();
var itemsPresenter = ((ItemsControl)w.Content).GetVisualChildren().FirstOrDefault();
var item = itemsPresenter
.GetVisualChildren().First()
.GetVisualChildren().First()
.GetVisualChildren().First();
((Control)item).RaiseEvent(new PointerPressedEventArgs {ClickCount = 20});
Assert.Equal(20, w.Args.ClickCount);
}
}
[Fact]
public void Custom_Properties_Should_Work_With_XClass()
{
var precompiled = new XamlIlClassWithCustomProperty();
Assert.Equal("123", precompiled.Test);
var loaded = (XamlIlClassWithCustomProperty)AvaloniaXamlLoader.Parse(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class='Avalonia.Markup.Xaml.UnitTests.XamlIlClassWithCustomProperty'
Test='321'>
</UserControl>");
Assert.Equal("321", loaded.Test);
}
}
public class XamlIlBugTestsEventHandlerCodeBehind : Window
{
public PointerPressedEventArgs Args;
public void HandlePointerPressed(object sender, PointerPressedEventArgs args)
{
Args = args;
}
public XamlIlBugTestsEventHandlerCodeBehind()
{
new AvaloniaXamlLoader().Load(@"
<Window x:Class='Avalonia.Markup.Xaml.UnitTests.XamlIlBugTestsEventHandlerCodeBehind'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns='https://github.com/avaloniaui'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests;assembly=Avalonia.Markup.Xaml.UnitTests'
>
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button PointerPressed='HandlePointerPressed' Content='{Binding .}' />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
", typeof(XamlIlBugTestsEventHandlerCodeBehind).Assembly, this);
((ItemsControl)Content).Items = new[] {"123"};
}
}
public class XamlIlClassWithCustomProperty : UserControl
{
public string Test { get; set; }
public XamlIlClassWithCustomProperty()
{
AvaloniaXamlLoader.Load(this);
}
}
public class XamlIlBugTestsBrushToColorConverter : IMultiValueConverter

7
tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

@ -273,13 +273,10 @@ namespace Avalonia.Styling.UnitTests
var root = new TestRoot();
var child = new Border();
((ISupportInitialize)child).BeginInit();
child.BeginInit();
root.Child = child;
child.Name = "foo";
Assert.Null(root.FindControl<Border>("foo"));
((ISupportInitialize)child).EndInit();
Assert.Same(root.FindControl<Border>("foo"), child);
child.EndInit();
}
}

23
tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs

@ -1,11 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
@ -70,23 +65,5 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(NameScope.GetNameScope((StyledElement)root.Presenter).Find("foo"));
}
[Fact]
public void Control_That_Is_NameScope_Should_Register_With_Parent_NameScope()
{
UserControl userControl;
var root = new TestTemplatedRoot
{
Content = userControl = new UserControl
{
Name = "foo",
}
};
root.ApplyTemplate();
Assert.Same(userControl, root.FindControl<UserControl>("foo"));
Assert.Same(userControl, userControl.FindControl<UserControl>("foo"));
}
}
}

10
tests/Avalonia.UnitTests/MockGlobalClock.cs

@ -0,0 +1,10 @@
using System;
using Avalonia.Animation;
namespace Avalonia.UnitTests
{
public class MockGlobalClock : ClockBase, IGlobalClock
{
public new void Pulse(TimeSpan systemTime) => base.Pulse(systemTime);
}
}

6
tests/Avalonia.UnitTests/TestServices.cs

@ -16,6 +16,7 @@ using System.Reactive.Concurrency;
using System.Collections.Generic;
using Avalonia.Controls;
using System.Reflection;
using Avalonia.Animation;
namespace Avalonia.UnitTests
{
@ -58,6 +59,7 @@ namespace Avalonia.UnitTests
public TestServices(
IAssetLoader assetLoader = null,
IFocusManager focusManager = null,
IGlobalClock globalClock = null,
IInputManager inputManager = null,
Func<IKeyboardDevice> keyboardDevice = null,
IKeyboardNavigationHandler keyboardNavigation = null,
@ -75,6 +77,7 @@ namespace Avalonia.UnitTests
{
AssetLoader = assetLoader;
FocusManager = focusManager;
GlobalClock = globalClock;
InputManager = inputManager;
KeyboardDevice = keyboardDevice;
KeyboardNavigation = keyboardNavigation;
@ -93,6 +96,7 @@ namespace Avalonia.UnitTests
public IAssetLoader AssetLoader { get; }
public IInputManager InputManager { get; }
public IFocusManager FocusManager { get; }
public IGlobalClock GlobalClock { get; }
public Func<IKeyboardDevice> KeyboardDevice { get; }
public IKeyboardNavigationHandler KeyboardNavigation { get; }
public Func<IMouseDevice> MouseDevice { get; }
@ -109,6 +113,7 @@ namespace Avalonia.UnitTests
public TestServices With(
IAssetLoader assetLoader = null,
IFocusManager focusManager = null,
IGlobalClock globalClock = null,
IInputManager inputManager = null,
Func<IKeyboardDevice> keyboardDevice = null,
IKeyboardNavigationHandler keyboardNavigation = null,
@ -127,6 +132,7 @@ namespace Avalonia.UnitTests
return new TestServices(
assetLoader: assetLoader ?? AssetLoader,
focusManager: focusManager ?? FocusManager,
globalClock: globalClock ?? GlobalClock,
inputManager: inputManager ?? InputManager,
keyboardDevice: keyboardDevice ?? KeyboardDevice,
keyboardNavigation: keyboardNavigation ?? KeyboardNavigation,

2
tests/Avalonia.UnitTests/UnitTestApplication.cs

@ -12,6 +12,7 @@ using Avalonia.Threading;
using System.Reactive.Disposables;
using System.Reactive.Concurrency;
using Avalonia.Input.Platform;
using Avalonia.Animation;
namespace Avalonia.UnitTests
{
@ -52,6 +53,7 @@ namespace Avalonia.UnitTests
AvaloniaLocator.CurrentMutable
.Bind<IAssetLoader>().ToConstant(Services.AssetLoader)
.Bind<IFocusManager>().ToConstant(Services.FocusManager)
.Bind<IGlobalClock>().ToConstant(Services.GlobalClock)
.BindToSelf<IGlobalStyles>(this)
.Bind<IInputManager>().ToConstant(Services.InputManager)
.Bind<IKeyboardDevice>().ToConstant(Services.KeyboardDevice?.Invoke())

Loading…
Cancel
Save