diff --git a/.gitignore b/.gitignore index 2b2c9c3d0d..971c945246 100644 --- a/.gitignore +++ b/.gitignore @@ -196,3 +196,5 @@ ModuleCache.noindex/ Build/Intermediates.noindex/ info.plist build-intermediate +obj-Direct2D1/ +obj-Skia/ diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index b7aa34f5ba..2a8d288614 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -26,8 +26,7 @@ namespace ControlCatalog => AppBuilder.Configure() .LogToDebug() .UsePlatformDetect() - .UseReactiveUI() - .UseDataGrid(); + .UseReactiveUI(); private static void ConfigureAssetAssembly(AppBuilder builder) { diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index c8f3fb9921..93f5611ec4 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -47,8 +47,7 @@ namespace ControlCatalog.NetCore => AppBuilder.Configure() .UsePlatformDetect() .UseSkia() - .UseReactiveUI() - .UseDataGrid(); + .UseReactiveUI(); static void ConsoleSilencer() { diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 3bae7edb00..3047b1e519 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -56,7 +56,7 @@ - + diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml index 3af90f1844..e37b9bb5fc 100644 --- a/samples/RenderDemo/SideBar.xaml +++ b/samples/RenderDemo/SideBar.xaml @@ -47,7 +47,7 @@ - + diff --git a/samples/interop/WindowsInteropTest/Program.cs b/samples/interop/WindowsInteropTest/Program.cs index 9f005b942d..fac06d74b0 100644 --- a/samples/interop/WindowsInteropTest/Program.cs +++ b/samples/interop/WindowsInteropTest/Program.cs @@ -15,7 +15,7 @@ namespace WindowsInteropTest { System.Windows.Forms.Application.EnableVisualStyles(); System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); - AppBuilder.Configure().UseWin32().UseDirect2D1().UseDataGrid().SetupWithoutStarting(); + AppBuilder.Configure().UseWin32().UseDirect2D1().SetupWithoutStarting(); System.Windows.Forms.Application.Run(new SelectorForm()); } } diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 4b0f76c5d5..3a3d00b94a 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/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 _previousTransitions; + /// /// Gets or sets the property transitions for the control. /// 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(); + + return _transitions; + } + set + { + SetAndRaise(TransitionsProperty, ref _transitions, value); + } } /// @@ -52,13 +66,18 @@ namespace Avalonia.Animation /// The event args. 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; } } } diff --git a/src/Avalonia.Animation/Properties/AssemblyInfo.cs b/src/Avalonia.Animation/Properties/AssemblyInfo.cs index 985a8e5bfe..eb38a66a84 100644 --- a/src/Avalonia.Animation/Properties/AssemblyInfo.cs +++ b/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")] \ No newline at end of file +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")] + +[assembly: InternalsVisibleTo("Avalonia.LeakTests")] diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index eff2c4e9f3..10ea6bf523 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/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() diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index ec668f2a2b..df840464c1 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/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); } diff --git a/src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs b/src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs deleted file mode 100644 index bdb9bf182c..0000000000 --- a/src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs +++ /dev/null @@ -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(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, 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; - } - } -} diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index a26b7d7405..419064b051 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/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); } - + /// /// Sets up the platform-specific services for the application, but does not run it. /// @@ -220,13 +227,13 @@ namespace Avalonia.Controls /// /// Sets the shutdown mode of the application. /// - /// The shutdown mode. + /// The shutdown mode. /// - public TAppBuilder SetExitMode(ExitMode exitMode) + public TAppBuilder SetShutdownMode(ShutdownMode shutdownMode) { - Instance.ExitMode = exitMode; + Instance.ShutdownMode = shutdownMode; return Self; - } + } protected virtual bool CheckSetup => true; diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 1d4e4cbeaa..0e696e0199 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/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; /// /// Initializes a new instance of the class. @@ -52,10 +52,14 @@ namespace Avalonia public Application() { Windows = new WindowCollection(this); - - OnExit += OnExiting; } + /// + public event EventHandler Startup; + + /// + public event EventHandler Exit; + /// public event EventHandler ResourcesChanged; @@ -164,14 +168,14 @@ namespace Avalonia IResourceNode IResourceNode.ResourceParent => null; /// - /// Gets or sets the . This property indicates whether the application exits explicitly or implicitly. - /// If is set to OnExplicitExit the application is only closes if Exit is called. + /// Gets or sets the . This property indicates whether the application is shutdown explicitly or implicitly. + /// If is set to OnExplicitShutdown the application is only closes if Shutdown is called. /// The default is OnLastWindowClose /// /// /// The shutdown mode. /// - public ExitMode ExitMode { get; set; } + public ShutdownMode ShutdownMode { get; set; } /// /// Gets or sets the main window of the application. @@ -190,132 +194,181 @@ namespace Avalonia public WindowCollection Windows { get; } /// - /// Gets or sets a value indicating whether this instance is existing. + /// Gets or sets a value indicating whether this instance is shutting down. /// /// - /// true if this instance is existing; otherwise, false. + /// true if this instance is shutting down; otherwise, false. /// - internal bool IsExiting { get; set; } + internal bool IsShuttingDown { get; private set; } /// /// Initializes the application by loading XAML etc. /// - public virtual void Initialize() + public virtual void Initialize() { } + + /// + /// Runs the application's main loop. + /// + /// + /// This will return when the condition is met + /// or was called. + /// + /// The application's exit code that is returned to the operating system on termination. + public int Run() { + return Run(new CancellationTokenSource()); } /// - /// Runs the application's main loop until the is closed. + /// Runs the application's main loop. /// - /// The closable to track - public void Run(ICloseable closable) + /// + /// This will return when the condition is met + /// or was called. + /// This also returns when is closed. + /// + /// The closable to track. + /// The application's exit code that is returned to the operating system on termination. + public int Run(ICloseable closable) { - if (_mainLoopCancellationTokenSource != null) + closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); + + return Run(new CancellationTokenSource()); + } + + /// + /// Runs the application's main loop. + /// + /// + /// This will return when the condition is met + /// or was called. + /// + /// The window that is used as + /// when the isn't already set. + /// The application's exit code that is returned to the operating system on termination. + 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()); } - /// - /// Runs the application's main loop until some condition occurs that is specified by ExitMode. + /// Runs the application's main loop. /// - /// The main window - public void Run(Window mainWindow) + /// + /// This will return when the condition is met + /// or was called. + /// This also returns when the is canceled. + /// + /// The application's exit code that is returned to the operating system on termination. + /// The token to track. + 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; } /// - /// Runs the application's main loop until the is canceled. + /// Raises the event. /// - /// The token to track - public void Run(CancellationToken token) + /// A that contains the event data. + 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); } /// - /// Exits the application + /// Raises the event. /// - public void Exit() + /// A that contains the event data. + protected virtual void OnExit(ExitEventArgs e) { - IsExiting = true; + Exit?.Invoke(this, e); + } + + /// + 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; + } } /// - 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); } - /// - /// Sent when the application is exiting. - /// - public event EventHandler OnExit; - - /// - /// Called when the application is exiting. - /// - /// - /// - protected virtual void OnExiting(object sender, EventArgs e) - { - } - /// /// Register's the services needed by Avalonia. /// diff --git a/src/Avalonia.Controls/ExitEventArgs.cs b/src/Avalonia.Controls/ExitEventArgs.cs new file mode 100644 index 0000000000..c99f7fe047 --- /dev/null +++ b/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 +{ + /// + /// Contains the arguments for the event. + /// + public class ExitEventArgs : EventArgs + { + /// + /// Gets or sets the exit code that an application returns to the operating system when the application exits. + /// + public int ApplicationExitCode { get; set; } + } +} diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index f0ba2b5578..1fa9798784 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -13,18 +13,11 @@ namespace Avalonia.Controls public class Expander : HeaderedContentControl { - public static readonly DirectProperty ContentTransitionProperty = - AvaloniaProperty.RegisterDirect( - nameof(ContentTransition), - o => o.ContentTransition, - (o, v) => o.ContentTransition = v); + public static readonly StyledProperty ContentTransitionProperty = + AvaloniaProperty.Register(nameof(ContentTransition)); - public static readonly DirectProperty ExpandDirectionProperty = - AvaloniaProperty.RegisterDirect( - nameof(ExpandDirection), - o => o.ExpandDirection, - (o, v) => o.ExpandDirection = v, - ExpandDirection.Down); + public static readonly StyledProperty ExpandDirectionProperty = + AvaloniaProperty.Register(nameof(ExpandDirection), ExpandDirection.Down); public static readonly DirectProperty IsExpandedProperty = AvaloniaProperty.RegisterDirect( @@ -33,6 +26,8 @@ namespace Avalonia.Controls (o, v) => o.IsExpanded = v, defaultBindingMode: Data.BindingMode.TwoWay); + private bool _isExpanded; + static Expander() { PseudoClass(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; } } diff --git a/src/Avalonia.Controls/IApplicationLifecycle.cs b/src/Avalonia.Controls/IApplicationLifecycle.cs index 51f554c078..a3c6599b20 100644 --- a/src/Avalonia.Controls/IApplicationLifecycle.cs +++ b/src/Avalonia.Controls/IApplicationLifecycle.cs @@ -7,14 +7,20 @@ namespace Avalonia.Controls /// public interface IApplicationLifecycle { + /// + /// Sent when the application is starting up. + /// + event EventHandler Startup; + /// /// Sent when the application is exiting. /// - event EventHandler OnExit; + event EventHandler Exit; /// - /// Exits the application. + /// Shuts down the application and sets the exit code that is returned to the operating system when the application exits. /// - void Exit(); + /// An integer exit code for an application. The default exit code is 0. + void Shutdown(int exitCode = 0); } } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 280c3ad93a..dcde8117f6 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -644,20 +644,20 @@ namespace Avalonia.Controls.Primitives /// The desired items. internal static void SynchronizeItems(IList items, IEnumerable 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; diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index ba4c5027d0..32e220b789 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/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); } diff --git a/src/Avalonia.Controls/ExitMode.cs b/src/Avalonia.Controls/ShutdownMode.cs similarity index 54% rename from src/Avalonia.Controls/ExitMode.cs rename to src/Avalonia.Controls/ShutdownMode.cs index b73fe4a963..46e27ff4e1 100644 --- a/src/Avalonia.Controls/ExitMode.cs +++ b/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 { /// - /// Enum for ExitMode + /// Describes the possible values for . /// - public enum ExitMode + public enum ShutdownMode { /// - /// Indicates an implicit call to Application.Exit when the last window closes. + /// Indicates an implicit call to Application.Shutdown when the last window closes. /// OnLastWindowClose, /// - /// Indicates an implicit call to Application.Exit when the main window closes. + /// Indicates an implicit call to Application.Shutdown when the main window closes. /// OnMainWindowClose, /// - /// 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. /// - OnExplicitExit + OnExplicitShutdown } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/StartupEventArgs.cs b/src/Avalonia.Controls/StartupEventArgs.cs new file mode 100644 index 0000000000..0e12f5c01a --- /dev/null +++ b/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 +{ + /// + /// Contains the arguments for the event. + /// + public class StartupEventArgs : EventArgs + { + private string[] _args; + + /// + /// Gets the command line arguments that were passed to the application. + /// + public IReadOnlyList 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]; + } + } + } +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index b08d55ff18..21bd0e4e57 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/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; } /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index e40e114769..01614ba87b 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -250,7 +250,7 @@ namespace Avalonia.Controls /// /// Fired before a window is closed. /// - public event EventHandler Closing; + public event EventHandler 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 /// public Task ShowDialog(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); } + + /// + /// Raises the event. + /// + /// The event args. + /// + /// A type that derives from may override . The + /// overridden method must call on the base class if the + /// event needs to be raised. + /// + protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); } } diff --git a/src/Avalonia.Controls/WindowCollection.cs b/src/Avalonia.Controls/WindowCollection.cs index df79c3e3c8..328bb9f147 100644 --- a/src/Avalonia.Controls/WindowCollection.cs +++ b/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; diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index d3e62ece6f..c195209305 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/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(device != null); Contract.Requires(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(device != null); Contract.Requires(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(device != null); Contract.Requires(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(device != null); Contract.Requires(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) diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index eec783623c..cbaacee012 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -28,6 +28,6 @@ namespace Avalonia.Controls /// /// True if the resource if found, otherwise false. /// - bool TryGetResource(string key, out object value); + bool TryGetResource(object key, out object value); } } diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 74a861b36b..901e27b7b7 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -69,7 +69,7 @@ namespace Avalonia.Controls } /// - public bool TryGetResource(string key, out object value) + public bool TryGetResource(object key, out object value) { if (TryGetValue(key, out value)) { diff --git a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs index 52309b87a2..01112eaf2c 100644 --- a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs +++ b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs @@ -11,7 +11,7 @@ namespace Avalonia.Controls /// The control. /// The resource key. /// The resource, or if not found. - 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 /// The resource key. /// On return, contains the resource if found, otherwise null. /// True if the resource was found; otherwise false. - 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(control != null); Contract.Requires(key != null); @@ -52,7 +52,7 @@ namespace Avalonia.Controls return false; } - public static IObservable GetResourceObservable(this IResourceNode target, string key) + public static IObservable GetResourceObservable(this IResourceNode target, object key) { return new ResourceObservable(target, key); } @@ -60,9 +60,9 @@ namespace Avalonia.Controls private class ResourceObservable : LightweightObservableBase { 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; diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index d314a8d44e..f7e063dfb5 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -415,7 +415,7 @@ namespace Avalonia } /// - 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); - } } } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index d799df7ac9..3ce82b4160 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -171,7 +171,7 @@ namespace Avalonia.Styling } /// - 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; diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 288cf35d08..789bb6ffd3 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -178,7 +178,7 @@ namespace Avalonia.Styling } /// - public bool TryGetResource(string key, out object value) + public bool TryGetResource(object key, out object value) { if (_resources != null && _resources.TryGetValue(key, out value)) { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 5f0e84c63a..48e55dc251 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/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); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs index 827f58a909..323a341f6a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs @@ -47,7 +47,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions bool IResourceProvider.HasResources => Loaded.HasResources; /// - bool IResourceProvider.TryGetResource(string key, out object value) + bool IResourceProvider.TryGetResource(object key, out object value) { return Loaded.TryGetResource(key, out value); } diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 01ec9753bd..7acee50d80 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -86,7 +86,7 @@ namespace Avalonia.Markup.Xaml.Styling } /// - 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); /// void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 65149a65de..b84f50fa8d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/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()) + { + if (child.Property is XamlIlAstNamePropertyReference prop) + { + if (prop.DeclaringType.Equals(oldType)) + prop.DeclaringType = newType; + if (prop.TargetType.Equals(oldType)) + prop.TargetType = newType; + } + } + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index f91e221ac0..70b7fe6aec 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/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().Parents .OfType().ToList(); - - return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes)); + var rootObject = provider.GetService().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 _parentResourceNodes; - public DeferredParentServiceProvider(IServiceProvider parentProvider, List parentResourceNodes) + public DeferredParentServiceProvider(IServiceProvider parentProvider, List parentResourceNodes, + object rootObject) { _parentProvider = parentProvider; _parentResourceNodes = parentResourceNodes; + RootObject = rootObject; } public IEnumerable 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; } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index 3b3c1f93a5..1e3ffc3154 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit 3b3c1f93a566080d417b9782f9cc4ea67cd62344 +Subproject commit 1e3ffc315401f0b2eb96a0e79b25c2fc19a80d78 diff --git a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs new file mode 100644 index 0000000000..f1b4b0d071 --- /dev/null +++ b/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); + } + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index df14c808db..b7d752dfe3 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/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); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 014bf458ea..aa99d31cff 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -215,7 +215,7 @@ namespace Avalonia.Controls.UnitTests var impl = new Mock(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); - UnitTestApplication.Current.Exit(); + UnitTestApplication.Current.Shutdown(); Assert.True(target.IsClosed); } } diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 519872f9f2..15081b184c 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/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(); + 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(); + 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(); + 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 _children; diff --git a/tests/Avalonia.LeakTests/TransitionTests.cs b/tests/Avalonia.LeakTests/TransitionTests.cs new file mode 100644 index 0000000000..c7add1fe11 --- /dev/null +++ b/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 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()).ObjectsCount)); + } + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index c9eb72757f..3107f4cf22 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -32,7 +32,8 @@ Designer - + + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs index 34e77cb2fd..6b67303b07 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs +++ b/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 = @" + + + + + + + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var itemsControl = window.FindControl("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() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithCustomProperty.xaml b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithCustomProperty.xaml new file mode 100644 index 0000000000..330561a2c6 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithCustomProperty.xaml @@ -0,0 +1,6 @@ + + + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index a584027768..795aba153a 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/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(@" + + +"); + 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(@" + + + + +