Răsfoiți Sursa

Merge branch 'master' into transitioninstance

Jumar Macato 6 ani în urmă
părinte
comite
df486e1816
59 a modificat fișierele cu 1032 adăugiri și 338 ștergeri
  1. 76 0
      CODE_OF_CONDUCT.md
  2. 1 0
      build/Base.props
  3. 1 0
      build/BuildTargets.targets
  4. 2 2
      native/Avalonia.Native/src/OSX/window.mm
  5. 2 0
      packages/Avalonia/AvaloniaBuildTasks.targets
  6. 4 4
      readme.md
  7. 6 6
      src/Avalonia.Base/ValueStore.cs
  8. 3 1
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  9. 2 1
      src/Avalonia.Build.Tasks/Program.cs
  10. 2 2
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  11. 2 3
      src/Avalonia.Controls/AppBuilderBase.cs
  12. 4 5
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  13. 1 1
      src/Avalonia.Controls/AutoCompleteBox.cs
  14. 10 3
      src/Avalonia.Controls/ColumnDefinition.cs
  15. 51 3
      src/Avalonia.Controls/DefinitionBase.cs
  16. 1 0
      src/Avalonia.Controls/DesktopApplicationExtensions.cs
  17. 18 5
      src/Avalonia.Controls/Grid.cs
  18. 5 3
      src/Avalonia.Controls/ItemsControl.cs
  19. 2 1
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  20. 3 3
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  21. 2 0
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  22. 4 0
      src/Avalonia.Controls/Primitives/Thumb.cs
  23. 2 0
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  24. 10 3
      src/Avalonia.Controls/RowDefinition.cs
  25. 9 9
      src/Avalonia.Input/DragDropDevice.cs
  26. 48 54
      src/Avalonia.Input/KeyboardDevice.cs
  27. 2 4
      src/Avalonia.Input/Raw/RawDragEvent.cs
  28. 8 1
      src/Avalonia.Input/Raw/RawInputEventArgs.cs
  29. 2 1
      src/Avalonia.Input/Raw/RawKeyEventArgs.cs
  30. 1 7
      src/Avalonia.Input/Raw/RawPointerEventArgs.cs
  31. 8 3
      src/Avalonia.Input/Raw/RawTextInputEventArgs.cs
  32. 2 2
      src/Avalonia.Native/WindowImplBase.cs
  33. 6 1
      src/Avalonia.Themes.Default/Accents/BaseDark.xaml
  34. 12 4
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  35. 0 8
      src/Avalonia.Themes.Default/ContextMenu.xaml
  36. 7 22
      src/Avalonia.Themes.Default/MenuItem.xaml
  37. 1 5
      src/Avalonia.Themes.Default/RepeatButton.xaml
  38. 138 126
      src/Avalonia.Themes.Default/ScrollBar.xaml
  39. 1 0
      src/Avalonia.Themes.Default/ScrollViewer.xaml
  40. 18 1
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  41. 11 1
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  42. 2 2
      src/Avalonia.X11/X11Window.cs
  43. 3 3
      src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
  44. 1 1
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  45. 3 3
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  46. 9 0
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  47. 2 2
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  48. 23 3
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  49. 3 1
      src/Windows/Avalonia.Win32/WindowImpl.cs
  50. 12 0
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs
  51. 9 9
      tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
  52. 277 9
      tests/Avalonia.Controls.UnitTests/GridTests.cs
  53. 28 0
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  54. 1 0
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  55. 90 0
      tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
  56. 3 3
      tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs
  57. 6 6
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
  58. 52 0
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  59. 20 1
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs

+ 76 - 0
CODE_OF_CONDUCT.md

@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at [email protected]. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq

+ 1 - 0
build/Base.props

@@ -1,5 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
     <PackageReference Include="System.ValueTuple" Version="4.5.0" />
+    <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
   </ItemGroup>
 </Project>

+ 1 - 0
build/BuildTargets.targets

@@ -2,6 +2,7 @@
   <PropertyGroup>
     <AvaloniaBuildTasksLocation>$(MSBuildThisFileDirectory)\..\src\Avalonia.Build.Tasks\bin\$(Configuration)\netstandard2.0\Avalonia.Build.Tasks.dll</AvaloniaBuildTasksLocation>
     <AvaloniaUseExternalMSBuild>true</AvaloniaUseExternalMSBuild>
+    <AvaloniaXamlIlVerifyIl>true</AvaloniaXamlIlVerifyIl>
   </PropertyGroup>
   <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.props"/>
   <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.targets"/>

+ 2 - 2
native/Avalonia.Native/src/OSX/window.mm

@@ -855,8 +855,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     
     if(type == Wheel)
     {
-        delta.X = [event scrollingDeltaX] / 5;
-        delta.Y = [event scrollingDeltaY] / 5;
+        delta.X = [event scrollingDeltaX] / 50;
+        delta.Y = [event scrollingDeltaY] / 50;
         
         if(delta.X == 0 && delta.Y == 0)
         {

+ 2 - 0
packages/Avalonia/AvaloniaBuildTasks.targets

@@ -53,6 +53,7 @@
     <PropertyGroup>
       <AvaloniaXamlReferencesTemporaryFilePath Condition="'$(AvaloniaXamlReferencesTemporaryFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/references</AvaloniaXamlReferencesTemporaryFilePath>
       <AvaloniaXamlOriginalCopyFilePath Condition="'$(AvaloniaXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/original.dll</AvaloniaXamlOriginalCopyFilePath>
+      <AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
     </PropertyGroup>
     <WriteLinesToFile
       Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
@@ -65,6 +66,7 @@
       ReferencesFilePath="$(AvaloniaXamlReferencesTemporaryFilePath)"
       OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
       ProjectDirectory="$(MSBuildProjectDirectory)"
+      VerifyIl="$(AvaloniaXamlIlVerifyIl)"
     />
     <Exec
       Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"

+ 4 - 4
readme.md

@@ -8,9 +8,9 @@
 
 ## About
 
-**Avalonia** is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS.
+**Avalonia** is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), macOS and with experimental support for Android and iOS.
 
-**Avalonia** is ready for **General-Purpose Desktop App Development**. However there may be some bugs and breaking changes as we continue along into this project's development. To see the status for some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239).
+**Avalonia** is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development. To see the status of some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239).
 
 | Control catalog | Desktop platforms | Mobile platforms |
 |---|---|---|
@@ -20,11 +20,11 @@
 
 Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started. After installing it, open "New Project" dialog in Visual Studio, choose "Avalonia" in "Visual C#" section, select "Avalonia .NET Core Application" and press OK (<a href="http://avaloniaui.net/docs/quickstart/images/new-project-dialog.png">screenshot</a>). Now you can write code and markup that will work on multiple platforms!
 
-For those without Visual Studio, starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core).
+For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core).
 
 Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed))
 
-Use these commands in Package Manager console to install Avalonia manually:
+Use these commands in the Package Manager console to install Avalonia manually:
 ```
 Install-Package Avalonia
 Install-Package Avalonia.Desktop

+ 6 - 6
src/Avalonia.Base/ValueStore.cs

@@ -57,7 +57,8 @@ namespace Avalonia
                 {
                     if (priority == (int)BindingPriority.LocalValue)
                     {
-                        _propertyValues.SetValue(property, Validate(property, value));
+                        Validate(property, ref value);
+                        _propertyValues.SetValue(property, value);
                         Changed(property, priority, v, value);
                         return;
                     }
@@ -78,7 +79,8 @@ namespace Avalonia
 
                 if (priority == (int)BindingPriority.LocalValue)
                 {
-                    _propertyValues.AddValue(property, Validate(property, value));
+                    Validate(property, ref value);
+                    _propertyValues.AddValue(property, value);
                     Changed(property, priority, AvaloniaProperty.UnsetValue, value);
                     return;
                 }
@@ -166,16 +168,14 @@ namespace Avalonia
                 validate2);
         }
 
-        private object Validate(AvaloniaProperty property, object value)
+        private void Validate(AvaloniaProperty property, ref object value)
         {
             var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType());
 
             if (validate != null && value != AvaloniaProperty.UnsetValue)
             {
-                return validate(_owner, value);
+                value = validate(_owner, value);
             }
-
-            return value;
         }
 
         private DeferredSetter<T> GetDeferredSetter<T>(AvaloniaProperty property)

+ 3 - 1
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@@ -34,7 +34,7 @@ namespace Avalonia.Build.Tasks
 
             var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input,
                 File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
-                ProjectDirectory, OutputPath);
+                ProjectDirectory, OutputPath, VerifyIl);
             if (!res.Success)
                 return false;
             if (!res.WrittenFile)
@@ -66,6 +66,8 @@ namespace Avalonia.Build.Tasks
         public string ProjectDirectory { get; set; }
         
         public string OutputPath { get; set; }
+
+        public bool VerifyIl { get; set; }
         
         public IBuildEngine BuildEngine { get; set; }
         public ITaskHost HostObject { get; set; }

+ 2 - 1
src/Avalonia.Build.Tasks/Program.cs

@@ -28,7 +28,8 @@ namespace Avalonia.Build.Tasks
                 ReferencesFilePath = args[1],
                 OutputPath = args[2],
                 BuildEngine = new ConsoleBuildEngine(),
-                ProjectDirectory = Directory.GetCurrentDirectory()
+                ProjectDirectory = Directory.GetCurrentDirectory(),
+                VerifyIl = true
             }.Execute() ?
                 0 :
                 2;

+ 2 - 2
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -40,7 +40,7 @@ namespace Avalonia.Build.Tasks
         }
         
         public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory,
-            string output)
+            string output, bool verifyIl)
         {
             var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input);
             var asm = typeSystem.TargetAssemblyDefinition;
@@ -65,7 +65,7 @@ namespace Avalonia.Build.Tasks
             var contextClass = XamlIlContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
                 xamlLanguage);
 
-            var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass);
+            var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass) { EnableIlVerification = verifyIl };
 
             var editorBrowsableAttribute = typeSystem
                 .GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute"))

+ 2 - 3
src/Avalonia.Controls/AppBuilderBase.cs

@@ -125,9 +125,8 @@ namespace Avalonia.Controls
             });
             
             // Copy-pasted because we can't call extension methods due to generic constraints
-            var lifetime = new ClassicDesktopStyleApplicationLifetime(Instance) {ShutdownMode = ShutdownMode.OnMainWindowClose};
-            Instance.ApplicationLifetime = lifetime;
-            SetupWithoutStarting();
+            var lifetime = new ClassicDesktopStyleApplicationLifetime() {ShutdownMode = ShutdownMode.OnMainWindowClose};
+            SetupWithLifetime(lifetime);
             lifetime.Start(Array.Empty<string>());
         }
 

+ 4 - 5
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@@ -5,12 +5,12 @@ using System.Threading;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Interactivity;
+using Avalonia.Threading;
 
 namespace Avalonia.Controls.ApplicationLifetimes
 {
     public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable
     {
-        private readonly Application _app;
         private int _exitCode;
         private CancellationTokenSource _cts;
         private bool _isShuttingDown;
@@ -34,12 +34,11 @@ namespace Avalonia.Controls.ApplicationLifetimes
             _activeLifetime?._windows.Add((Window)sender);
         }
 
-        public ClassicDesktopStyleApplicationLifetime(Application app)
+        public ClassicDesktopStyleApplicationLifetime()
         {
             if (_activeLifetime != null)
                 throw new InvalidOperationException(
                     "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed");
-            _app = app;
             _activeLifetime = this;
         }
         
@@ -103,7 +102,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
             Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args));
             _cts = new CancellationTokenSource();
             MainWindow?.Show();
-            _app.Run(_cts.Token);
+            Dispatcher.UIThread.MainLoop(_cts.Token);
             Environment.ExitCode = _exitCode;
             return _exitCode;
         }
@@ -124,7 +123,7 @@ namespace Avalonia
             this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
             where T : AppBuilderBase<T>, new()
         {
-            var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode};
+            var lifetime = new ClassicDesktopStyleApplicationLifetime() {ShutdownMode = shutdownMode};
             builder.SetupWithLifetime(lifetime);
             return lifetime.Start(args);
         }

+ 1 - 1
src/Avalonia.Controls/AutoCompleteBox.cs

@@ -704,7 +704,7 @@ namespace Avalonia.Controls
                 added.Add(e.NewValue);
             }
 
-            OnSelectionChanged(new SelectionChangedEventArgs(SelectionChangedEvent, removed, added));
+            OnSelectionChanged(new SelectionChangedEventArgs(SelectionChangedEvent, added, removed));
         }
 
         /// <summary>

+ 10 - 3
src/Avalonia.Controls/ColumnDefinition.cs

@@ -26,6 +26,16 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<GridLength> WidthProperty =
             AvaloniaProperty.Register<ColumnDefinition, GridLength>(nameof(Width), new GridLength(1, GridUnitType.Star));
 
+        /// <summary>
+        /// Initializes static members of the <see cref="ColumnDefinition"/> class.
+        /// </summary>
+        static ColumnDefinition()
+        {
+            AffectsParentMeasure(MinWidthProperty, MaxWidthProperty);
+
+            WidthProperty.Changed.AddClassHandler<DefinitionBase>(OnUserSizePropertyChanged);
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ColumnDefinition"/> class.
         /// </summary>
@@ -68,7 +78,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(MaxWidthProperty, value);
             }
         }
@@ -84,7 +93,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(MinWidthProperty, value);
             }
         }
@@ -100,7 +108,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(WidthProperty, value);
             }
         }

+ 51 - 3
src/Avalonia.Controls/DefinitionBase.cs

@@ -7,9 +7,6 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics;
-
-using Avalonia;
-using Avalonia.Collections;
 using Avalonia.Utilities;
 
 namespace Avalonia.Controls
@@ -50,6 +47,8 @@ namespace Avalonia.Controls
                     }
                 }
             }
+
+            Parent?.InvalidateMeasure();
         }
 
         /// <summary>
@@ -63,6 +62,8 @@ namespace Avalonia.Controls
                 _sharedState.RemoveMember(this);
                 _sharedState = null;
             }
+
+            Parent?.InvalidateMeasure();
         }
 
         /// <summary>
@@ -114,6 +115,36 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <remarks>
+        /// Notifies parent <see cref="Grid"/> or size scope that definition size has been changed.
+        /// </remarks>
+        internal static void OnUserSizePropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (definition.Parent == null)
+            {
+                return;
+            }
+
+            if (definition._sharedState != null)
+            {
+                definition._sharedState.Invalidate();
+            }
+            else
+            {
+                GridUnitType oldUnitType = ((GridLength)e.OldValue).GridUnitType;
+                GridUnitType newUnitType = ((GridLength)e.NewValue).GridUnitType;
+
+                if (oldUnitType != newUnitType)
+                {
+                    definition.Parent.Invalidate();
+                }
+                else
+                {
+                    definition.Parent.InvalidateMeasure();
+                }
+            }
+        }
+
         /// <summary>
         /// Returns <c>true</c> if this definition is a part of shared group.
         /// </summary>
@@ -730,5 +761,22 @@ namespace Avalonia.Controls
             SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged);
             PrivateSharedSizeScopeProperty.Changed.AddClassHandler<DefinitionBase>(OnPrivateSharedSizeScopePropertyChanged);
         }
+
+        /// <summary>
+        /// Marks a property on a definition as affecting the parent grid's measurement.
+        /// </summary>
+        /// <param name="properties">The properties.</param>
+        protected static void AffectsParentMeasure(params AvaloniaProperty[] properties)
+        {
+            void Invalidate(AvaloniaPropertyChangedEventArgs e)
+            {
+                (e.Sender as DefinitionBase)?.Parent?.InvalidateMeasure();
+            }
+
+            foreach (var property in properties)
+            {
+                property.Changed.Subscribe(Invalidate);
+            }
+        }
     }
 }

+ 1 - 0
src/Avalonia.Controls/DesktopApplicationExtensions.cs

@@ -50,6 +50,7 @@ namespace Avalonia.Controls
         /// On desktop-style platforms runs the application's main loop with custom CancellationToken
         /// without setting a lifetime.
         /// </summary>
+        /// <param name="app">The application.</param>
         /// <param name="token">The token to track.</param>
         public static void Run(this Application app, CancellationToken token)
         {

+ 18 - 5
src/Avalonia.Controls/Grid.cs

@@ -6,6 +6,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.Diagnostics;
 using System.Linq;
 using System.Threading;
@@ -24,13 +25,14 @@ namespace Avalonia.Controls
     {
         static Grid()
         {
-            IsSharedSizeScopeProperty.Changed.AddClassHandler<Control>(DefinitionBase.OnIsSharedSizeScopePropertyChanged);
             ShowGridLinesProperty.Changed.AddClassHandler<Grid>(OnShowGridLinesPropertyChanged);
 
-            ColumnProperty.Changed.AddClassHandler<Grid>(OnCellAttachedPropertyChanged);
-            ColumnSpanProperty.Changed.AddClassHandler<Grid>(OnCellAttachedPropertyChanged);
-            RowProperty.Changed.AddClassHandler<Grid>(OnCellAttachedPropertyChanged);
-            RowSpanProperty.Changed.AddClassHandler<Grid>(OnCellAttachedPropertyChanged);
+            IsSharedSizeScopeProperty.Changed.AddClassHandler<Control>(DefinitionBase.OnIsSharedSizeScopePropertyChanged);
+            ColumnProperty.Changed.AddClassHandler<Control>(OnCellAttachedPropertyChanged);
+            ColumnSpanProperty.Changed.AddClassHandler<Control>(OnCellAttachedPropertyChanged);
+            RowProperty.Changed.AddClassHandler<Control>(OnCellAttachedPropertyChanged);
+            RowSpanProperty.Changed.AddClassHandler<Control>(OnCellAttachedPropertyChanged);
+
             AffectsParentMeasure<Grid>(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty);
         }
 
@@ -177,6 +179,7 @@ namespace Avalonia.Controls
                 if (_data == null) { _data = new ExtendedData(); }
                 _data.ColumnDefinitions = value;
                 _data.ColumnDefinitions.Parent = this;
+                InvalidateMeasure();
             }
         }
 
@@ -197,6 +200,7 @@ namespace Avalonia.Controls
                 if (_data == null) { _data = new ExtendedData(); }
                 _data.RowDefinitions = value;
                 _data.RowDefinitions.Parent = this;
+                InvalidateMeasure();
             }
         }
 
@@ -568,6 +572,15 @@ namespace Avalonia.Controls
             return (arrangeSize);
         }
 
+        /// <summary>
+        /// <see cref="Panel.ChildrenChanged"/>
+        /// </summary>
+        protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            CellsStructureDirty = true;
+            base.ChildrenChanged(sender, e);
+        }
+
         /// <summary>
         /// Invalidates grid caches and makes the grid dirty for measure.
         /// </summary>

+ 5 - 3
src/Avalonia.Controls/ItemsControl.cs

@@ -489,18 +489,20 @@ namespace Avalonia.Controls
             bool wrap)
         {
             IInputElement result;
+            var c = from;
 
             do
             {
-                result = container.GetControl(direction, from, wrap);
+                result = container.GetControl(direction, c, wrap);
+                from = from ?? result;
 
                 if (result?.Focusable == true)
                 {
                     return result;
                 }
 
-                from = result;
-            } while (from != null);
+                c = result;
+            } while (c != null && c != from);
 
             return null;
         }

+ 2 - 1
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@@ -273,7 +273,8 @@ namespace Avalonia.Controls.Platform
 
             if (item.IsTopLevel)
             {
-                if (item.Parent.SelectedItem?.IsSubMenuOpen == true)
+                if (item != item.Parent.SelectedItem &&
+                    item.Parent.SelectedItem?.IsSubMenuOpen == true)
                 {
                     item.Parent.SelectedItem.Close();
                     SelectItemAndAncestors(item);

+ 3 - 3
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@@ -21,7 +21,7 @@ namespace Avalonia.Platform
 
         private DragDropEffects _allowedEffects;
         private IDataObject _draggedData;
-        private IInputElement _lastRoot;
+        private IInputRoot _lastRoot;
         private Point _lastPosition;
         private StandardCursorType _lastCursorType;
         private object _originalCursor;
@@ -56,7 +56,7 @@ namespace Avalonia.Platform
             return DragDropEffects.None;
         }
 
-        private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, RawInputModifiers modifiers)
+        private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputRoot root, Point pt, RawInputModifiers modifiers)
         {
             _lastPosition = pt;
 
@@ -91,7 +91,7 @@ namespace Avalonia.Platform
             return StandardCursorType.No;
         }
         
-        private void UpdateCursor(IInputElement root, DragDropEffects effect)
+        private void UpdateCursor(IInputRoot root, DragDropEffects effect)
         {
             if (_lastRoot != root)
             {

+ 2 - 0
src/Avalonia.Controls/Primitives/PopupRoot.cs

@@ -41,6 +41,8 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Initializes a new instance of the <see cref="PopupRoot"/> class.
         /// </summary>
+        /// <param name="parent">The popup parent.</param>
+        /// <param name="impl">The popup implementation.</param>
         /// <param name="dependencyResolver">
         /// The dependency resolver to use. If null the default dependency resolver will be used.
         /// </param>

+ 4 - 0
src/Avalonia.Controls/Primitives/Thumb.cs

@@ -83,6 +83,8 @@ namespace Avalonia.Controls.Primitives
                 Vector = (Vector)_lastPoint,
             };
 
+            PseudoClasses.Add(":pressed");
+
             RaiseEvent(ev);
         }
 
@@ -102,6 +104,8 @@ namespace Avalonia.Controls.Primitives
 
                 RaiseEvent(ev);
             }
+
+            PseudoClasses.Remove(":pressed");
         }
     }
 }

+ 2 - 0
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@@ -227,6 +227,7 @@ namespace Avalonia.Controls.Remote.Server
                         Input?.Invoke(new RawKeyEventArgs(
                             KeyboardDevice,
                             0,
+                            InputRoot,
                             key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
                             (Key)key.Key,
                             GetAvaloniaRawInputModifiers(key.Modifiers)));
@@ -241,6 +242,7 @@ namespace Avalonia.Controls.Remote.Server
                         Input?.Invoke(new RawTextInputEventArgs(
                             KeyboardDevice,
                             0,
+                            InputRoot,
                             text.Text));
                     }, DispatcherPriority.Input);
                 }

+ 10 - 3
src/Avalonia.Controls/RowDefinition.cs

@@ -26,6 +26,16 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<GridLength> HeightProperty =
             AvaloniaProperty.Register<RowDefinition, GridLength>(nameof(Height), new GridLength(1, GridUnitType.Star));
 
+        /// <summary>
+        /// Initializes static members of the <see cref="RowDefinition"/> class.
+        /// </summary>
+        static RowDefinition()
+        {
+            AffectsParentMeasure(MaxHeightProperty, MinHeightProperty);
+
+            HeightProperty.Changed.AddClassHandler<DefinitionBase>(OnUserSizePropertyChanged);
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="RowDefinition"/> class.
         /// </summary>
@@ -68,7 +78,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(MaxHeightProperty, value);
             }
         }
@@ -84,7 +93,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(MinHeightProperty, value);
             }
         }
@@ -100,7 +108,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(HeightProperty, value);
             }
         }

+ 9 - 9
src/Avalonia.Input/DragDropDevice.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Input
         
         private Interactive _lastTarget = null;
         
-        private Interactive GetTarget(IInputElement root, Point local)
+        private Interactive GetTarget(IInputRoot root, Point local)
         {
             var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault();
             if (target != null && DragDrop.GetAllowDrop(target))
@@ -19,7 +19,7 @@ namespace Avalonia.Input
             return null;
         }
         
-        private DragDropEffects RaiseDragEvent(Interactive target, IInputElement inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data, InputModifiers modifiers)
+        private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data, InputModifiers modifiers)
         {
             if (target == null)
                 return DragDropEffects.None;
@@ -38,13 +38,13 @@ namespace Avalonia.Input
             return args.DragEffects;
         }
         
-        private DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
+        private DragDropEffects DragEnter(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
         {
             _lastTarget = GetTarget(inputRoot, point);
             return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers);
         }
 
-        private DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
+        private DragDropEffects DragOver(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
         {
             var target = GetTarget(inputRoot, point);
 
@@ -77,7 +77,7 @@ namespace Avalonia.Input
             }
         }
 
-        private DragDropEffects Drop(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
+        private DragDropEffects Drop(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
         {
             try
             {
@@ -100,16 +100,16 @@ namespace Avalonia.Input
             switch (e.Type)
             {
                 case RawDragEventType.DragEnter:
-                    e.Effects = DragEnter(e.InputRoot, e.Location, e.Data, e.Effects, e.Modifiers);
+                    e.Effects = DragEnter(e.Root, e.Location, e.Data, e.Effects, e.Modifiers);
                     break;
                 case RawDragEventType.DragOver:
-                    e.Effects = DragOver(e.InputRoot, e.Location, e.Data, e.Effects, e.Modifiers);
+                    e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.Modifiers);
                     break;
                 case RawDragEventType.DragLeave:
-                    DragLeave(e.InputRoot);
+                    DragLeave(e.Root);
                     break;
                 case RawDragEventType.Drop:
-                    e.Effects = Drop(e.InputRoot, e.Location, e.Data, e.Effects, e.Modifiers);
+                    e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.Modifiers);
                     break;
             }
         }

+ 48 - 54
src/Avalonia.Input/KeyboardDevice.cs

@@ -70,66 +70,60 @@ namespace Avalonia.Input
         {
             if(e.Handled)
                 return;
-            IInputElement element = FocusedElement;
 
-            if (element != null)
-            {
-                var keyInput = e as RawKeyEventArgs;
+            var element = FocusedElement ?? e.Root;
 
-                if (keyInput != null)
+            if (e is RawKeyEventArgs keyInput)
+            {
+                switch (keyInput.Type)
                 {
-                    switch (keyInput.Type)
-                    {
-                        case RawKeyEventType.KeyDown:
-                        case RawKeyEventType.KeyUp:
-                            var routedEvent = keyInput.Type == RawKeyEventType.KeyDown
-                                ? InputElement.KeyDownEvent
-                                : InputElement.KeyUpEvent;
-
-                            KeyEventArgs ev = new KeyEventArgs
-                            {
-                                RoutedEvent = routedEvent,
-                                Device = this,
-                                Key = keyInput.Key,
-                                KeyModifiers = KeyModifiersUtils.ConvertToKey(keyInput.Modifiers),
-                                Source = element,
-                            };
-
-                            IVisual currentHandler = element;
-                            while (currentHandler != null && !ev.Handled && keyInput.Type == RawKeyEventType.KeyDown)
-                            {
-                                var bindings = (currentHandler as IInputElement)?.KeyBindings;
-                                if(bindings!=null)
-                                    foreach (var binding in bindings)
-                                    {
-                                        if(ev.Handled)
-                                            break;
-                                        binding.TryHandle(ev);
-                                    }
-                                currentHandler = currentHandler.VisualParent;
-                            }
-
-                            element.RaiseEvent(ev);
-                            e.Handled = ev.Handled;
-                            break;
-                    }
+                    case RawKeyEventType.KeyDown:
+                    case RawKeyEventType.KeyUp:
+                        var routedEvent = keyInput.Type == RawKeyEventType.KeyDown
+                            ? InputElement.KeyDownEvent
+                            : InputElement.KeyUpEvent;
+
+                        KeyEventArgs ev = new KeyEventArgs
+                        {
+                            RoutedEvent = routedEvent,
+                            Device = this,
+                            Key = keyInput.Key,
+                            KeyModifiers = KeyModifiersUtils.ConvertToKey(keyInput.Modifiers),
+                            Source = element,
+                        };
+
+                        IVisual currentHandler = element;
+                        while (currentHandler != null && !ev.Handled && keyInput.Type == RawKeyEventType.KeyDown)
+                        {
+                            var bindings = (currentHandler as IInputElement)?.KeyBindings;
+                            if (bindings != null)
+                                foreach (var binding in bindings)
+                                {
+                                    if (ev.Handled)
+                                        break;
+                                    binding.TryHandle(ev);
+                                }
+                            currentHandler = currentHandler.VisualParent;
+                        }
+
+                        element.RaiseEvent(ev);
+                        e.Handled = ev.Handled;
+                        break;
                 }
+            }
 
-                var text = e as RawTextInputEventArgs;
-
-                if (text != null)
+            if (e is RawTextInputEventArgs text)
+            {
+                var ev = new TextInputEventArgs()
                 {
-                    var ev = new TextInputEventArgs()
-                    {
-                        Device = this,
-                        Text = text.Text,
-                        Source = element,
-                        RoutedEvent = InputElement.TextInputEvent
-                    };
-
-                    element.RaiseEvent(ev);
-                    e.Handled = ev.Handled;
-                }
+                    Device = this,
+                    Text = text.Text,
+                    Source = element,
+                    RoutedEvent = InputElement.TextInputEvent
+                };
+
+                element.RaiseEvent(ev);
+                e.Handled = ev.Handled;
             }
         }
     }

+ 2 - 4
src/Avalonia.Input/Raw/RawDragEvent.cs

@@ -2,7 +2,6 @@
 {
     public class RawDragEvent : RawInputEventArgs
     {
-        public IInputElement InputRoot { get; }
         public Point Location { get; set; }
         public IDataObject Data { get; }
         public DragDropEffects Effects { get; set; }
@@ -10,11 +9,10 @@
         public InputModifiers Modifiers { get; }
 
         public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type, 
-            IInputElement inputRoot, Point location, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers)
-            :base(inputDevice, 0)
+            IInputRoot root, Point location, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers)
+            :base(inputDevice, 0, root)
         {
             Type = type;
-            InputRoot = inputRoot;
             Location = location;
             Data = data;
             Effects = effects;

+ 8 - 1
src/Avalonia.Input/Raw/RawInputEventArgs.cs

@@ -21,12 +21,14 @@ namespace Avalonia.Input.Raw
         /// </summary>
         /// <param name="device">The associated device.</param>
         /// <param name="timestamp">The event timestamp.</param>
-        public RawInputEventArgs(IInputDevice device, ulong timestamp)
+        /// <param name="root">The root from which the event originates.</param>
+        public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root)
         {
             Contract.Requires<ArgumentNullException>(device != null);
 
             Device = device;
             Timestamp = timestamp;
+            Root = root;
         }
 
         /// <summary>
@@ -34,6 +36,11 @@ namespace Avalonia.Input.Raw
         /// </summary>
         public IInputDevice Device { get; }
 
+        /// <summary>
+        /// Gets the root from which the event originates.
+        /// </summary>
+        public IInputRoot Root { get; }
+
         /// <summary>
         /// Gets or sets a value indicating whether the event was handled.
         /// </summary>

+ 2 - 1
src/Avalonia.Input/Raw/RawKeyEventArgs.cs

@@ -14,9 +14,10 @@ namespace Avalonia.Input.Raw
         public RawKeyEventArgs(
             IKeyboardDevice device,
             ulong timestamp,
+            IInputRoot root,
             RawKeyEventType type,
             Key key, RawInputModifiers modifiers)
-            : base(device, timestamp)
+            : base(device, timestamp, root)
         {
             Key = key;
             Type = type;

+ 1 - 7
src/Avalonia.Input/Raw/RawPointerEventArgs.cs

@@ -44,22 +44,16 @@ namespace Avalonia.Input.Raw
             RawPointerEventType type,
             Point position, 
             RawInputModifiers inputModifiers)
-            : base(device, timestamp)
+            : base(device, timestamp, root)
         {
             Contract.Requires<ArgumentNullException>(device != null);
             Contract.Requires<ArgumentNullException>(root != null);
 
-            Root = root;
             Position = position;
             Type = type;
             InputModifiers = inputModifiers;
         }
 
-        /// <summary>
-        /// Gets the root from which the event originates.
-        /// </summary>
-        public IInputRoot Root { get; }
-
         /// <summary>
         /// Gets the mouse position, in client DIPs.
         /// </summary>

+ 8 - 3
src/Avalonia.Input/Raw/RawTextInputEventArgs.cs

@@ -5,11 +5,16 @@ namespace Avalonia.Input.Raw
 {
     public class RawTextInputEventArgs : RawInputEventArgs
     {
-        public string Text { get; set; }
-
-        public RawTextInputEventArgs(IKeyboardDevice device, ulong timestamp, string text) : base(device, timestamp)
+        public RawTextInputEventArgs(
+            IKeyboardDevice device,
+            ulong timestamp,
+            IInputRoot root,
+            string text)
+            : base(device, timestamp, root)
         {
             Text = text;
         }
+
+        public string Text { get; set; }
     }
 }

+ 2 - 2
src/Avalonia.Native/WindowImplBase.cs

@@ -204,7 +204,7 @@ namespace Avalonia.Native
         {
             Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
 
-            var args = new RawTextInputEventArgs(_keyboard, timeStamp, text);
+            var args = new RawTextInputEventArgs(_keyboard, timeStamp, _inputRoot, text);
 
             Input?.Invoke(args);
 
@@ -215,7 +215,7 @@ namespace Avalonia.Native
         {
             Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
 
-            var args = new RawKeyEventArgs(_keyboard, timeStamp, (RawKeyEventType)type, (Key)key, (RawInputModifiers)modifiers);
+            var args = new RawKeyEventArgs(_keyboard, timeStamp, _inputRoot, (RawKeyEventType)type, (Key)key, (RawInputModifiers)modifiers);
 
             Input?.Invoke(args);
 

+ 6 - 1
src/Avalonia.Themes.Default/Accents/BaseDark.xaml

@@ -14,7 +14,9 @@
         <Color x:Key="ThemeBorderHighColor">#FFA0A0A0</Color>
         <Color x:Key="ThemeControlLowColor">#FF282828</Color>
         <Color x:Key="ThemeControlMidColor">#FF505050</Color>
+        <Color x:Key="ThemeControlMidHighColor">#FF686868</Color>
         <Color x:Key="ThemeControlHighColor">#FF808080</Color>
+        <Color x:Key="ThemeControlVeryHighColor">#FFEFEBEF</Color>
         <Color x:Key="ThemeControlHighlightLowColor">#FFA8A8A8</Color>
         <Color x:Key="ThemeControlHighlightMidColor">#FF828282</Color>
         <Color x:Key="ThemeControlHighlightHighColor">#FF505050</Color>
@@ -32,7 +34,9 @@
         <SolidColorBrush x:Key="ThemeBorderHighBrush" Color="{DynamicResource ThemeBorderHighColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlLowBrush" Color="{DynamicResource ThemeControlLowColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlMidBrush" Color="{DynamicResource ThemeControlMidColor}"></SolidColorBrush>
+        <SolidColorBrush x:Key="ThemeControlMidHighBrush" Color="{DynamicResource ThemeControlMidHighColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlHighBrush" Color="{DynamicResource ThemeControlHighColor}"></SolidColorBrush>
+        <SolidColorBrush x:Key="ThemeControlVeryHighBrush" Color="{DynamicResource ThemeControlVeryHighColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlHighlightLowBrush" Color="{DynamicResource ThemeControlHighlightLowColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlHighlightMidBrush" Color="{DynamicResource ThemeControlHighlightMidColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlHighlightHighBrush" Color="{DynamicResource ThemeControlHighlightHighColor}"></SolidColorBrush>
@@ -61,6 +65,7 @@
         <sys:Double x:Key="FontSizeNormal">12</sys:Double>
         <sys:Double x:Key="FontSizeLarge">16</sys:Double>
 
-        <sys:Double x:Key="ScrollBarThickness">10</sys:Double>
+        <sys:Double x:Key="ScrollBarThickness">18</sys:Double>
+        <sys:Double x:Key="ScrollBarThumbThickness">8</sys:Double>
     </Style.Resources>
 </Style>

+ 12 - 4
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@@ -12,9 +12,14 @@
         <Color x:Key="ThemeBorderLowColor">#FFAAAAAA</Color>
         <Color x:Key="ThemeBorderMidColor">#FF888888</Color>
         <Color x:Key="ThemeBorderHighColor">#FF333333</Color>
-        <Color x:Key="ThemeControlLowColor">#FFFFFFFF</Color>
-        <Color x:Key="ThemeControlMidColor">#FFAAAAAA</Color>
-        <Color x:Key="ThemeControlHighColor">#FF888888</Color>
+      
+      
+        <Color x:Key="ThemeControlLowColor">#FF868999</Color>
+        <Color x:Key="ThemeControlMidColor">#FFF5F5F5</Color>
+        <Color x:Key="ThemeControlMidHighColor">#FFC2C3C9</Color>
+        <Color x:Key="ThemeControlHighColor">#FF686868</Color>
+        <Color x:Key="ThemeControlVeryHighColor">#FF5B5B5B</Color>
+
         <Color x:Key="ThemeControlHighlightLowColor">#FFF0F0F0</Color>
         <Color x:Key="ThemeControlHighlightMidColor">#FFD0D0D0</Color>
         <Color x:Key="ThemeControlHighlightHighColor">#FF808080</Color>
@@ -32,7 +37,9 @@
         <SolidColorBrush x:Key="ThemeBorderHighBrush" Color="{DynamicResource ThemeBorderHighColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlLowBrush" Color="{DynamicResource ThemeControlLowColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlMidBrush" Color="{DynamicResource ThemeControlMidColor}"></SolidColorBrush>
+        <SolidColorBrush x:Key="ThemeControlMidHighBrush" Color="{DynamicResource ThemeControlMidHighColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlHighBrush" Color="{DynamicResource ThemeControlHighColor}"></SolidColorBrush>
+        <SolidColorBrush x:Key="ThemeControlVeryHighBrush" Color="{DynamicResource ThemeControlVeryHighColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlHighlightLowBrush" Color="{DynamicResource ThemeControlHighlightLowColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlHighlightMidBrush" Color="{DynamicResource ThemeControlHighlightMidColor}"></SolidColorBrush>
         <SolidColorBrush x:Key="ThemeControlHighlightHighBrush" Color="{DynamicResource ThemeControlHighlightHighColor}"></SolidColorBrush>
@@ -61,6 +68,7 @@
         <sys:Double x:Key="FontSizeNormal">12</sys:Double>
         <sys:Double x:Key="FontSizeLarge">16</sys:Double>
 
-        <sys:Double x:Key="ScrollBarThickness">10</sys:Double>
+        <sys:Double x:Key="ScrollBarThickness">18</sys:Double>
+        <sys:Double x:Key="ScrollBarThumbThickness">8</sys:Double>
     </Style.Resources>
 </Style>

+ 0 - 8
src/Avalonia.Themes.Default/ContextMenu.xaml

@@ -11,19 +11,11 @@
               BorderThickness="{TemplateBinding BorderThickness}"
               Padding="{TemplateBinding Padding}">
           <ScrollViewer>
-            <Panel>
               <ItemsPresenter Name="PART_ItemsPresenter"
                               Items="{TemplateBinding Items}"
                               ItemsPanel="{TemplateBinding ItemsPanel}"
                               ItemTemplate="{TemplateBinding ItemTemplate}"
                               KeyboardNavigation.TabNavigation="Continue"/>
-              <Rectangle Name="iconSeparator"
-                         Fill="{DynamicResource ThemeControlMidBrush}"
-                         HorizontalAlignment="Left"
-                         IsHitTestVisible="False"
-                         Margin="29,2,0,2"
-                         Width="1"/>
-              </Panel>
           </ScrollViewer>
       </Border>
     </ControlTemplate>

+ 7 - 22
src/Avalonia.Themes.Default/MenuItem.xaml

@@ -4,14 +4,14 @@
   <Style Selector="MenuItem">
     <Setter Property="Background" Value="Transparent"/>
     <Setter Property="BorderThickness" Value="1"/>
-    <Setter Property="Padding" Value="6,0"/>
+    <Setter Property="Padding" Value="6 0"/>
     <Setter Property="Template">
       <ControlTemplate>
         <Border Name="root"
                 Background="{TemplateBinding Background}"
                 BorderBrush="{TemplateBinding BorderBrush}"
                 BorderThickness="{TemplateBinding BorderThickness}">
-          <Grid ColumnDefinitions="22,13,*,20">
+          <Grid ColumnDefinitions="20,5,*,20">
             <ContentPresenter Name="icon"
                               Content="{TemplateBinding Icon}"
                               Width="16"
@@ -50,20 +50,12 @@
               <Border Background="{TemplateBinding Background}"
                       BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                       BorderThickness="{TemplateBinding BorderThickness}">
-                <ScrollViewer>
-                  <Panel>
+                <ScrollViewer>                  
                     <ItemsPresenter Name="PART_ItemsPresenter"
                                     Items="{TemplateBinding Items}"
                                     ItemsPanel="{TemplateBinding ItemsPanel}"
                                     ItemTemplate="{TemplateBinding ItemTemplate}"
-                                    Margin="2"/>
-                    <Rectangle Name="iconSeparator"
-                               Fill="{DynamicResource ThemeControlMidBrush}"
-                               HorizontalAlignment="Left"
-                               IsHitTestVisible="False"
-                               Margin="29,2,0,2"
-                               Width="1"/>
-                  </Panel>
+                                    Margin="4 2"/>                                      
                 </ScrollViewer>
               </Border>
             </Popup>
@@ -77,13 +69,14 @@
     <Setter Property="Template">
       <ControlTemplate>
         <Separator Background="{DynamicResource ThemeControlMidBrush}"
-                   Margin="29,1,0,1"
+                   Margin="20,1,0,1"
                    Height="1"/>
       </ControlTemplate>
     </Setter>
   </Style>
 
   <Style Selector="Menu > MenuItem">
+    <Setter Property="Padding" Value="6 0"/>
     <Setter Property="Template">
       <ControlTemplate>
         <Border Name="root"
@@ -108,19 +101,11 @@
                       BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                       BorderThickness="{TemplateBinding BorderThickness}">
                 <ScrollViewer>
-                  <Panel>
-                    <ItemsPresenter Name="PART_ItemsPresenter"
+                  <ItemsPresenter Name="PART_ItemsPresenter"
                                     Items="{TemplateBinding Items}"
                                     ItemsPanel="{TemplateBinding ItemsPanel}"
                                     ItemTemplate="{TemplateBinding ItemTemplate}"
                                     Margin="2"/>
-                    <Rectangle Name="iconSeparator"
-                               Fill="{DynamicResource ThemeControlMidBrush}"
-                               HorizontalAlignment="Left"
-                               IsHitTestVisible="False"
-                               Margin="29,2,0,2"
-                               Width="1"/>
-                  </Panel>
                 </ScrollViewer>
               </Border>
             </Popup>

+ 1 - 5
src/Avalonia.Themes.Default/RepeatButton.xaml

@@ -33,12 +33,8 @@
         <Setter Property="BorderBrush"
                 Value="{DynamicResource ThemeBorderMidBrush}" />
     </Style>
-    <Style Selector="RepeatButton:pressed  /template/ ContentPresenter">
-        <Setter Property="Background"
-                Value="{DynamicResource ThemeControlHighBrush}" />
-    </Style>
     <Style Selector="RepeatButton:disabled">
         <Setter Property="Opacity"
                 Value="{DynamicResource ThemeDisabledOpacity}" />
     </Style>
-</Styles>
+</Styles>

+ 138 - 126
src/Avalonia.Themes.Default/ScrollBar.xaml

@@ -1,128 +1,140 @@
 <Styles xmlns="https://github.com/avaloniaui">
-    <Style Selector="ScrollBar">
-        <Setter Property="Template">
-            <ControlTemplate>
-                <Border Background="{DynamicResource ThemeControlMidBrush}">
-                    <Grid RowDefinitions="Auto,*,Auto">
-                        <RepeatButton Name="PART_LineUpButton"
-                                      Classes="repeat"
-                                      Grid.Row="0"
-                                      Grid.Column="0"
-                                      Focusable="False">
-                            <Path Data="M 0,4 C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4 z"
-                                  Stretch="Uniform"
-                                  Fill="{DynamicResource ThemeForegroundLowBrush}" />
-                        </RepeatButton>
-                        <Track Grid.Row="1"
-                               Grid.Column="1"
-                               Minimum="{TemplateBinding Minimum}"
-                               Maximum="{TemplateBinding Maximum}"
-                               Value="{TemplateBinding Value, Mode=TwoWay}"
-                               ViewportSize="{TemplateBinding ViewportSize}"
-                               Orientation="{TemplateBinding Orientation}"
-                               IsDirectionReversed="True">
-                            <Track.DecreaseButton>
-                                <RepeatButton Name="PART_PageUpButton"
-                                              Classes="repeattrack"
-                                              Focusable="False"/>
-                            </Track.DecreaseButton>
-                            <Track.IncreaseButton>
-                                <RepeatButton Name="PART_PageDownButton"
-                                              Classes="repeattrack"
-                                              Focusable="False"/>
-                            </Track.IncreaseButton>
-                            <Thumb Name="thumb"/>
-                        </Track>
-                        <RepeatButton Name="PART_LineDownButton"
-                                      Classes="repeat"
-                                      Grid.Row="2"
-                                      Grid.Column="2"
-                                      Focusable="False">
-                            <Path Data="M 0,2.5 C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5 z"
-                                  Stretch="Uniform"
-                                  Fill="{DynamicResource ThemeForegroundLowBrush}" />
-                        </RepeatButton>
-                    </Grid>
-                </Border>
-            </ControlTemplate>
-        </Setter>
-    </Style>
-    <Style Selector="ScrollBar:horizontal">
-        <Setter Property="Height" Value="{DynamicResource ScrollBarThickness}" />
-        <Setter Property="Template">
-            <ControlTemplate>
-                <Border Background="{DynamicResource ThemeControlMidBrush}">
-                    <Grid ColumnDefinitions="Auto,*,Auto">
-                        <RepeatButton Name="PART_LineUpButton"
-                                      Classes="repeat"
-                                      Grid.Row="0"
-                                      Grid.Column="0"
-                                      Focusable="False">
-                            <Path Data="M 3.18,7 C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7 z"
-                                  Stretch="Uniform"
-                                  Fill="{DynamicResource ThemeForegroundLowBrush}" />
-                        </RepeatButton>
-                        <Track Grid.Row="1"
-                               Grid.Column="1"
-                               Minimum="{TemplateBinding Minimum}"
-                               Maximum="{TemplateBinding Maximum}"
-                               Value="{TemplateBinding Value, Mode=TwoWay}"
-                               ViewportSize="{TemplateBinding ViewportSize}"
-                               Orientation="{TemplateBinding Orientation}">
-                            <Track.DecreaseButton>
-                                <RepeatButton Name="PART_PageUpButton"
-                                              Classes="repeattrack"
-                                              Focusable="False"/>
-                            </Track.DecreaseButton>
-                            <Track.IncreaseButton>
-                                <RepeatButton Name="PART_PageDownButton"
-                                              Classes="repeattrack"
-                                              Focusable="False"/>
-                            </Track.IncreaseButton>
-                            <Thumb Name="thumb"/>
-                        </Track>
-                        <RepeatButton Name="PART_LineDownButton"
-                                      Classes="repeat"
-                                      Grid.Row="2"
-                                      Grid.Column="2"
-                                      Focusable="False">
-                            <Path Data="M 1.81,7 C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7 z"
-                                  Stretch="Uniform"
-                                  Fill="{DynamicResource ThemeForegroundLowBrush}" />
-                        </RepeatButton>
-                    </Grid>
-                </Border>
-            </ControlTemplate>
-        </Setter>
-    </Style>
-    <Style Selector="ScrollBar /template/ Thumb#thumb">
-        <Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/>
-        <Setter Property="Template">
-            <Setter.Value>
-                <ControlTemplate>
-                    <Border Background="{TemplateBinding Background}"/>
-                </ControlTemplate>
-            </Setter.Value>
-        </Setter>
-    </Style>
-    <Style Selector="ScrollBar:horizontal /template/ Thumb#thumb">
-        <Setter Property="MinWidth" Value="{DynamicResource ScrollBarThickness}" />
-    </Style>
-    <Style Selector="ScrollBar:vertical">
-        <Setter Property="Width" Value="{DynamicResource ScrollBarThickness}" />
-    </Style>
-    <Style Selector="ScrollBar:vertical /template/ Thumb#thumb">
-        <Setter Property="MinHeight" Value="{DynamicResource ScrollBarThickness}" />
-    </Style>
-    <Style Selector="ScrollBar /template/ RepeatButton.repeat">
-        <Setter Property="Padding" Value="2" />
-        <Setter Property="BorderThickness" Value="0" />
-    </Style>
-    <Style Selector="ScrollBar /template/ RepeatButton.repeattrack">
-        <Setter Property="Template">
-            <ControlTemplate>
-                <Border Background="{TemplateBinding Background}" />
-            </ControlTemplate>
-        </Setter>
-    </Style>
+  <Style Selector="ScrollBar">
+    <Setter Property="Cursor" Value="Arrow" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Border Background="{DynamicResource ThemeControlMidBrush}">
+          <Grid RowDefinitions="Auto,*,Auto">
+            <RepeatButton Name="PART_LineUpButton" HorizontalAlignment="Center"
+                          Classes="repeat"
+                          Grid.Row="0"
+                          Focusable="False"
+                          MinHeight="{DynamicResource ScrollBarThickness}">
+              <Path Data="M 0 4 L 8 4 L 4 0 Z" />
+            </RepeatButton>
+            <Track Grid.Row="1"
+                   Grid.Column="1"
+                   Minimum="{TemplateBinding Minimum}"
+                   Maximum="{TemplateBinding Maximum}"
+                   Value="{TemplateBinding Value, Mode=TwoWay}"
+                   ViewportSize="{TemplateBinding ViewportSize}"
+                   Orientation="{TemplateBinding Orientation}"
+                   IsDirectionReversed="True">
+              <Track.DecreaseButton>
+                <RepeatButton Name="PART_PageUpButton"
+                              Classes="repeattrack"
+                              Focusable="False"/>
+              </Track.DecreaseButton>
+              <Track.IncreaseButton>
+                <RepeatButton Name="PART_PageDownButton"
+                              Classes="repeattrack"
+                              Focusable="False"/>
+              </Track.IncreaseButton>
+              <Thumb Name="thumb"/>
+            </Track>
+            <RepeatButton Name="PART_LineDownButton" HorizontalAlignment="Center"
+                          Classes="repeat"
+                          Grid.Row="2"
+                          Grid.Column="2"
+                          Focusable="False"
+                          MinHeight="{DynamicResource ScrollBarThickness}">
+              <Path Data="M 0 0 L 4 4 L 8 0 Z" />
+            </RepeatButton>
+          </Grid>
+        </Border>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+  <Style Selector="ScrollBar:horizontal">
+    <Setter Property="Height" Value="{DynamicResource ScrollBarThickness}" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Border Background="{DynamicResource ThemeControlMidBrush}">
+          <Grid ColumnDefinitions="Auto,*,Auto">
+            <RepeatButton Name="PART_LineUpButton" VerticalAlignment="Center"
+                          Classes="repeat"
+                          Grid.Row="0"
+                          Grid.Column="0"
+                          Focusable="False"
+                          MinWidth="{DynamicResource ScrollBarThickness}">
+              <Path Data="M 4 0 L 4 8 L 0 4 Z" />
+            </RepeatButton>
+            <Track Grid.Row="1"
+                   Grid.Column="1"
+                   Minimum="{TemplateBinding Minimum}"
+                   Maximum="{TemplateBinding Maximum}"
+                   Value="{TemplateBinding Value, Mode=TwoWay}"
+                   ViewportSize="{TemplateBinding ViewportSize}"
+                   Orientation="{TemplateBinding Orientation}">
+              <Track.DecreaseButton>
+                <RepeatButton Name="PART_PageUpButton"
+                              Classes="repeattrack"
+                              Focusable="False"/>
+              </Track.DecreaseButton>
+              <Track.IncreaseButton>
+                <RepeatButton Name="PART_PageDownButton"
+                              Classes="repeattrack"
+                              Focusable="False"/>
+              </Track.IncreaseButton>
+              <Thumb Name="thumb"/>
+            </Track>
+            <RepeatButton Name="PART_LineDownButton" VerticalAlignment="Center"
+                          Classes="repeat"
+                          Grid.Row="2"
+                          Grid.Column="2"
+                          Focusable="False"
+                          MinWidth="{DynamicResource ScrollBarThickness}">
+              <Path Data="M 0 0 L 4 4 L 0 8 Z"  />
+            </RepeatButton>
+          </Grid>
+        </Border>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+  <Style Selector="ScrollBar /template/ Thumb#thumb">
+    <Setter Property="Background" Value="{DynamicResource ThemeControlMidHighBrush}"/>
+    <Setter Property="Template">
+      <Setter.Value>
+        <ControlTemplate>
+          <Border Background="{TemplateBinding Background}"/>
+        </ControlTemplate>
+      </Setter.Value>
+    </Setter>
+  </Style>
+  <Style Selector="ScrollBar /template/ Thumb#thumb:pointerover">
+    <Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/>
+  </Style>
+  <Style Selector="ScrollBar /template/ Thumb#thumb:pressed">
+    <Setter Property="Background" Value="{DynamicResource ThemeControlVeryHighBrush}"/>
+  </Style>
+  <Style Selector="ScrollBar:horizontal /template/ Thumb#thumb">
+    <Setter Property="MinWidth" Value="{DynamicResource ScrollBarThickness}" />
+    <Setter Property="Height" Value="{DynamicResource ScrollBarThumbThickness}" />
+  </Style>
+  <Style Selector="ScrollBar:vertical">
+    <Setter Property="Width" Value="{DynamicResource ScrollBarThickness}" />
+  </Style>
+  <Style Selector="ScrollBar:vertical /template/ Thumb#thumb">
+    <Setter Property="MinHeight" Value="{DynamicResource ScrollBarThickness}" />
+    <Setter Property="Width" Value="{DynamicResource ScrollBarThumbThickness}" />
+  </Style>
+  <Style Selector="ScrollBar /template/ RepeatButton.repeat">
+    <Setter Property="Padding" Value="2" />
+    <Setter Property="BorderThickness" Value="0" />
+  </Style>
+  <Style Selector="ScrollBar /template/ RepeatButton.repeattrack">
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Border Background="{TemplateBinding Background}" />
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="ScrollBar /template/ RepeatButton > Path">
+    <Setter Property="Fill" Value="{DynamicResource ThemeForegroundLowBrush}" />
+  </Style>
+
+  <Style Selector="ScrollBar /template/ RepeatButton:pointerover > Path">
+    <Setter Property="Fill" Value="{DynamicResource ThemeAccentBrush}" />
+  </Style>
 </Styles>

+ 1 - 0
src/Avalonia.Themes.Default/ScrollViewer.xaml

@@ -36,6 +36,7 @@
                    Visibility="{TemplateBinding VerticalScrollBarVisibility}"
                    Grid.Column="1"
                    Focusable="False"/>
+        <Panel Grid.Row="1" Grid.Column="1" Background="{DynamicResource ThemeControlMidBrush}"/>
       </Grid>
     </ControlTemplate>
   </Setter>

+ 18 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@@ -148,6 +148,19 @@ namespace Avalonia.Rendering.SceneGraph
             return (VisualNode)node;
         }
 
+        private static object GetOrCreateChildNode(Scene scene, IVisual child, VisualNode parent)
+        {
+            var result = (VisualNode)scene.FindNode(child);
+
+            if (result != null && result.Parent != parent)
+            {
+                Deindex(scene, result);
+                result = null;
+            }
+
+            return result ?? CreateNode(scene, child, parent);
+        }
+
         private static void Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, bool forceRecurse)
         {
             var visual = node.Visual;
@@ -231,7 +244,7 @@ namespace Avalonia.Rendering.SceneGraph
                     {
                         foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance))
                         {
-                            var childNode = scene.FindNode(child) ?? CreateNode(scene, child, node);
+                            var childNode = GetOrCreateChildNode(scene, child, node);
                             Update(context, scene, (VisualNode)childNode, clip, forceRecurse);
                         }
 
@@ -240,6 +253,10 @@ namespace Avalonia.Rendering.SceneGraph
                     }
                 }
             }
+            else
+            {
+                contextImpl.BeginUpdate(node).Dispose();
+            }
         }
 
         private void UpdateSize(Scene scene)

+ 11 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@@ -119,6 +119,11 @@ namespace Avalonia.Rendering.SceneGraph
                 throw new ObjectDisposedException("Visual node for {node.Visual}");
             }
 
+            if (child.Parent != this)
+            {
+                throw new AvaloniaInternalException("VisualNode added to wrong parent.");
+            }
+
             EnsureChildrenCreated();
             _children.Add(child);
         }
@@ -155,6 +160,11 @@ namespace Avalonia.Rendering.SceneGraph
                 throw new ObjectDisposedException("Visual node for {node.Visual}");
             }
 
+            if (node.Parent != this)
+            {
+                throw new AvaloniaInternalException("VisualNode added to wrong parent.");
+            }
+
             EnsureChildrenCreated();
             _children[index] = node;
         }
@@ -218,7 +228,7 @@ namespace Avalonia.Rendering.SceneGraph
             if (first < _children?.Count)
             {
                 EnsureChildrenCreated();
-                for (int i = first; i < _children.Count - first; i++)
+                for (int i = first; i < _children.Count; i++)
                 {
                     _children[i].Dispose();
                 }

+ 2 - 2
src/Avalonia.X11/X11Window.cs

@@ -455,7 +455,7 @@ namespace Avalonia.X11
                     key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();
                 
                 
-                ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(),
+                ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot,
                     ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
                     X11KeyTransform.ConvertKey(key), TranslateModifiers(ev.KeyEvent.state)), ref ev);
 
@@ -470,7 +470,7 @@ namespace Avalonia.X11
                             if (text[0] < ' ' || text[0] == 0x7f) //Control codes or DEL
                                 return;
                         }
-                        ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), text),
+                        ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot, text),
                             ref ev);
                     }
                 }

+ 3 - 3
src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs

@@ -114,10 +114,10 @@ namespace Avalonia.Markup.Xaml.XamlIl
 
             InitializeSre();
             var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly);
-            
+
             var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_sreTypeSystem, asm,
-                _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter),
-                _sreContextType);
+                    _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter),
+                _sreContextType) { EnableIlVerification = true };
             var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
 
             IXamlIlType overrideType = null;

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

@@ -1 +1 @@
-Subproject commit c7155c5f6c1a5153ee2d8cd78e5d1524dd6744cf
+Subproject commit ad9915e19398a49c5a11b66000c361659ca692b3

+ 3 - 3
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@@ -212,17 +212,17 @@ namespace Avalonia.Win32.Interop.Wpf
         protected override void OnMouseLeave(MouseEventArgs e) => MouseEvent(RawPointerEventType.LeaveWindow, e);
 
         protected override void OnKeyDown(KeyEventArgs e)
-            => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint) e.Timestamp, RawKeyEventType.KeyDown,
+            => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint) e.Timestamp, _inputRoot, RawKeyEventType.KeyDown,
                 (Key) e.Key,
                 GetModifiers(null)));
 
         protected override void OnKeyUp(KeyEventArgs e)
-            => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint)e.Timestamp, RawKeyEventType.KeyUp,
+            => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint)e.Timestamp, _inputRoot, RawKeyEventType.KeyUp,
                 (Key)e.Key,
                 GetModifiers(null)));
 
         protected override void OnTextInput(TextCompositionEventArgs e) 
-            => _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, e.Text));
+            => _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, _inputRoot, e.Text));
 
         void ITopLevelImpl.SetCursor(IPlatformHandle cursor)
         {

+ 9 - 0
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -1031,6 +1031,9 @@ namespace Avalonia.Win32.Interop
         [DllImport("shcore.dll")]
         public static extern long GetDpiForMonitor(IntPtr hmonitor, MONITOR_DPI_TYPE dpiType, out uint dpiX, out uint dpiY);
 
+        [DllImport("gdi32.dll")]
+        public static extern int GetDeviceCaps(IntPtr hdc, DEVICECAP nIndex);
+
         [DllImport("shcore.dll")]
         public static extern void GetScaleFactorForMonitor(IntPtr hMon, out uint pScale);
 
@@ -1147,6 +1150,12 @@ namespace Avalonia.Win32.Interop
             }
         }
 
+        public enum DEVICECAP
+        {
+            HORZRES = 8,
+            DESKTOPHORZRES = 118
+        }
+
         public enum PROCESS_DPI_AWARENESS
         {
             PROCESS_DPI_UNAWARE = 0,

+ 2 - 2
src/Windows/Avalonia.Win32/OleDropTarget.cs

@@ -8,13 +8,13 @@ namespace Avalonia.Win32
 {
     class OleDropTarget : IDropTarget
     {
-        private readonly IInputElement _target;
+        private readonly IInputRoot _target;
         private readonly ITopLevelImpl _tl;
         private readonly IDragDropDevice _dragDevice;
         
         private IDataObject _currentDrag = null;
 
-        public OleDropTarget(ITopLevelImpl tl, IInputElement target)
+        public OleDropTarget(ITopLevelImpl tl, IInputRoot target)
         {
             _dragDevice = AvaloniaLocator.Current.GetService<IDragDropDevice>();
             _tl = tl;

+ 23 - 3
src/Windows/Avalonia.Win32/ScreenImpl.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using Avalonia.Platform;
+using Avalonia.Win32.Interop;
 using static Avalonia.Win32.Interop.UnmanagedMethods;
 
 namespace Avalonia.Win32
@@ -28,9 +29,28 @@ namespace Avalonia.Win32
                         (IntPtr monitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr data) =>
                         {
                             MONITORINFO monitorInfo = MONITORINFO.Create();
-                            if (GetMonitorInfo(monitor,ref monitorInfo))
+                            if (GetMonitorInfo(monitor, ref monitorInfo))
                             {
-                                GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var x, out _);
+                                var dpi = 1.0;
+
+                                var shcore = LoadLibrary("shcore.dll");
+                                var method = GetProcAddress(shcore, nameof(GetDpiForMonitor));
+                                if (method != IntPtr.Zero)
+                                { 
+                                    GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var x, out _);
+                                    dpi = (double)x;
+                                }
+                                else
+                                {
+                                    var hdc = GetDC(IntPtr.Zero);
+
+                                    double virtW = GetDeviceCaps(hdc, DEVICECAP.HORZRES);
+                                    double physW = GetDeviceCaps(hdc, DEVICECAP.DESKTOPHORZRES);
+
+                                    dpi = (96d * physW / virtW);
+
+                                    ReleaseDC(IntPtr.Zero, hdc);
+                                }
 
                                 RECT bounds = monitorInfo.rcMonitor;
                                 RECT workingArea = monitorInfo.rcWork;
@@ -40,7 +60,7 @@ namespace Avalonia.Win32
                                     new PixelRect(workingArea.left, workingArea.top, workingArea.right - workingArea.left,
                                         workingArea.bottom - workingArea.top);
                                 screens[index] =
-                                    new WinScreen((double)x / 96.0d, avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1,
+                                    new WinScreen(dpi / 96.0d, avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1,
                                         monitor);
                                 index++;
                             }

+ 3 - 1
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -508,6 +508,7 @@ namespace Avalonia.Win32
                     e = new RawKeyEventArgs(
                             WindowsKeyboardDevice.Instance,
                             timestamp,
+                            _owner,
                             RawKeyEventType.KeyDown,
                             KeyInterop.KeyFromVirtualKey(ToInt32(wParam)), WindowsKeyboardDevice.Instance.Modifiers);
                     break;
@@ -521,6 +522,7 @@ namespace Avalonia.Win32
                     e = new RawKeyEventArgs(
                             WindowsKeyboardDevice.Instance,
                             timestamp,
+                            _owner,
                             RawKeyEventType.KeyUp,
                             KeyInterop.KeyFromVirtualKey(ToInt32(wParam)), WindowsKeyboardDevice.Instance.Modifiers);
                     break;
@@ -528,7 +530,7 @@ namespace Avalonia.Win32
                     // Ignore control chars
                     if (ToInt32(wParam) >= 32)
                     {
-                        e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp,
+                        e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner,
                             new string((char)ToInt32(wParam), 1));
                     }
 

+ 12 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs

@@ -78,6 +78,18 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal(10, target.GetValue(Class1.AttachedProperty));
         }
 
+        [Fact]
+        public void PropertyChanged_Event_Uses_Coerced_Value()
+        {
+            var inst = new Class1();
+            inst.PropertyChanged += (sender, e) =>
+            {
+                Assert.Equal(10, e.NewValue);
+            };
+
+            inst.SetValue(Class1.QuxProperty, 15);
+        }
+
         private class Class1 : AvaloniaObject
         {
             public static readonly StyledProperty<int> QuxProperty =

+ 9 - 9
tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Controls.UnitTests
         public void Should_Set_ExitCode_After_Shutdown()
         {
             using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))    
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())    
             {
                 lifetime.Shutdown(1337);
 
@@ -31,7 +31,7 @@ namespace Avalonia.Controls.UnitTests
         public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var windows = new List<Window> { new Window(), new Window(), new Window(), new Window() };
 
@@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
         public void Should_Only_Exit_On_Explicit_Exit()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 lifetime.ShutdownMode = ShutdownMode.OnExplicitShutdown;
 
@@ -84,7 +84,7 @@ namespace Avalonia.Controls.UnitTests
         public void Should_Exit_After_MainWindow_Closed()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose;
 
@@ -112,7 +112,7 @@ namespace Avalonia.Controls.UnitTests
         public void Should_Exit_After_Last_Window_Closed()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 lifetime.ShutdownMode = ShutdownMode.OnLastWindowClose;
 
@@ -142,7 +142,7 @@ namespace Avalonia.Controls.UnitTests
         public void Show_Should_Add_Window_To_OpenWindows()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var window = new Window();
 
@@ -156,7 +156,7 @@ namespace Avalonia.Controls.UnitTests
         public void Window_Should_Be_Added_To_OpenWindows_Only_Once()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var window = new Window();
 
@@ -174,7 +174,7 @@ namespace Avalonia.Controls.UnitTests
         public void Close_Should_Remove_Window_From_OpenWindows()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var window = new Window();
 
@@ -197,7 +197,7 @@ namespace Avalonia.Controls.UnitTests
                 windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));
 
             using (UnitTestApplication.Start(services))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var window = new Window();
 

+ 277 - 9
tests/Avalonia.Controls.UnitTests/GridTests.cs

@@ -1,11 +1,6 @@
+using System;
 using System.Collections.Generic;
 using System.Linq;
-using Avalonia.Controls.Primitives;
-using Avalonia.Input;
-using Avalonia.Platform;
-using Avalonia.UnitTests;
-
-using Moq;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -34,7 +29,6 @@ namespace Avalonia.Controls.UnitTests
 
         private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns)
         {
-
             var grid = new Grid();
             foreach (var k in columns.Select(c => new ColumnDefinition
             {
@@ -1178,6 +1172,41 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth);
         }
 
+        [Fact]
+        public void Size_Group_Definition_Resizes_Are_Tracked()
+        {
+            var grids = new[] {
+                CreateGrid(("A", new GridLength(5, GridUnitType.Pixel)), (null, new GridLength())),
+                CreateGrid(("A", new GridLength(5, GridUnitType.Pixel)), (null, new GridLength())) };
+            var scope = new Grid();
+            foreach (var xgrids in grids)
+                scope.Children.Add(xgrids);
+
+            var root = new Grid();
+            root.UseLayoutRounding = false;
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+            root.Children.Add(scope);
+
+            root.Measure(new Size(50, 50));
+            root.Arrange(new Rect(new Point(), new Point(50, 50)));
+
+            PrintColumnDefinitions(grids[0]);
+            Assert.Equal(5, grids[0].ColumnDefinitions[0].ActualWidth);
+            Assert.Equal(5, grids[1].ColumnDefinitions[0].ActualWidth);
+
+            grids[0].ColumnDefinitions[0].Width = new GridLength(10, GridUnitType.Pixel);
+
+            foreach (Grid grid in grids)
+            {
+                grid.Measure(new Size(50, 50));
+                grid.Arrange(new Rect(new Point(), new Point(50, 50)));
+            }
+
+            PrintColumnDefinitions(grids[0]);
+            Assert.Equal(10, grids[0].ColumnDefinitions[0].ActualWidth);
+            Assert.Equal(10, grids[1].ColumnDefinitions[0].ActualWidth);
+        }
+
         [Fact]
         public void Collection_Changes_Are_Tracked()
         {
@@ -1270,11 +1299,11 @@ namespace Avalonia.Controls.UnitTests
             // grid.Measure(new Size(100, 100));
             // grid.Arrange(new Rect(new Point(), new Point(100, 100)));
             // PrintColumnDefinitions(grid);
-            
+
             // NOTE: THIS IS BROKEN IN WPF
             // all in group are equal to width (MinWidth) of the sizer in the second column
             // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 1 * 6, cd.ActualWidth));
-            
+
             // NOTE: THIS IS BROKEN IN WPF
             // grid.ColumnDefinitions[2].SharedSizeGroup = null;
 
@@ -1382,6 +1411,245 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(new Size(100, 100), grid.Bounds.Size);
         }
 
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Column_Width_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.ColumnDefinitions[0][ColumnDefinition.WidthProperty] = new GridLength(5);
+                else
+                    grid.ColumnDefinitions[0].Width = new GridLength(5);
+            });
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Column_MinWidth_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.ColumnDefinitions[0][ColumnDefinition.MinWidthProperty] = 5;
+                else
+                    grid.ColumnDefinitions[0].MinWidth = 5;
+            });
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Column_MaxWidth_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.ColumnDefinitions[0][ColumnDefinition.MaxWidthProperty] = 5;
+                else
+                    grid.ColumnDefinitions[0].MaxWidth = 5;
+            });
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Row_Height_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.RowDefinitions[0][RowDefinition.HeightProperty] = new GridLength(5);
+                else
+                    grid.RowDefinitions[0].Height = new GridLength(5);
+            });
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Row_MinHeight_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.RowDefinitions[0][RowDefinition.MinHeightProperty] = 5;
+                else
+                    grid.RowDefinitions[0].MinHeight = 5;
+            });
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Row_MaxHeight_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.RowDefinitions[0][RowDefinition.MaxHeightProperty] = 5;
+                else
+                    grid.RowDefinitions[0].MaxHeight = 5;
+            });
+        }
+
+        [Fact]
+        public void Adding_Column_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(5)));
+            });
+        }
+
+        [Fact]
+        public void Adding_Row_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.RowDefinitions.Add(new RowDefinition(new GridLength(5)));
+            });
+        }
+
+        [Fact]
+        public void Replacing_Columns_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.ColumnDefinitions = ColumnDefinitions.Parse("2*,1*");
+            });
+        }
+
+        [Fact]
+        public void Replacing_Rows_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.RowDefinitions = RowDefinitions.Parse("2*,1*");
+            });
+        }
+
+        [Fact]
+        public void Removing_Column_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.ColumnDefinitions.RemoveAt(0);
+            });
+        }
+
+        [Fact]
+        public void Removing_Row_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.RowDefinitions.RemoveAt(0);
+            });
+        }
+
+        [Fact]
+        public void Removing_Child_Should_Invalidate_Grid_And_Be_Operational()
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("*,Auto") };
+
+            grid.Children.Add(new Decorator() { [Grid.ColumnProperty] = 0 });
+            grid.Children.Add(new Decorator() { Width = 10, Height = 10, [Grid.ColumnProperty] = 1 });
+
+            var size = new Size(100, 100);
+            grid.Measure(size);
+            grid.Arrange(new Rect(size));
+
+            Assert.True(grid.IsMeasureValid);
+            Assert.True(grid.IsArrangeValid);
+
+            Assert.Equal(90, grid.Children[0].Bounds.Width);
+            Assert.Equal(10, grid.Children[1].Bounds.Width);
+
+            grid.Children.RemoveAt(1);
+
+            Assert.False(grid.IsMeasureValid);
+            Assert.False(grid.IsArrangeValid);
+
+            grid.Measure(size);
+            grid.Arrange(new Rect(size));
+
+            Assert.True(grid.IsMeasureValid);
+            Assert.True(grid.IsArrangeValid);
+
+            Assert.Equal(100, grid.Children[0].Bounds.Width);
+        }
+
+        [Fact]
+        public void Adding_Child_Should_Invalidate_Grid_And_Be_Operational()
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("*,Auto") };
+
+            grid.Children.Add(new Decorator() { [Grid.ColumnProperty] = 0 });
+
+            var size = new Size(100, 100);
+            grid.Measure(size);
+            grid.Arrange(new Rect(size));
+
+            Assert.True(grid.IsMeasureValid);
+            Assert.True(grid.IsArrangeValid);
+
+            Assert.Equal(100, grid.Children[0].Bounds.Width);
+
+            grid.Children.Add(new Decorator() { Width = 10, Height = 10, [Grid.ColumnProperty] = 1 });
+
+            Assert.False(grid.IsMeasureValid);
+            Assert.False(grid.IsArrangeValid);
+
+            grid.Measure(size);
+            grid.Arrange(new Rect(size));
+
+            Assert.True(grid.IsMeasureValid);
+            Assert.True(grid.IsArrangeValid);
+
+            Assert.Equal(90, grid.Children[0].Bounds.Width);
+            Assert.Equal(10, grid.Children[1].Bounds.Width);
+        }
+
+        private static void Change_Property_And_Verify_Measure_Requested(Grid grid, Action change)
+        {
+            grid.Measure(new Size(100, 100));
+            grid.Arrange(new Rect(grid.DesiredSize));
+
+            Assert.True(grid.IsMeasureValid);
+            Assert.True(grid.IsArrangeValid);
+
+            change();
+
+            Assert.False(grid.IsMeasureValid);
+            Assert.False(grid.IsArrangeValid);
+        }
+
         private class TestControl : Control
         {
             public Size MeasureSize { get; set; }

+ 28 - 0
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@@ -1000,6 +1000,26 @@ namespace Avalonia.Controls.UnitTests.Primitives
             Assert.Equal(new[] { "Bar" }, selectedItems);
         }
 
+        [Fact]
+        public void MoveSelection_Wrap_Does_Not_Hang_With_No_Focusable_Controls()
+        {
+            // Issue #3094.
+            var target = new TestSelector
+            {
+                Template = Template(),
+                Items = new[]
+                {
+                    new ListBoxItem { Focusable = false },
+                    new ListBoxItem { Focusable = false },
+                },
+                SelectedIndex = 0,
+            };
+
+            target.Measure(new Size(100, 100));
+            target.Arrange(new Rect(0, 0, 100, 100));
+            target.MoveSelection(NavigationDirection.Next, true);
+        }
+
         private FuncControlTemplate Template()
         {
             return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
@@ -1044,5 +1064,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
             public List<string> Items { get; set; } = new List<string>() { "a", "b", "c", "d", "e" };
             public string Selected { get; set; } = "b";
         }
+
+        private class TestSelector : SelectingItemsControl
+        {
+            public new bool MoveSelection(NavigationDirection direction, bool wrap)
+            {
+                return base.MoveSelection(direction, wrap);
+            }
+        }
     }
 }

+ 1 - 0
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@@ -182,6 +182,7 @@ namespace Avalonia.Controls.UnitTests
                 var input = new RawKeyEventArgs(
                     new Mock<IKeyboardDevice>().Object,
                     0,
+                    target,
                     RawKeyEventType.KeyDown,
                     Key.A, RawInputModifiers.None);
                 impl.Object.Input(input);

+ 90 - 0
tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs

@@ -0,0 +1,90 @@
+using Avalonia.Input.Raw;
+using Avalonia.Interactivity;
+using Moq;
+using Xunit;
+
+namespace Avalonia.Input.UnitTests
+{
+    public class KeyboardDeviceTests
+    {
+        [Fact]
+        public void Keypresses_Should_Be_Sent_To_Root_If_No_Focused_Element()
+        {
+            var target = new KeyboardDevice();
+            var root = new Mock<IInputRoot>();
+
+            target.ProcessRawEvent(
+                new RawKeyEventArgs(
+                    target,
+                    0,
+                    root.Object,
+                    RawKeyEventType.KeyDown,
+                    Key.A,
+                    RawInputModifiers.None));
+
+            root.Verify(x => x.RaiseEvent(It.IsAny<KeyEventArgs>()));
+        }
+
+        [Fact]
+        public void Keypresses_Should_Be_Sent_To_Focused_Element()
+        {
+            var target = new KeyboardDevice();
+            var focused = new Mock<IInputElement>();
+            var root = Mock.Of<IInputRoot>();
+
+            target.SetFocusedElement(
+                focused.Object,
+                NavigationMethod.Unspecified,
+                InputModifiers.None);
+
+            target.ProcessRawEvent(
+                new RawKeyEventArgs(
+                    target,
+                    0,
+                    root,
+                    RawKeyEventType.KeyDown,
+                    Key.A,
+                    RawInputModifiers.None));
+
+            focused.Verify(x => x.RaiseEvent(It.IsAny<KeyEventArgs>()));
+        }
+
+        [Fact]
+        public void TextInput_Should_Be_Sent_To_Root_If_No_Focused_Element()
+        {
+            var target = new KeyboardDevice();
+            var root = new Mock<IInputRoot>();
+
+            target.ProcessRawEvent(
+                new RawTextInputEventArgs(
+                    target,
+                    0,
+                    root.Object,
+                    "Foo"));
+
+            root.Verify(x => x.RaiseEvent(It.IsAny<TextInputEventArgs>()));
+        }
+
+        [Fact]
+        public void TextInput_Should_Be_Sent_To_Focused_Element()
+        {
+            var target = new KeyboardDevice();
+            var focused = new Mock<IInputElement>();
+            var root = Mock.Of<IInputRoot>();
+
+            target.SetFocusedElement(
+                focused.Object,
+                NavigationMethod.Unspecified,
+                InputModifiers.None);
+
+            target.ProcessRawEvent(
+                new RawTextInputEventArgs(
+                    target,
+                    0,
+                    root,
+                    "Foo"));
+
+            focused.Verify(x => x.RaiseEvent(It.IsAny<TextInputEventArgs>()));
+        }
+    }
+}

+ 3 - 3
tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs

@@ -45,7 +45,7 @@ namespace Avalonia.ReactiveUI.UnitTests
         public void AutoSuspendHelper_Should_Immediately_Fire_IsLaunchingNew() 
         {
             using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) 
-            using (var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using (var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var isLaunchingReceived = false;
                 var application = AvaloniaLocator.Current.GetService<Application>();
@@ -86,7 +86,7 @@ namespace Avalonia.ReactiveUI.UnitTests
         public void ShouldPersistState_Should_Fire_On_App_Exit_When_SuspensionDriver_Is_Initialized() 
         {
             using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
-            using (var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) 
+            using (var lifetime = new ClassicDesktopStyleApplicationLifetime()) 
             {
                 var shouldPersistReceived = false;
                 var application = AvaloniaLocator.Current.GetService<Application>();
@@ -105,4 +105,4 @@ namespace Avalonia.ReactiveUI.UnitTests
             }
         }
     }
-}
+}

+ 6 - 6
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
         public void Should_Add_VisualNode()
         {
             var parent = new VisualNode(new TestRoot(), null);
-            var child = new VisualNode(Mock.Of<IVisual>(), null);
+            var child = new VisualNode(Mock.Of<IVisual>(), parent);
             var layers = new SceneLayers(parent.Visual);
             var target = new DeferredDrawingContextImpl(null, layers);
 
@@ -32,7 +32,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
         public void Should_Not_Replace_Identical_VisualNode()
         {
             var parent = new VisualNode(new TestRoot(), null);
-            var child = new VisualNode(Mock.Of<IVisual>(), null);
+            var child = new VisualNode(Mock.Of<IVisual>(), parent);
             var layers = new SceneLayers(parent.Visual);
 
             parent.AddChild(child);
@@ -50,8 +50,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
         public void Should_Replace_Different_VisualNode()
         {
             var parent = new VisualNode(new TestRoot(), null);
-            var child1 = new VisualNode(Mock.Of<IVisual>(), null);
-            var child2 = new VisualNode(Mock.Of<IVisual>(), null);
+            var child1 = new VisualNode(Mock.Of<IVisual>(), parent);
+            var child2 = new VisualNode(Mock.Of<IVisual>(), parent);
             var layers = new SceneLayers(parent.Visual);
 
             parent.AddChild(child1);
@@ -78,8 +78,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
 
             var layers = new SceneLayers(root);
             var target = new DeferredDrawingContextImpl(null, layers);
-            var child1 = new VisualNode(Mock.Of<IVisual>(), null) { LayerRoot = root };
-            var child2 = new VisualNode(Mock.Of<IVisual>(), null) { LayerRoot = root };
+            var child1 = new VisualNode(Mock.Of<IVisual>(), node) { LayerRoot = root };
+            var child2 = new VisualNode(Mock.Of<IVisual>(), node) { LayerRoot = root };
 
             target.BeginUpdate(node);
             using (target.BeginUpdate(child1)) { }

+ 52 - 0
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@@ -577,6 +577,58 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
             }
         }
 
+        [Fact]
+        public void Should_Not_Dispose_Active_VisualNode_When_Control_Reparented_And_Child_Made_Invisible()
+        {
+            // Issue #3115
+            using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+            {
+                StackPanel panel;
+                Border border1;
+                Border border2;
+                var tree = new TestRoot
+                {
+                    Width = 100,
+                    Height = 100,
+                    Child = panel = new StackPanel
+                    {
+                        Children =
+                        {
+                            (border1 = new Border
+                            {
+                                Background = Brushes.Red,
+                            }),
+                            (border2 = new Border
+                            {
+                                Background = Brushes.Green,
+                            }),
+                        }
+                    }
+                };
+
+                tree.Measure(Size.Infinity);
+                tree.Arrange(new Rect(tree.DesiredSize));
+
+                var scene = new Scene(tree);
+                var sceneBuilder = new SceneBuilder();
+                sceneBuilder.UpdateAll(scene);
+
+                var decorator = new Decorator();
+                tree.Child = null;
+                decorator.Child = panel;
+                tree.Child = decorator;
+                border1.IsVisible = false;
+
+                scene = scene.CloneScene();
+                sceneBuilder.Update(scene, decorator);
+
+                var panelNode = (VisualNode)scene.FindNode(panel);
+                Assert.Equal(2, panelNode.Children.Count);
+                Assert.False(panelNode.Children[0].Disposed);
+                Assert.False(panelNode.Children[1].Disposed);
+            }
+        }
+
         [Fact]
         public void Should_Update_ClipBounds_For_Negative_Margin()
         {

+ 20 - 1
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs

@@ -24,7 +24,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
             var node = new VisualNode(Mock.Of<IVisual>(), null);
             var collection = node.Children;
 
-            node.AddChild(Mock.Of<IVisualNode>());
+            node.AddChild(Mock.Of<IVisualNode>(x => x.Parent == node));
 
             Assert.NotSame(collection, node.Children);
         }
@@ -101,5 +101,24 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
 
             node.SortChildren(scene);
         }
+
+        [Fact]
+        public void TrimChildren_Should_Work_Correctly()
+        {
+            var parent = new VisualNode(Mock.Of<IVisual>(), null);
+            var child1 = new VisualNode(Mock.Of<IVisual>(), parent);
+            var child2 = new VisualNode(Mock.Of<IVisual>(), parent);
+            var child3 = new VisualNode(Mock.Of<IVisual>(), parent);
+
+            parent.AddChild(child1);
+            parent.AddChild(child2);
+            parent.AddChild(child3);
+            parent.TrimChildren(2);
+
+            Assert.Equal(2, parent.Children.Count);
+            Assert.False(child1.Disposed);
+            Assert.False(child2.Disposed);
+            Assert.True(child3.Disposed);
+        }
     }
 }