diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000000..dc57a73f48
--- /dev/null
+++ b/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 steven@avaloniaui.net. 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
diff --git a/build/Base.props b/build/Base.props
index a60373ebb3..100c9088cd 100644
--- a/build/Base.props
+++ b/build/Base.props
@@ -1,5 +1,6 @@
+
diff --git a/build/BuildTargets.targets b/build/BuildTargets.targets
index 08ec039ec7..a5543cd050 100644
--- a/build/BuildTargets.targets
+++ b/build/BuildTargets.targets
@@ -2,6 +2,7 @@
$(MSBuildThisFileDirectory)\..\src\Avalonia.Build.Tasks\bin\$(Configuration)\netstandard2.0\Avalonia.Build.Tasks.dll
true
+ true
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 9c7e9323e0..2a5acaea91 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/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)
{
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index a455e2b3fc..552713f94b 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -53,6 +53,7 @@
$(IntermediateOutputPath)/Avalonia/references
$(IntermediateOutputPath)/Avalonia/original.dll
+ false
screenshot). 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 NuGet 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
diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs
index 1bdbd4ca7c..e06c5996c9 100644
--- a/src/Avalonia.Base/ValueStore.cs
+++ b/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 GetDeferredSetter(AvaloniaProperty property)
diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
index 61303dbbfe..39ee3f6bca 100644
--- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
+++ b/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; }
diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs
index d356b15408..1909c4c6ec 100644
--- a/src/Avalonia.Build.Tasks/Program.cs
+++ b/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;
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
index 0dd52c9b48..e348eb0fbc 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+++ b/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"))
diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs
index d9be9171ed..3b644191c2 100644
--- a/src/Avalonia.Controls/AppBuilderBase.cs
+++ b/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());
}
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
index 2533191ae4..6dd5b8cc81 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
+++ b/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, new()
{
- var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode};
+ var lifetime = new ClassicDesktopStyleApplicationLifetime() {ShutdownMode = shutdownMode};
builder.SetupWithLifetime(lifetime);
return lifetime.Start(args);
}
diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
index ce4358648b..64db832a81 100644
--- a/src/Avalonia.Controls/AutoCompleteBox.cs
+++ b/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));
}
///
diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs
index 9c520c434e..293b6326d6 100644
--- a/src/Avalonia.Controls/ColumnDefinition.cs
+++ b/src/Avalonia.Controls/ColumnDefinition.cs
@@ -26,6 +26,16 @@ namespace Avalonia.Controls
public static readonly StyledProperty WidthProperty =
AvaloniaProperty.Register(nameof(Width), new GridLength(1, GridUnitType.Star));
+ ///
+ /// Initializes static members of the class.
+ ///
+ static ColumnDefinition()
+ {
+ AffectsParentMeasure(MinWidthProperty, MaxWidthProperty);
+
+ WidthProperty.Changed.AddClassHandler(OnUserSizePropertyChanged);
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -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);
}
}
diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs
index a68fe1265f..e4ae777453 100644
--- a/src/Avalonia.Controls/DefinitionBase.cs
+++ b/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();
}
///
@@ -63,6 +62,8 @@ namespace Avalonia.Controls
_sharedState.RemoveMember(this);
_sharedState = null;
}
+
+ Parent?.InvalidateMeasure();
}
///
@@ -114,6 +115,36 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Notifies parent or size scope that definition size has been changed.
+ ///
+ 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();
+ }
+ }
+ }
+
///
/// Returns true if this definition is a part of shared group.
///
@@ -730,5 +761,22 @@ namespace Avalonia.Controls
SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged);
PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged);
}
+
+ ///
+ /// Marks a property on a definition as affecting the parent grid's measurement.
+ ///
+ /// The properties.
+ 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);
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/DesktopApplicationExtensions.cs b/src/Avalonia.Controls/DesktopApplicationExtensions.cs
index ff6705cdc0..ddd4e57a40 100644
--- a/src/Avalonia.Controls/DesktopApplicationExtensions.cs
+++ b/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.
///
+ /// The application.
/// The token to track.
public static void Run(this Application app, CancellationToken token)
{
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index 4f41b0bf1e..23c1cd4794 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/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(DefinitionBase.OnIsSharedSizeScopePropertyChanged);
ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged);
- ColumnProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged);
- ColumnSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged);
- RowProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged);
- RowSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged);
+ IsSharedSizeScopeProperty.Changed.AddClassHandler(DefinitionBase.OnIsSharedSizeScopePropertyChanged);
+ ColumnProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged);
+ ColumnSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged);
+ RowProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged);
+ RowSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged);
+
AffectsParentMeasure(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);
}
+ ///
+ ///
+ ///
+ protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ CellsStructureDirty = true;
+ base.ChildrenChanged(sender, e);
+ }
+
///
/// Invalidates grid caches and makes the grid dirty for measure.
///
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 558f496ede..b027da6d0c 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/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;
}
diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
index b5dbd1e668..97aeead608 100644
--- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
+++ b/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);
diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs
index 6eb056bb32..9d644aaa00 100644
--- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs
+++ b/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)
{
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index 74a6d288f4..caaf58b25b 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -41,6 +41,8 @@ namespace Avalonia.Controls.Primitives
///
/// Initializes a new instance of the class.
///
+ /// The popup parent.
+ /// The popup implementation.
///
/// The dependency resolver to use. If null the default dependency resolver will be used.
///
diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs
index 7e9680dc9f..9f5ddb666c 100644
--- a/src/Avalonia.Controls/Primitives/Thumb.cs
+++ b/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");
}
}
}
diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
index be24f22e20..9d70fbcf31 100644
--- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
+++ b/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);
}
diff --git a/src/Avalonia.Controls/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs
index 1f2f738670..85e7ed6519 100644
--- a/src/Avalonia.Controls/RowDefinition.cs
+++ b/src/Avalonia.Controls/RowDefinition.cs
@@ -26,6 +26,16 @@ namespace Avalonia.Controls
public static readonly StyledProperty HeightProperty =
AvaloniaProperty.Register(nameof(Height), new GridLength(1, GridUnitType.Star));
+ ///
+ /// Initializes static members of the class.
+ ///
+ static RowDefinition()
+ {
+ AffectsParentMeasure(MaxHeightProperty, MinHeightProperty);
+
+ HeightProperty.Changed.AddClassHandler(OnUserSizePropertyChanged);
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -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);
}
}
diff --git a/src/Avalonia.Input/DragDropDevice.cs b/src/Avalonia.Input/DragDropDevice.cs
index 0b9f09b224..25d3b6887f 100644
--- a/src/Avalonia.Input/DragDropDevice.cs
+++ b/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()?.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 routedEvent, DragDropEffects operation, IDataObject data, InputModifiers modifiers)
+ private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent 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;
}
}
diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs
index a02c580ad2..9db8525591 100644
--- a/src/Avalonia.Input/KeyboardDevice.cs
+++ b/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;
}
}
}
diff --git a/src/Avalonia.Input/Raw/RawDragEvent.cs b/src/Avalonia.Input/Raw/RawDragEvent.cs
index 1480f92f03..5722e17593 100644
--- a/src/Avalonia.Input/Raw/RawDragEvent.cs
+++ b/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;
diff --git a/src/Avalonia.Input/Raw/RawInputEventArgs.cs b/src/Avalonia.Input/Raw/RawInputEventArgs.cs
index 78c1b58624..6a488c4cc7 100644
--- a/src/Avalonia.Input/Raw/RawInputEventArgs.cs
+++ b/src/Avalonia.Input/Raw/RawInputEventArgs.cs
@@ -21,12 +21,14 @@ namespace Avalonia.Input.Raw
///
/// The associated device.
/// The event timestamp.
- public RawInputEventArgs(IInputDevice device, ulong timestamp)
+ /// The root from which the event originates.
+ public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root)
{
Contract.Requires(device != null);
Device = device;
Timestamp = timestamp;
+ Root = root;
}
///
@@ -34,6 +36,11 @@ namespace Avalonia.Input.Raw
///
public IInputDevice Device { get; }
+ ///
+ /// Gets the root from which the event originates.
+ ///
+ public IInputRoot Root { get; }
+
///
/// Gets or sets a value indicating whether the event was handled.
///
diff --git a/src/Avalonia.Input/Raw/RawKeyEventArgs.cs b/src/Avalonia.Input/Raw/RawKeyEventArgs.cs
index cd8b2eacf7..c720cf11f8 100644
--- a/src/Avalonia.Input/Raw/RawKeyEventArgs.cs
+++ b/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;
diff --git a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs
index 88f6daf11f..56854c7d29 100644
--- a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs
+++ b/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(device != null);
Contract.Requires(root != null);
- Root = root;
Position = position;
Type = type;
InputModifiers = inputModifiers;
}
- ///
- /// Gets the root from which the event originates.
- ///
- public IInputRoot Root { get; }
-
///
/// Gets the mouse position, in client DIPs.
///
diff --git a/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs b/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs
index 0d1e5d2422..010342da15 100644
--- a/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs
+++ b/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; }
}
}
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 209e1bc7ea..fe7458d583 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/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);
diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml
index 0ed17fae76..ffe3e92202 100644
--- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml
+++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml
@@ -14,7 +14,9 @@
#FFA0A0A0
#FF282828
#FF505050
+ #FF686868
#FF808080
+ #FFEFEBEF
#FFA8A8A8
#FF828282
#FF505050
@@ -32,7 +34,9 @@
+
+
@@ -61,6 +65,7 @@
12
16
- 10
+ 18
+ 8
diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml
index 3a8a8ec446..c0e5f47eed 100644
--- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml
+++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml
@@ -12,9 +12,14 @@
#FFAAAAAA
#FF888888
#FF333333
- #FFFFFFFF
- #FFAAAAAA
- #FF888888
+
+
+ #FF868999
+ #FFF5F5F5
+ #FFC2C3C9
+ #FF686868
+ #FF5B5B5B
+
#FFF0F0F0
#FFD0D0D0
#FF808080
@@ -32,7 +37,9 @@
+
+
@@ -61,6 +68,7 @@
12
16
- 10
+ 18
+ 8
diff --git a/src/Avalonia.Themes.Default/ContextMenu.xaml b/src/Avalonia.Themes.Default/ContextMenu.xaml
index 53d7c5abb4..75f8f7c23d 100644
--- a/src/Avalonia.Themes.Default/ContextMenu.xaml
+++ b/src/Avalonia.Themes.Default/ContextMenu.xaml
@@ -11,19 +11,11 @@
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
-
-
-
diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml
index a794d15577..93989d3782 100644
--- a/src/Avalonia.Themes.Default/MenuItem.xaml
+++ b/src/Avalonia.Themes.Default/MenuItem.xaml
@@ -4,14 +4,14 @@
-
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml
index c9552a607c..64a4399d16 100644
--- a/src/Avalonia.Themes.Default/ScrollBar.xaml
+++ b/src/Avalonia.Themes.Default/ScrollBar.xaml
@@ -1,128 +1,140 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Default/ScrollViewer.xaml b/src/Avalonia.Themes.Default/ScrollViewer.xaml
index 3e130cad67..38f4eef964 100644
--- a/src/Avalonia.Themes.Default/ScrollViewer.xaml
+++ b/src/Avalonia.Themes.Default/ScrollViewer.xaml
@@ -36,6 +36,7 @@
Visibility="{TemplateBinding VerticalScrollBarVisibility}"
Grid.Column="1"
Focusable="False"/>
+
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
index 161cbc099e..558e96e132 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
+++ b/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)
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
index f579bf0a62..d2a9e0a673 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
+++ b/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();
}
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 2630f9cf96..17471fad10 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/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);
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
index b91d679fba..5a5da518d0 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
+++ b/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;
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
index c7155c5f6c..ad9915e193 160000
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
@@ -1 +1 @@
-Subproject commit c7155c5f6c1a5153ee2d8cd78e5d1524dd6744cf
+Subproject commit ad9915e19398a49c5a11b66000c361659ca692b3
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
index 71c398481b..2887468046 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
+++ b/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)
{
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index d37bec3334..2c6425e26c 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/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,
diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs
index 8ac0f25598..b17e0d6c09 100644
--- a/src/Windows/Avalonia.Win32/OleDropTarget.cs
+++ b/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();
_tl = tl;
diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs
index e77aa07bcd..df4cf7fa19 100644
--- a/src/Windows/Avalonia.Win32/ScreenImpl.cs
+++ b/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++;
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 4f4c9852e8..04a9303d53 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/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));
}
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs
index 2cf1eb1a97..f0e93dbb3a 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs
+++ b/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 QuxProperty =
diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
index 74523d4193..dee7a84812 100644
--- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
+++ b/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 { 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();
diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs
index 2b9197e20b..353bb9c98d 100644
--- a/tests/Avalonia.Controls.UnitTests/GridTests.cs
+++ b/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; }
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index d5237e2aca..17f0e609a5 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/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((control, scope) =>
@@ -1044,5 +1064,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
public List Items { get; set; } = new List() { "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);
+ }
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
index 645f87163a..72c81659c3 100644
--- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
@@ -182,6 +182,7 @@ namespace Avalonia.Controls.UnitTests
var input = new RawKeyEventArgs(
new Mock().Object,
0,
+ target,
RawKeyEventType.KeyDown,
Key.A, RawInputModifiers.None);
impl.Object.Input(input);
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
new file mode 100644
index 0000000000..3c8e800fca
--- /dev/null
+++ b/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();
+
+ target.ProcessRawEvent(
+ new RawKeyEventArgs(
+ target,
+ 0,
+ root.Object,
+ RawKeyEventType.KeyDown,
+ Key.A,
+ RawInputModifiers.None));
+
+ root.Verify(x => x.RaiseEvent(It.IsAny()));
+ }
+
+ [Fact]
+ public void Keypresses_Should_Be_Sent_To_Focused_Element()
+ {
+ var target = new KeyboardDevice();
+ var focused = new Mock();
+ var root = Mock.Of();
+
+ 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()));
+ }
+
+ [Fact]
+ public void TextInput_Should_Be_Sent_To_Root_If_No_Focused_Element()
+ {
+ var target = new KeyboardDevice();
+ var root = new Mock();
+
+ target.ProcessRawEvent(
+ new RawTextInputEventArgs(
+ target,
+ 0,
+ root.Object,
+ "Foo"));
+
+ root.Verify(x => x.RaiseEvent(It.IsAny()));
+ }
+
+ [Fact]
+ public void TextInput_Should_Be_Sent_To_Focused_Element()
+ {
+ var target = new KeyboardDevice();
+ var focused = new Mock();
+ var root = Mock.Of();
+
+ target.SetFocusedElement(
+ focused.Object,
+ NavigationMethod.Unspecified,
+ InputModifiers.None);
+
+ target.ProcessRawEvent(
+ new RawTextInputEventArgs(
+ target,
+ 0,
+ root,
+ "Foo"));
+
+ focused.Verify(x => x.RaiseEvent(It.IsAny()));
+ }
+ }
+}
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs
index 56b14c3936..c263a80ef3 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs
+++ b/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();
@@ -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();
@@ -105,4 +105,4 @@ namespace Avalonia.ReactiveUI.UnitTests
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
index 4d23c57eed..f57c73c45c 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
+++ b/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(), null);
+ var child = new VisualNode(Mock.Of(), 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(), null);
+ var child = new VisualNode(Mock.Of(), 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(), null);
- var child2 = new VisualNode(Mock.Of(), null);
+ var child1 = new VisualNode(Mock.Of(), parent);
+ var child2 = new VisualNode(Mock.Of(), 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(), null) { LayerRoot = root };
- var child2 = new VisualNode(Mock.Of(), null) { LayerRoot = root };
+ var child1 = new VisualNode(Mock.Of(), node) { LayerRoot = root };
+ var child2 = new VisualNode(Mock.Of(), node) { LayerRoot = root };
target.BeginUpdate(node);
using (target.BeginUpdate(child1)) { }
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
index 13bcd27240..327ef98c4d 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
+++ b/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()
{
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs
index 24ba2d1c48..4ec3630053 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs
@@ -24,7 +24,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var node = new VisualNode(Mock.Of(), null);
var collection = node.Children;
- node.AddChild(Mock.Of());
+ node.AddChild(Mock.Of(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(), null);
+ var child1 = new VisualNode(Mock.Of(), parent);
+ var child2 = new VisualNode(Mock.Of(), parent);
+ var child3 = new VisualNode(Mock.Of(), 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);
+ }
}
}