diff --git a/packages.cake b/packages.cake
index d633230189..9defa3004c 100644
--- a/packages.cake
+++ b/packages.cake
@@ -303,7 +303,7 @@ public class Packages
{
new NuSpecContent { Source = "Avalonia.Android.dll", Target = "lib/MonoAndroid10" }
},
- BasePath = context.Directory("./src/Android/Avalonia.Android/bin/" + parameters.DirSuffix + "/monoandroid44/"),
+ BasePath = context.Directory("./src/Android/Avalonia.Android/bin/" + parameters.DirSuffix + "/monoandroid44/MonoAndroid44/"),
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
diff --git a/readme.md b/readme.md
index 345ad7fe9b..f345cbd9df 100644
--- a/readme.md
+++ b/readme.md
@@ -35,6 +35,9 @@ Install-Package Avalonia.Desktop
Try out the latest build of Avalonia available for download here:
https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master/artifacts
+or use nightly build feeds as described here:
+https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed
+
## Documentation
As mentioned above, Avalonia is still in beta and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
diff --git a/scripts/ReplaceNugetCache.ps1 b/scripts/ReplaceNugetCache.ps1
index a03d442bff..6de50f978d 100644
--- a/scripts/ReplaceNugetCache.ps1
+++ b/scripts/ReplaceNugetCache.ps1
@@ -1,5 +1,6 @@
+copy ..\samples\ControlCatalog.Desktop\bin\Debug\net461\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\net461\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\
-copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
-copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\
-copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
-copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\
+copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
+copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Gtk3.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\
+copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
+copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\
diff --git a/src/Avalonia.Base/AvaloniaLocator.cs b/src/Avalonia.Base/AvaloniaLocator.cs
index cd398c53ed..f9bbe38bec 100644
--- a/src/Avalonia.Base/AvaloniaLocator.cs
+++ b/src/Avalonia.Base/AvaloniaLocator.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Reactive.Disposables;
#pragma warning disable CS1591 // Enable me later
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 35e189e6a4..7e8d733f1b 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -5,12 +5,10 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
-using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Logging;
-using Avalonia.Reactive;
using Avalonia.Threading;
using Avalonia.Utilities;
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index f7dabd3a43..4b0116a536 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -231,7 +231,7 @@ namespace Avalonia
}
///
- /// Tests two s for unequality.
+ /// Tests two s for inequality.
///
/// The first property.
/// The second property.
@@ -558,4 +558,4 @@ namespace Avalonia
public override string ToString() => "(unset)";
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs
index 41c2ad6e54..d5a4e63003 100644
--- a/src/Avalonia.Base/Collections/AvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaList.cs
@@ -8,7 +8,6 @@ using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Avalonia.Diagnostics;
-using Avalonia.Platform;
namespace Avalonia.Collections
{
@@ -551,4 +550,4 @@ namespace Avalonia.Collections
NotifyCountChanged();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Collections/AvaloniaListConverter.cs b/src/Avalonia.Base/Collections/AvaloniaListConverter.cs
index 7f5442857c..63c2e07ecf 100644
--- a/src/Avalonia.Base/Collections/AvaloniaListConverter.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaListConverter.cs
@@ -40,4 +40,4 @@ namespace Avalonia.Collections
return result;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
index b27b06a277..1731950222 100644
--- a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
@@ -6,7 +6,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
-using System.Linq;
using System.Reactive.Disposables;
namespace Avalonia.Collections
diff --git a/src/Avalonia.Base/Collections/IAvaloniaList.cs b/src/Avalonia.Base/Collections/IAvaloniaList.cs
index 48c36976a5..b7ed9b1ad6 100644
--- a/src/Avalonia.Base/Collections/IAvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/IAvaloniaList.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace Avalonia.Collections
{
///
- /// A notiftying list.
+ /// A notifying list.
///
/// The type of the items in the list.
public interface IAvaloniaList : IList, IAvaloniaReadOnlyList
@@ -64,4 +64,4 @@ namespace Avalonia.Collections
/// The number of items to remove.
void RemoveRange(int index, int count);
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Collections/IAvaloniaReadOnlyList.cs b/src/Avalonia.Base/Collections/IAvaloniaReadOnlyList.cs
index 1aa4ff34d5..755efa3eaf 100644
--- a/src/Avalonia.Base/Collections/IAvaloniaReadOnlyList.cs
+++ b/src/Avalonia.Base/Collections/IAvaloniaReadOnlyList.cs
@@ -8,10 +8,10 @@ using System.ComponentModel;
namespace Avalonia.Collections
{
///
- /// A read-only notiftying list.
+ /// A read-only notifying list.
///
/// The type of the items in the list.
public interface IAvaloniaReadOnlyList : IReadOnlyList, INotifyCollectionChanged, INotifyPropertyChanged
{
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
index 3a355bcb48..ba84c37e4a 100644
--- a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
+++ b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
@@ -25,7 +25,7 @@ namespace Avalonia.Collections
}
///
- /// Subcribes to the CollectionChanged event using a weak subscription.
+ /// Subscribes to the CollectionChanged event using a weak subscription.
///
/// The collection.
///
@@ -44,7 +44,7 @@ namespace Avalonia.Collections
}
///
- /// Subcribes to the CollectionChanged event using a weak subscription.
+ /// Subscribes to the CollectionChanged event using a weak subscription.
///
/// The collection.
///
diff --git a/src/Avalonia.Base/Data/BindingChainException.cs b/src/Avalonia.Base/Data/BindingChainException.cs
index 97b0d3ba8b..a609ace4ec 100644
--- a/src/Avalonia.Base/Data/BindingChainException.cs
+++ b/src/Avalonia.Base/Data/BindingChainException.cs
@@ -6,7 +6,7 @@ using System;
namespace Avalonia.Data
{
///
- /// An exception returned through signalling that a
+ /// An exception returned through signaling that a
/// requested binding expression could not be evaluated because of a null in one of the links
/// of the binding chain.
///
@@ -15,14 +15,14 @@ namespace Avalonia.Data
private string _message;
///
- /// Initalizes a new instance of the class.
+ /// Initializes a new instance of the class.
///
public BindingChainException()
{
}
///
- /// Initalizes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The error message.
public BindingChainException(string message)
@@ -31,7 +31,7 @@ namespace Avalonia.Data
}
///
- /// Initalizes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The error message.
/// The expression.
@@ -51,7 +51,7 @@ namespace Avalonia.Data
public string Expression { get; protected set; }
///
- /// Gets the point in the expression at which the error occured.
+ /// Gets the point in the expression at which the error occurred.
///
public string ExpressionErrorPoint { get; protected set; }
diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs
index 5d3e6b26f4..7c55321a80 100644
--- a/src/Avalonia.Base/Data/BindingNotification.cs
+++ b/src/Avalonia.Base/Data/BindingNotification.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using Avalonia.Logging;
namespace Avalonia.Data
{
diff --git a/src/Avalonia.Base/Data/BindingOperations.cs b/src/Avalonia.Base/Data/BindingOperations.cs
index eb7c449bec..ca148659e6 100644
--- a/src/Avalonia.Base/Data/BindingOperations.cs
+++ b/src/Avalonia.Base/Data/BindingOperations.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Data
/// An optional anchor from which to locate required context. When binding to objects that
/// are not in the logical tree, certain types of binding need an anchor into the tree in
/// order to locate named controls or resources. The parameter
- /// can be used to provice this context.
+ /// can be used to provide this context.
///
/// An which can be used to cancel the binding.
public static IDisposable Apply(
diff --git a/src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs b/src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs
index d2fb48ffb8..7f4c83772d 100644
--- a/src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs
+++ b/src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs
@@ -1,10 +1,8 @@
-using Avalonia.Utilities;
-using System;
-using System.Collections.Generic;
+using System;
using System.Globalization;
using System.Reflection;
-using System.Text;
using System.Windows.Input;
+using Avalonia.Utilities;
namespace Avalonia.Data.Converters
{
diff --git a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
index ec75076892..990a4b04f2 100644
--- a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
+++ b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
@@ -3,9 +3,8 @@
using System;
using System.Globalization;
-using Avalonia.Data;
-using Avalonia.Utilities;
using System.Windows.Input;
+using Avalonia.Utilities;
namespace Avalonia.Data.Converters
{
diff --git a/src/Avalonia.Base/Data/Converters/IValueConverter.cs b/src/Avalonia.Base/Data/Converters/IValueConverter.cs
index b55a2c4fe8..754aa7975d 100644
--- a/src/Avalonia.Base/Data/Converters/IValueConverter.cs
+++ b/src/Avalonia.Base/Data/Converters/IValueConverter.cs
@@ -3,7 +3,6 @@
using System;
using System.Globalization;
-using Avalonia.Data;
namespace Avalonia.Data.Converters
{
diff --git a/src/Avalonia.Base/Data/Converters/StringConverters.cs b/src/Avalonia.Base/Data/Converters/StringConverters.cs
index 470f0d2289..0101cf5d29 100644
--- a/src/Avalonia.Base/Data/Converters/StringConverters.cs
+++ b/src/Avalonia.Base/Data/Converters/StringConverters.cs
@@ -1,9 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Globalization;
-using Avalonia.Utilities;
namespace Avalonia.Data.Converters
{
diff --git a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
index f7e5de2fe2..28c0dce518 100644
--- a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
+++ b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
@@ -1,7 +1,5 @@
using System;
-using System.Collections.Generic;
using System.Reactive.Linq;
-using System.Text;
using Avalonia.Reactive;
namespace Avalonia.Data.Core
diff --git a/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs b/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
index 0e2c3c035c..d5658b124b 100644
--- a/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
+++ b/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
@@ -1,9 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Reactive.Linq;
-
namespace Avalonia.Data.Core
{
public class EmptyExpressionNode : ExpressionNode
diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs
index 773049d3a5..65f26df011 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Linq;
-using Avalonia.Data;
using Avalonia.Data.Core.Parsers;
using Avalonia.Data.Core.Plugins;
using Avalonia.Reactive;
diff --git a/src/Avalonia.Base/Data/Core/ExpressionParseException.cs b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs
index 195c9b7660..0365ead24d 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionParseException.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using Avalonia.Data.Core.Parsers;
namespace Avalonia.Data.Core
{
diff --git a/src/Avalonia.Base/Data/Core/ITransformNode.cs b/src/Avalonia.Base/Data/Core/ITransformNode.cs
index 7638db8302..843d09fc19 100644
--- a/src/Avalonia.Base/Data/Core/ITransformNode.cs
+++ b/src/Avalonia.Base/Data/Core/ITransformNode.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Avalonia.Data.Core
+namespace Avalonia.Data.Core
{
interface ITransformNode
{
diff --git a/src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs b/src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs
index 04412b61ef..4206a99e3d 100644
--- a/src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs
+++ b/src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs
@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
-using System.Text;
-using Avalonia.Data;
namespace Avalonia.Data.Core
{
diff --git a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
index 5c3295a9d8..5e09bbcc2f 100644
--- a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
+++ b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
@@ -3,12 +3,8 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
-using System.Globalization;
using System.Linq;
using System.Reactive.Linq;
-using System.Reflection;
-using System.Text;
-using Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia.Data.Core
diff --git a/src/Avalonia.Base/Data/Core/LogicalNotNode.cs b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs
index 20f1bcd21e..6d58b18a9f 100644
--- a/src/Avalonia.Base/Data/Core/LogicalNotNode.cs
+++ b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs
@@ -3,7 +3,6 @@
using System;
using System.Globalization;
-using Avalonia.Data;
namespace Avalonia.Data.Core
{
diff --git a/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs
index db5d117687..d2035a592f 100644
--- a/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs
+++ b/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs
@@ -1,8 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
+using System.Linq;
using System.Linq.Expressions;
-using System.Text;
namespace Avalonia.Data.Core.Parsers
{
diff --git a/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs
index 1b4d1c200d..bcb6eeca41 100644
--- a/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs
+++ b/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs
@@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
-using System.Text;
namespace Avalonia.Data.Core.Parsers
{
diff --git a/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
index ee91f964ff..8d2ed905ee 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
@@ -2,9 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Linq;
using System.Reactive.Linq;
-using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins
{
diff --git a/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
index 47e4d91e13..33c40abea8 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
-using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins
{
@@ -31,7 +30,7 @@ namespace Avalonia.Data.Core.Plugins
return new Accessor(reference, name, inner);
}
- private class Accessor : DataValidatiorBase
+ private class Accessor : DataValidationBase
{
private ValidationContext _context;
diff --git a/src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs b/src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs
similarity index 95%
rename from src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs
rename to src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs
index 03ab7712bd..faec981eed 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs
@@ -15,15 +15,15 @@ namespace Avalonia.Data.Core.Plugins
/// and convert any values received from the inner property accessor into
/// s.
///
- public abstract class DataValidatiorBase : PropertyAccessorBase, IObserver
+ public abstract class DataValidationBase : PropertyAccessorBase, IObserver
{
private readonly IPropertyAccessor _inner;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The inner property accessor.
- protected DataValidatiorBase(IPropertyAccessor inner)
+ protected DataValidationBase(IPropertyAccessor inner)
{
_inner = inner;
}
@@ -77,4 +77,4 @@ namespace Avalonia.Data.Core.Plugins
PublishValue(notification);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
index 4507b32e0c..eabfa31d4b 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Data.Core.Plugins
return new Validator(reference, name, inner);
}
- private class Validator : DataValidatiorBase
+ private class Validator : DataValidationBase
{
public Validator(WeakReference reference, string name, IPropertyAccessor inner)
: base(inner)
diff --git a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
index c55917b088..2c3a9a53b4 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins
{
diff --git a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
index 4d6fc01229..2e83e0c25e 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Data.Core.Plugins
return new Validator(reference, name, accessor);
}
- private class Validator : DataValidatiorBase, IWeakSubscriber
+ private class Validator : DataValidationBase, IWeakSubscriber
{
WeakReference _reference;
string _name;
diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
index dab32b639a..4047489ccc 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
@@ -6,8 +6,6 @@ using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
-using Avalonia.Data;
-using Avalonia.Logging;
using Avalonia.Utilities;
namespace Avalonia.Data.Core.Plugins
diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
index cf0abc6f35..135935498c 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
@@ -1,9 +1,6 @@
using System;
-using System.Collections.Generic;
-using System.Text;
-using Avalonia.Data;
-using System.Reflection;
using System.Linq;
+using System.Reflection;
namespace Avalonia.Data.Core.Plugins
{
diff --git a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
index cc9b3abd56..16862f576d 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
@@ -2,12 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reflection;
using System.Threading.Tasks;
-using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins
{
diff --git a/src/Avalonia.Base/Data/Core/SettableNode.cs b/src/Avalonia.Base/Data/Core/SettableNode.cs
index e7c6ab766f..7c839acb78 100644
--- a/src/Avalonia.Base/Data/Core/SettableNode.cs
+++ b/src/Avalonia.Base/Data/Core/SettableNode.cs
@@ -1,9 +1,4 @@
-using Avalonia.Data;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System;
namespace Avalonia.Data.Core
{
diff --git a/src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs b/src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs
index fa8b56765c..fa2929c6ef 100644
--- a/src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs
+++ b/src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Text;
using System.Threading.Tasks;
namespace Avalonia
diff --git a/src/Avalonia.Base/Data/IBinding.cs b/src/Avalonia.Base/Data/IBinding.cs
index 70447ad3eb..95239cae06 100644
--- a/src/Avalonia.Base/Data/IBinding.cs
+++ b/src/Avalonia.Base/Data/IBinding.cs
@@ -17,7 +17,7 @@ namespace Avalonia.Data
/// An optional anchor from which to locate required context. When binding to objects that
/// are not in the logical tree, certain types of binding need an anchor into the tree in
/// order to locate named controls or resources. The parameter
- /// can be used to provice this context.
+ /// can be used to provide this context.
///
/// Whether data validation should be enabled.
///
diff --git a/src/Avalonia.Base/Logging/Logger.cs b/src/Avalonia.Base/Logging/Logger.cs
index 2c60336816..b1132ff4a9 100644
--- a/src/Avalonia.Base/Logging/Logger.cs
+++ b/src/Avalonia.Base/Logging/Logger.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Logging
public static class Logger
{
///
- /// Gets or sets the application-defined sink that recieves the messages.
+ /// Gets or sets the application-defined sink that receives the messages.
///
public static ILogSink Sink { get; set; }
diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs
index 66253dc5b2..9adb98e689 100644
--- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs
+++ b/src/Avalonia.Base/Platform/IRuntimePlatform.cs
@@ -1,9 +1,5 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
namespace Avalonia.Platform
{
diff --git a/src/Avalonia.Base/PriorityLevel.cs b/src/Avalonia.Base/PriorityLevel.cs
index b25247deaf..96661bd7ea 100644
--- a/src/Avalonia.Base/PriorityLevel.cs
+++ b/src/Avalonia.Base/PriorityLevel.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Data;
-using Avalonia.Logging;
namespace Avalonia
{
diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs
index 03094e2236..c8b434c6f9 100644
--- a/src/Avalonia.Base/PriorityValue.cs
+++ b/src/Avalonia.Base/PriorityValue.cs
@@ -12,12 +12,12 @@ using Avalonia.Utilities;
namespace Avalonia
{
///
- /// Maintains a list of prioritised bindings together with a current value.
+ /// Maintains a list of prioritized bindings together with a current value.
///
///
/// Bindings, in the form of s are added to the object using
/// the method. With the observable is passed a priority, where lower values
- /// represent higher priorites. The current is selected from the highest
+ /// represent higher priorities. The current is selected from the highest
/// priority binding that doesn't return . Where there
/// are multiple bindings registered with the same priority, the most recently added binding
/// has a higher priority. Each time the value changes, the
@@ -236,7 +236,7 @@ namespace Avalonia
}
///
- /// Updates the current and notifies all subscibers.
+ /// Updates the current and notifies all subscribers.
///
/// The value to set.
/// The priority level that the value came from.
diff --git a/src/Avalonia.Base/PropertyMetadata.cs b/src/Avalonia.Base/PropertyMetadata.cs
index 395aad53e4..bec562354d 100644
--- a/src/Avalonia.Base/PropertyMetadata.cs
+++ b/src/Avalonia.Base/PropertyMetadata.cs
@@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
using Avalonia.Data;
namespace Avalonia
diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
index 6af5ab63cf..278efbcd0d 100644
--- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
+++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
@@ -1,11 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using System.Threading;
namespace Avalonia.Threading
@@ -48,4 +43,4 @@ namespace Avalonia.Threading
Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Utilities/CharacterReader.cs b/src/Avalonia.Base/Utilities/CharacterReader.cs
index 55be8db043..56f3f7abd4 100644
--- a/src/Avalonia.Base/Utilities/CharacterReader.cs
+++ b/src/Avalonia.Base/Utilities/CharacterReader.cs
@@ -2,8 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Globalization;
-using System.Text;
namespace Avalonia.Utilities
{
diff --git a/src/Avalonia.Base/Utilities/DeferredSetter.cs b/src/Avalonia.Base/Utilities/DeferredSetter.cs
index ae6f599005..1b1324b1c5 100644
--- a/src/Avalonia.Base/Utilities/DeferredSetter.cs
+++ b/src/Avalonia.Base/Utilities/DeferredSetter.cs
@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Reactive.Disposables;
-using System.Runtime.CompilerServices;
-using System.Text;
namespace Avalonia.Utilities
{
diff --git a/src/Avalonia.Base/Utilities/IdentifierParser.cs b/src/Avalonia.Base/Utilities/IdentifierParser.cs
index 0a2e8e1e1b..09c50bf147 100644
--- a/src/Avalonia.Base/Utilities/IdentifierParser.cs
+++ b/src/Avalonia.Base/Utilities/IdentifierParser.cs
@@ -2,9 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Collections.Generic;
using System.Globalization;
-using System.Text;
namespace Avalonia.Utilities
{
diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs
index 81eb05b957..ab21e5fda0 100644
--- a/src/Avalonia.Base/Utilities/MathUtilities.cs
+++ b/src/Avalonia.Base/Utilities/MathUtilities.cs
@@ -1,10 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
namespace Avalonia.Utilities
{
diff --git a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs
index fd984d758a..bda85f90e2 100644
--- a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs
+++ b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs
@@ -2,8 +2,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Avalonia.Utilities
{
diff --git a/src/Avalonia.Base/Utilities/SingleOrQueue.cs b/src/Avalonia.Base/Utilities/SingleOrQueue.cs
index 4a66b72a56..643b0ba04d 100644
--- a/src/Avalonia.Base/Utilities/SingleOrQueue.cs
+++ b/src/Avalonia.Base/Utilities/SingleOrQueue.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Text;
namespace Avalonia.Utilities
{
diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs
index dd93811498..7b76457049 100644
--- a/src/Avalonia.Base/Utilities/TypeUtilities.cs
+++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
@@ -103,8 +102,8 @@ namespace Avalonia.Utilities
/// The type to cast to.
/// The value to cast.
/// The culture to use.
- /// If sucessful, contains the cast value.
- /// True if the cast was sucessful, otherwise false.
+ /// If successful, contains the cast value.
+ /// True if the cast was successful, otherwise false.
public static bool TryConvert(Type to, object value, CultureInfo culture, out object result)
{
if (value == null)
@@ -208,8 +207,8 @@ namespace Avalonia.Utilities
///
/// The type to cast to.
/// The value to cast.
- /// If sucessful, contains the cast value.
- /// True if the cast was sucessful, otherwise false.
+ /// If successful, contains the cast value.
+ /// True if the cast was successful, otherwise false.
public static bool TryConvertImplicit(Type to, object value, out object result)
{
if (value == null)
@@ -315,7 +314,7 @@ namespace Avalonia.Utilities
/// Determines if a type is numeric. Nullable numeric types are considered numeric.
///
///
- /// True if the type is numberic; otherwise false.
+ /// True if the type is numeric; otherwise false.
///
///
/// Boolean is not considered numeric.
diff --git a/src/Avalonia.Base/Utilities/WeakTimer.cs b/src/Avalonia.Base/Utilities/WeakTimer.cs
index 5c44a6d122..a4d18ca13b 100644
--- a/src/Avalonia.Base/Utilities/WeakTimer.cs
+++ b/src/Avalonia.Base/Utilities/WeakTimer.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Avalonia.Threading;
namespace Avalonia.Utilities
diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs
index ab80e74923..adbe89aceb 100644
--- a/src/Avalonia.Base/ValueStore.cs
+++ b/src/Avalonia.Base/ValueStore.cs
@@ -168,14 +168,14 @@ namespace Avalonia
return value;
}
- private DeferredSetter _defferedSetter;
+ private DeferredSetter _deferredSetter;
public DeferredSetter Setter
{
get
{
- return _defferedSetter ??
- (_defferedSetter = new DeferredSetter());
+ return _deferredSetter ??
+ (_deferredSetter = new DeferredSetter());
}
}
}
diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs
index 83763c0836..c92d5d7694 100644
--- a/src/Avalonia.Controls/AppBuilderBase.cs
+++ b/src/Avalonia.Controls/AppBuilderBase.cs
@@ -57,14 +57,14 @@ namespace Avalonia.Controls
public Action AfterSetupCallback { get; private set; } = builder => { };
///
- /// Gets or sets a method to call before Startis called on the .
+ /// Gets or sets a method to call before Start is called on the .
///
public Action BeforeStartCallback { get; private set; } = builder => { };
- protected AppBuilderBase(IRuntimePlatform platform, Action platformSevices)
+ protected AppBuilderBase(IRuntimePlatform platform, Action platformServices)
{
RuntimePlatform = platform;
- RuntimePlatformServicesInitializer = () => platformSevices((TAppBuilder)this);
+ RuntimePlatformServicesInitializer = () => platformServices((TAppBuilder)this);
}
///
diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 499b65c5b7..4c549ac7d4 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -9,7 +9,6 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
-using Avalonia.Layout;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
index 96fb9be8ac..1bc402bc2f 100644
--- a/src/Avalonia.Controls/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox.cs
@@ -352,8 +352,8 @@ namespace Avalonia.Controls
private Func>> _asyncPopulator;
private CancellationTokenSource _populationCancellationTokenSource;
- private bool _itemTemplateIsFromValueMemeberBinding = true;
- private bool _settingItemTemplateFromValueMemeberBinding;
+ private bool _itemTemplateIsFromValueMemberBinding = true;
+ private bool _settingItemTemplateFromValueMemberBinding;
private object _selectedItem;
private bool _isDropDownOpen;
@@ -788,12 +788,12 @@ namespace Avalonia.Controls
private void OnItemTemplatePropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
- if (!_settingItemTemplateFromValueMemeberBinding)
- _itemTemplateIsFromValueMemeberBinding = false;
+ if (!_settingItemTemplateFromValueMemberBinding)
+ _itemTemplateIsFromValueMemberBinding = false;
}
private void OnValueMemberBindingChanged(IBinding value)
{
- if(_itemTemplateIsFromValueMemeberBinding)
+ if(_itemTemplateIsFromValueMemberBinding)
{
var template =
new FuncDataTemplate(
@@ -805,9 +805,9 @@ namespace Avalonia.Controls
return control;
});
- _settingItemTemplateFromValueMemeberBinding = true;
+ _settingItemTemplateFromValueMemberBinding = true;
ItemTemplate = template;
- _settingItemTemplateFromValueMemeberBinding = false;
+ _settingItemTemplateFromValueMemberBinding = false;
}
}
diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs
index 0382c8d675..5f84421c64 100644
--- a/src/Avalonia.Controls/Border.cs
+++ b/src/Avalonia.Controls/Border.cs
@@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using Avalonia;
using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.Media;
@@ -115,4 +114,4 @@ namespace Avalonia.Controls
return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs
index 029e4dadc8..8f5a32634e 100644
--- a/src/Avalonia.Controls/Calendar/Calendar.cs
+++ b/src/Avalonia.Controls/Calendar/Calendar.cs
@@ -3,14 +3,14 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
+using System;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
-using System;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
namespace Avalonia.Controls
{
diff --git a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
index 0d48418683..5d883f2d14 100644
--- a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
@@ -3,11 +3,10 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-using Avalonia.Threading;
using System;
using System.Collections.ObjectModel;
using System.Linq;
-using System.Threading;
+using Avalonia.Threading;
namespace Avalonia.Controls.Primitives
{
diff --git a/src/Avalonia.Controls/Calendar/CalendarDateRange.cs b/src/Avalonia.Controls/Calendar/CalendarDateRange.cs
index 718cc7142b..47a0ad047b 100644
--- a/src/Avalonia.Controls/Calendar/CalendarDateRange.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarDateRange.cs
@@ -4,7 +4,6 @@
// All other rights reserved.
using System;
-using System.Diagnostics;
namespace Avalonia.Controls
{
diff --git a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs
index f6d0fbba62..1b36f92fd3 100644
--- a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs
@@ -3,11 +3,9 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-using Avalonia.Input;
using System;
-using System.Collections.Generic;
using System.Globalization;
-using System.Text;
+using Avalonia.Input;
namespace Avalonia.Controls.Primitives
{
diff --git a/src/Avalonia.Controls/Calendar/CalendarExtensions.cs b/src/Avalonia.Controls/Calendar/CalendarExtensions.cs
index 4fda02bff3..73de1c38f3 100644
--- a/src/Avalonia.Controls/Calendar/CalendarExtensions.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarExtensions.cs
@@ -3,10 +3,7 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-using System;
-using System.Collections.Generic;
using Avalonia.Input;
-using System.Diagnostics;
namespace Avalonia.Controls.Primitives
{
diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs
index b0cbd0be53..577555333f 100644
--- a/src/Avalonia.Controls/Calendar/CalendarItem.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs
@@ -3,13 +3,13 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
+using System;
+using System.Diagnostics;
+using System.Globalization;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
-using System;
-using System.Diagnostics;
-using System.Globalization;
namespace Avalonia.Controls.Primitives
{
diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs
index 08608ad359..2270598623 100644
--- a/src/Avalonia.Controls/Calendar/DatePicker.cs
+++ b/src/Avalonia.Controls/Calendar/DatePicker.cs
@@ -3,15 +3,14 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-using Avalonia.Controls.Primitives;
-using Avalonia.Data;
-using Avalonia.Input;
-using Avalonia.Interactivity;
-using Avalonia.Media;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Interactivity;
namespace Avalonia.Controls
{
@@ -1083,7 +1082,7 @@ namespace Avalonia.Controls
else
{
// If parse error: TextBox should have the latest valid
- // selecteddate value:
+ // SelectedDate value:
if (SelectedDate != null)
{
string newtext = this.DateTimeToString(SelectedDate.Value);
diff --git a/src/Avalonia.Controls/Canvas.cs b/src/Avalonia.Controls/Canvas.cs
index 8a80d6bdf7..5c9a97cb27 100644
--- a/src/Avalonia.Controls/Canvas.cs
+++ b/src/Avalonia.Controls/Canvas.cs
@@ -136,8 +136,9 @@ namespace Avalonia.Controls
///
/// The movement direction.
/// The control from which movement begins.
+ /// Whether to wrap around when the first or last item is reached.
/// The control.
- IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from)
+ IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap)
{
// TODO: Implement this
return null;
diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs
index 13f00bdc87..a69152c42b 100644
--- a/src/Avalonia.Controls/ContextMenu.cs
+++ b/src/Avalonia.Controls/ContextMenu.cs
@@ -1,16 +1,18 @@
+using System;
+using System.Reactive.Linq;
+using System.Linq;
+using System.ComponentModel;
+using Avalonia.Controls.Platform;
+using System.Collections.Generic;
+using Avalonia.Input;
+using Avalonia.LogicalTree;
+using Avalonia.Controls.Primitives;
+
namespace Avalonia.Controls
{
- using Input;
- using Interactivity;
- using LogicalTree;
- using Primitives;
- using System;
- using System.Reactive.Linq;
- using System.Linq;
- using System.ComponentModel;
-
- public class ContextMenu : SelectingItemsControl
+ public class ContextMenu : SelectingItemsControl, IMenu
{
+ private readonly IMenuInteractionHandler _interaction;
private bool _isOpen;
private Popup _popup;
@@ -20,6 +22,25 @@ namespace Avalonia.Controls
public static readonly DirectProperty IsOpenProperty =
AvaloniaProperty.RegisterDirect(nameof(IsOpen), o => o.IsOpen);
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ContextMenu()
+ {
+ _interaction = AvaloniaLocator.Current.GetService() ??
+ new DefaultMenuInteractionHandler();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The menu interaction handler.
+ public ContextMenu(IMenuInteractionHandler interactionHandler)
+ {
+ Contract.Requires(interactionHandler != null);
+
+ _interaction = interactionHandler;
+ }
///
/// Initializes static members of the class.
@@ -27,8 +48,6 @@ namespace Avalonia.Controls
static ContextMenu()
{
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
-
- MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true);
}
///
@@ -36,6 +55,36 @@ namespace Avalonia.Controls
///
public bool IsOpen => _isOpen;
+ ///
+ IMenuInteractionHandler IMenu.InteractionHandler => _interaction;
+
+ ///
+ IMenuItem IMenuElement.SelectedItem
+ {
+ get
+ {
+ var index = SelectedIndex;
+ return (index != -1) ?
+ (IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
+ null;
+ }
+ set
+ {
+ SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
+ }
+ }
+
+ ///
+ IEnumerable IMenuElement.SubItems
+ {
+ get
+ {
+ return ItemContainerGenerator.Containers
+ .Select(x => x.ContainerControl)
+ .OfType();
+ }
+ }
+
///
/// Occurs when the value of the
///
@@ -50,7 +99,6 @@ namespace Avalonia.Controls
///
public event CancelEventHandler ContextMenuClosing;
-
///
/// Called when the property changes on a control.
///
@@ -71,62 +119,53 @@ namespace Avalonia.Controls
}
///
- /// Called when a submenu is clicked somewhere in the menu.
+ /// Opens the menu.
///
- /// The event args.
- private void OnContextMenuClick(RoutedEventArgs e)
- {
- Hide();
- FocusManager.Instance.Focus(null);
- e.Handled = true;
- }
+ public void Open() => Open(null);
///
- /// Closes the menu.
+ /// Opens a context menu on the specified control.
///
- public void Hide()
+ /// The control.
+ public void Open(Control control)
{
- if (_popup != null && _popup.IsVisible)
+ if (_popup == null)
{
- _popup.IsOpen = false;
+ _popup = new Popup()
+ {
+ PlacementMode = PlacementMode.Pointer,
+ PlacementTarget = control,
+ StaysOpen = false,
+ ObeyScreenEdges = true
+ };
+
+ _popup.Closed += PopupClosed;
+ _interaction.Attach(this);
}
- SelectedIndex = -1;
+ ((ISetLogicalParent)_popup).SetParent(control);
+ _popup.Child = this;
+ _popup.IsOpen = true;
- SetAndRaise(IsOpenProperty, ref _isOpen, false);
+ SetAndRaise(IsOpenProperty, ref _isOpen, true);
}
///
- /// Shows a context menu for the specified control.
+ /// Closes the menu.
///
- /// The control.
- private void Show(Control control)
+ public void Close()
{
- if (control != null)
+ if (_popup != null && _popup.IsVisible)
{
- if (_popup == null)
- {
- _popup = new Popup()
- {
- PlacementMode = PlacementMode.Pointer,
- PlacementTarget = control,
- StaysOpen = false,
- ObeyScreenEdges = true
- };
-
- _popup.Closed += PopupClosed;
- }
-
- ((ISetLogicalParent)_popup).SetParent(control);
- _popup.Child = this;
+ _popup.IsOpen = false;
+ }
- _popup.IsOpen = true;
+ SelectedIndex = -1;
- SetAndRaise(IsOpenProperty, ref _isOpen, true);
- }
+ SetAndRaise(IsOpenProperty, ref _isOpen, false);
}
- private static void PopupClosed(object sender, EventArgs e)
+ private void PopupClosed(object sender, EventArgs e)
{
var contextMenu = (sender as Popup)?.Child as ContextMenu;
@@ -152,7 +191,7 @@ namespace Avalonia.Controls
if (contextMenu.CancelClosing())
return;
- control.ContextMenu.Hide();
+ control.ContextMenu.Close();
e.Handled = true;
}
@@ -161,7 +200,7 @@ namespace Avalonia.Controls
if (contextMenu.CancelOpening())
return;
- contextMenu.Show(control);
+ contextMenu.Open(control);
e.Handled = true;
}
}
@@ -179,5 +218,10 @@ namespace Avalonia.Controls
ContextMenuOpening?.Invoke(this, eventArgs);
return eventArgs.Cancel;
}
+
+ bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap)
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index 67288972b6..a00d586233 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -1,22 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Linq;
-using System.Reactive;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using Avalonia.Collections;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
-using Avalonia.Data;
-using Avalonia.Diagnostics;
using Avalonia.Input;
using Avalonia.Interactivity;
-using Avalonia.Logging;
-using Avalonia.LogicalTree;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.VisualTree;
@@ -134,9 +122,9 @@ namespace Avalonia.Controls
}
///
- /// Gets the element that recieves the focus adorner.
+ /// Gets the element that receives the focus adorner.
///
- /// The control that recieves the focus adorner.
+ /// The control that receives the focus adorner.
protected virtual IControl GetTemplateFocusTarget()
{
return this;
diff --git a/src/Avalonia.Controls/DataValidationErrors.cs b/src/Avalonia.Controls/DataValidationErrors.cs
index a55bd63aa8..f0d7f8257e 100644
--- a/src/Avalonia.Controls/DataValidationErrors.cs
+++ b/src/Avalonia.Controls/DataValidationErrors.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
-using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
index c177d43917..224af979ab 100644
--- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
+++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
@@ -1,7 +1,6 @@
using System;
using Avalonia.Controls.Platform;
using Avalonia.Input;
-using Avalonia.Layout;
using Avalonia.Platform;
using Avalonia.Styling;
using JetBrains.Annotations;
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
index 5becdc0f61..8b39cc03b8 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
@@ -1,9 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Avalonia.Layout;
using Avalonia.Styling;
namespace Avalonia.Controls.Embedding.Offscreen
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
index c986c5d07c..37bb72e75a 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs
index 5323939b50..1049d742f3 100644
--- a/src/Avalonia.Controls/Expander.cs
+++ b/src/Avalonia.Controls/Expander.cs
@@ -1,6 +1,5 @@
using Avalonia.Animation;
using Avalonia.Controls.Primitives;
-using Avalonia.VisualTree;
namespace Avalonia.Controls
{
diff --git a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
index d139c95fd4..653a4f5dcb 100644
--- a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
@@ -28,7 +28,7 @@ namespace Avalonia.Controls.Generators
Type ContainerType { get; }
///
- /// Signalled whenever new containers are materialized.
+ /// Signaled whenever new containers are materialized.
///
event EventHandler Materialized;
@@ -110,4 +110,4 @@ namespace Avalonia.Controls.Generators
/// The index of the container, or -1 if not found.
int IndexFromContainer(IControl container);
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs
index 224fc9d17e..e2e591215e 100644
--- a/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs
@@ -1,8 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System.Collections.Generic;
-
namespace Avalonia.Controls.Generators
{
///
@@ -15,4 +13,4 @@ namespace Avalonia.Controls.Generators
///
TreeContainerIndex Index { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
index 259c524d59..320d6c8faf 100644
--- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
+++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
@@ -2,8 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Linq.Expressions;
-using System.Reflection;
using Avalonia.Controls.Templates;
using Avalonia.Data;
diff --git a/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs
index c9b3a55aaa..d3cf70e0f8 100644
--- a/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs
@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Avalonia.Controls.Generators
+namespace Avalonia.Controls.Generators
{
public class MenuItemContainerGenerator : ItemContainerGenerator
{
diff --git a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs
index 08c11e2965..24b3fc1f32 100644
--- a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs
+++ b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Controls.Generators
private readonly Dictionary _containerToItem = new Dictionary();
///
- /// Signalled whenever new containers are materialized.
+ /// Signaled whenever new containers are materialized.
///
public event EventHandler Materialized;
diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
index abcecb5b82..304c86dbf7 100644
--- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using Avalonia.Controls.Templates;
using Avalonia.Data;
diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs
index 608879812c..f6a608cd71 100644
--- a/src/Avalonia.Controls/GridLength.cs
+++ b/src/Avalonia.Controls/GridLength.cs
@@ -1,11 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using Avalonia.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.Linq;
+using Avalonia.Utilities;
namespace Avalonia.Controls
{
@@ -218,4 +217,4 @@ namespace Avalonia.Controls
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs
index 8112b2babd..1e4c6f2c2a 100644
--- a/src/Avalonia.Controls/GridSplitter.cs
+++ b/src/Avalonia.Controls/GridSplitter.cs
@@ -116,27 +116,27 @@ namespace Avalonia.Controls
_orientation = DetectOrientation();
- int defenitionIndex; //row or col
+ int definitionIndex; //row or col
if (_orientation == Orientation.Vertical)
{
Cursor = new Cursor(StandardCursorType.SizeWestEast);
_definitions = _grid.ColumnDefinitions.Cast().ToList();
- defenitionIndex = GetValue(Grid.ColumnProperty);
+ definitionIndex = GetValue(Grid.ColumnProperty);
PseudoClasses.Add(":vertical");
}
else
{
Cursor = new Cursor(StandardCursorType.SizeNorthSouth);
- defenitionIndex = GetValue(Grid.RowProperty);
+ definitionIndex = GetValue(Grid.RowProperty);
_definitions = _grid.RowDefinitions.Cast().ToList();
PseudoClasses.Add(":horizontal");
}
- if (defenitionIndex > 0)
- _prevDefinition = _definitions[defenitionIndex - 1];
+ if (definitionIndex > 0)
+ _prevDefinition = _definitions[definitionIndex - 1];
- if (defenitionIndex < _definitions.Count - 1)
- _nextDefinition = _definitions[defenitionIndex + 1];
+ if (definitionIndex < _definitions.Count - 1)
+ _nextDefinition = _definitions[definitionIndex + 1];
}
private Orientation DetectOrientation()
@@ -167,4 +167,4 @@ namespace Avalonia.Controls
return Orientation.Vertical;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs
index a59fb86fb7..95752e7875 100644
--- a/src/Avalonia.Controls/HotkeyManager.cs
+++ b/src/Avalonia.Controls/HotkeyManager.cs
@@ -1,10 +1,5 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows.Input;
-using Avalonia.Controls;
using Avalonia.Controls.Utils;
using Avalonia.Input;
diff --git a/src/Avalonia.Controls/IApplicationLifecycle.cs b/src/Avalonia.Controls/IApplicationLifecycle.cs
index beb97a44ae..51f554c078 100644
--- a/src/Avalonia.Controls/IApplicationLifecycle.cs
+++ b/src/Avalonia.Controls/IApplicationLifecycle.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Avalonia.Controls
{
diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs
index e7f2903249..87b66d5f81 100644
--- a/src/Avalonia.Controls/IControl.cs
+++ b/src/Avalonia.Controls/IControl.cs
@@ -1,12 +1,9 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
-using Avalonia.LogicalTree;
-using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@@ -23,4 +20,4 @@ namespace Avalonia.Controls
{
new IControl Parent { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/IMenu.cs b/src/Avalonia.Controls/IMenu.cs
new file mode 100644
index 0000000000..0722a22f08
--- /dev/null
+++ b/src/Avalonia.Controls/IMenu.cs
@@ -0,0 +1,20 @@
+using Avalonia.Controls.Platform;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Represents a or .
+ ///
+ public interface IMenu : IMenuElement
+ {
+ ///
+ /// Gets the menu interaction handler.
+ ///
+ IMenuInteractionHandler InteractionHandler { get; }
+
+ ///
+ /// Gets a value indicating whether the menu is open.
+ ///
+ bool IsOpen { get; }
+ }
+}
diff --git a/src/Avalonia.Controls/IMenuElement.cs b/src/Avalonia.Controls/IMenuElement.cs
new file mode 100644
index 0000000000..ee9d0fd6b6
--- /dev/null
+++ b/src/Avalonia.Controls/IMenuElement.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using Avalonia.Input;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Represents an or .
+ ///
+ public interface IMenuElement : IControl
+ {
+ ///
+ /// Gets or sets the currently selected submenu item.
+ ///
+ IMenuItem SelectedItem { get; set; }
+
+ ///
+ /// Gets the submenu items.
+ ///
+ IEnumerable SubItems { get; }
+
+ ///
+ /// Opens the menu or menu item.
+ ///
+ void Open();
+
+ ///
+ /// Closes the menu or menu item.
+ ///
+ void Close();
+
+ ///
+ /// Moves the submenu selection in the specified direction.
+ ///
+ /// The direction.
+ /// Whether to wrap after the first or last item.
+ /// True if the selection was moved; otherwise false.
+ bool MoveSelection(NavigationDirection direction, bool wrap);
+ }
+}
diff --git a/src/Avalonia.Controls/IMenuItem.cs b/src/Avalonia.Controls/IMenuItem.cs
new file mode 100644
index 0000000000..132d565cb7
--- /dev/null
+++ b/src/Avalonia.Controls/IMenuItem.cs
@@ -0,0 +1,39 @@
+namespace Avalonia.Controls
+{
+ ///
+ /// Represents a .
+ ///
+ public interface IMenuItem : IMenuElement
+ {
+ ///
+ /// Gets or sets a value that indicates whether the item has a submenu.
+ ///
+ bool HasSubMenu { get; }
+
+ ///
+ /// Gets a value indicating whether the mouse is currently over the menu item's submenu.
+ ///
+ bool IsPointerOverSubMenu { get; }
+
+ ///
+ /// Gets or sets a value that indicates whether the submenu of the is
+ /// open.
+ ///
+ bool IsSubMenuOpen { get; set; }
+
+ ///
+ /// Gets a value that indicates whether the is a top-level main menu item.
+ ///
+ bool IsTopLevel { get; }
+
+ ///
+ /// Gets the parent .
+ ///
+ new IMenuElement Parent { get; }
+
+ ///
+ /// Raises a click event on the menu item.
+ ///
+ void RaiseClick();
+ }
+}
diff --git a/src/Avalonia.Controls/IScrollable.cs b/src/Avalonia.Controls/IScrollable.cs
index 9bbd0d8518..204e918d7b 100644
--- a/src/Avalonia.Controls/IScrollable.cs
+++ b/src/Avalonia.Controls/IScrollable.cs
@@ -1,8 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
diff --git a/src/Avalonia.Controls/IVirtualizingPanel.cs b/src/Avalonia.Controls/IVirtualizingPanel.cs
index 2d8dcb42e3..cba196dfa8 100644
--- a/src/Avalonia.Controls/IVirtualizingPanel.cs
+++ b/src/Avalonia.Controls/IVirtualizingPanel.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Controls
/// Gets or sets the controller for the virtualizing panel.
///
///
- /// A virtualizing controller is responsible for maintaing the controls in the virtualizing
+ /// A virtualizing controller is responsible for maintaining the controls in the virtualizing
/// panel. This property will be set by the controller when virtualization is initialized.
/// Note that this property may remain null if the panel is added to a control that does
/// not act as a virtualizing controller.
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 3cb997f615..9d4cbb9260 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -15,6 +15,7 @@ using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -324,7 +325,47 @@ namespace Avalonia.Controls
}
///
- /// Caled when the property changes.
+ /// Handles directional navigation within the .
+ ///
+ /// The key events.
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ if (!e.Handled)
+ {
+ var focus = FocusManager.Instance;
+ var direction = e.Key.ToNavigationDirection();
+ var container = Presenter?.Panel as INavigableContainer;
+
+ if (container == null ||
+ focus.Current == null ||
+ direction == null ||
+ direction.Value.IsTab())
+ {
+ return;
+ }
+
+ var current = focus.Current
+ .GetSelfAndVisualAncestors()
+ .OfType()
+ .FirstOrDefault(x => x.VisualParent == container);
+
+ if (current != null)
+ {
+ var next = GetNextControl(container, direction.Value, current, false);
+
+ if (next != null)
+ {
+ focus.Focus(next, NavigationMethod.Directional);
+ e.Handled = true;
+ }
+ }
+ }
+
+ base.OnKeyDown(e);
+ }
+
+ ///
+ /// Called when the property changes.
///
/// The event args.
protected virtual void ItemsChanged(AvaloniaPropertyChangedEventArgs e)
@@ -335,6 +376,7 @@ namespace Avalonia.Controls
var oldValue = e.OldValue as IEnumerable;
var newValue = e.NewValue as IEnumerable;
+ UpdateItemCount();
RemoveControlItemsFromLogicalChildren(oldValue);
AddControlItemsToLogicalChildren(newValue);
SubscribeToItems(newValue);
@@ -358,10 +400,8 @@ namespace Avalonia.Controls
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
}
-
- int? count = (Items as IList)?.Count;
- if (count != null)
- ItemCount = (int)count;
+
+ UpdateItemCount();
var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection == null || collection.Count == 0);
@@ -445,5 +485,44 @@ namespace Avalonia.Controls
// TODO: Rebuild the item containers.
}
}
+
+ private void UpdateItemCount()
+ {
+ if (Items == null)
+ {
+ ItemCount = 0;
+ }
+ else if (Items is IList list)
+ {
+ ItemCount = list.Count;
+ }
+ else
+ {
+ ItemCount = Items.Count();
+ }
+ }
+
+ protected static IInputElement GetNextControl(
+ INavigableContainer container,
+ NavigationDirection direction,
+ IInputElement from,
+ bool wrap)
+ {
+ IInputElement result;
+
+ do
+ {
+ result = container.GetControl(direction, from, wrap);
+
+ if (result?.Focusable == true)
+ {
+ return result;
+ }
+
+ from = result;
+ } while (from != null);
+
+ return null;
+ }
}
}
diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs
index 994af9dab8..00fca385a0 100644
--- a/src/Avalonia.Controls/Menu.cs
+++ b/src/Avalonia.Controls/Menu.cs
@@ -2,30 +2,23 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
using System.Linq;
-using System.Reactive.Disposables;
using Avalonia.Controls.Generators;
+using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
-using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
-using Avalonia.Rendering;
namespace Avalonia.Controls
{
///
/// A top-level menu control.
///
- public class Menu : SelectingItemsControl, IFocusScope, IMainMenu
+ public class Menu : SelectingItemsControl, IFocusScope, IMainMenu, IMenu
{
- ///
- /// Defines the default items panel used by a .
- ///
- private static readonly ITemplate DefaultPanel =
- new FuncTemplate(() => new StackPanel { Orientation = Orientation.Horizontal });
-
///
/// Defines the property.
///
@@ -34,12 +27,42 @@ namespace Avalonia.Controls
nameof(IsOpen),
o => o.IsOpen);
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent MenuOpenedEvent =
+ RoutedEvent.Register(nameof(MenuOpened), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent MenuClosedEvent =
+ RoutedEvent.Register(nameof(MenuClosed), RoutingStrategies.Bubble);
+
+ private static readonly ITemplate DefaultPanel =
+ new FuncTemplate(() => new StackPanel { Orientation = Orientation.Horizontal });
+ private readonly IMenuInteractionHandler _interaction;
private bool _isOpen;
///
- /// Tracks event handlers added to the root of the visual tree.
+ /// Initializes a new instance of the class.
+ ///
+ public Menu()
+ {
+ _interaction = AvaloniaLocator.Current.GetService() ??
+ new DefaultMenuInteractionHandler();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
///
- private IDisposable _subscription;
+ /// The menu interaction handler.
+ public Menu(IMenuInteractionHandler interactionHandler)
+ {
+ Contract.Requires(interactionHandler != null);
+
+ _interaction = interactionHandler;
+ }
///
/// Initializes static members of the class.
@@ -47,7 +70,6 @@ namespace Avalonia.Controls
static Menu()
{
ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel);
- MenuItem.ClickEvent.AddClassHandler(x => x.OnMenuClick, handledEventsToo: true);
MenuItem.SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened);
}
@@ -60,18 +82,52 @@ namespace Avalonia.Controls
private set { SetAndRaise(IsOpenProperty, ref _isOpen, value); }
}
- ///
- /// Gets the selected container.
- ///
- private MenuItem SelectedMenuItem
+ ///
+ IMenuInteractionHandler IMenu.InteractionHandler => _interaction;
+
+ ///
+ IMenuItem IMenuElement.SelectedItem
{
get
{
var index = SelectedIndex;
return (index != -1) ?
- (MenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
+ (IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
null;
}
+ set
+ {
+ SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
+ }
+ }
+
+ ///
+ IEnumerable IMenuElement.SubItems
+ {
+ get
+ {
+ return ItemContainerGenerator.Containers
+ .Select(x => x.ContainerControl)
+ .OfType();
+ }
+ }
+
+ ///
+ /// Occurs when a is opened.
+ ///
+ public event EventHandler MenuOpened
+ {
+ add { AddHandler(MenuOpenedEvent, value); }
+ remove { RemoveHandler(MenuOpenedEvent, value); }
+ }
+
+ ///
+ /// Occurs when a is closed.
+ ///
+ public event EventHandler MenuClosed
+ {
+ add { AddHandler(MenuClosedEvent, value); }
+ remove { RemoveHandler(MenuClosedEvent, value); }
}
///
@@ -79,13 +135,22 @@ namespace Avalonia.Controls
///
public void Close()
{
- foreach (MenuItem i in this.GetLogicalChildren())
+ if (IsOpen)
{
- i.IsSubMenuOpen = false;
- }
+ foreach (var i in ((IMenu)this).SubItems)
+ {
+ i.Close();
+ }
+
+ IsOpen = false;
+ SelectedIndex = -1;
- IsOpen = false;
- SelectedIndex = -1;
+ RaiseEvent(new RoutedEventArgs
+ {
+ RoutedEvent = MenuClosedEvent,
+ Source = this,
+ });
+ }
}
///
@@ -93,9 +158,25 @@ namespace Avalonia.Controls
///
public void Open()
{
- SelectedIndex = 0;
- SelectedMenuItem.Focus();
- IsOpen = true;
+ if (!IsOpen)
+ {
+ IsOpen = true;
+
+ RaiseEvent(new RoutedEventArgs
+ {
+ RoutedEvent = MenuOpenedEvent,
+ Source = this,
+ });
+ }
+ }
+
+ ///
+ bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap);
+
+ ///
+ protected override IItemContainerGenerator CreateItemContainerGenerator()
+ {
+ return new ItemContainerGenerator(this, MenuItem.HeaderProperty, null);
}
///
@@ -103,79 +184,27 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTree(e);
- var topLevel = (TopLevel)e.Root;
- var window = e.Root as Window;
-
- if (window != null)
- window.Deactivated += Deactivated;
-
- var pointerPress = topLevel.AddHandler(
- PointerPressedEvent,
- TopLevelPreviewPointerPress,
- RoutingStrategies.Tunnel);
-
- _subscription = new CompositeDisposable(
- pointerPress,
- Disposable.Create(() =>
- {
- if (window != null)
- window.Deactivated -= Deactivated;
- }),
- InputManager.Instance.Process.Subscribe(ListenForNonClientClick));
-
var inputRoot = e.Root as IInputRoot;
if (inputRoot?.AccessKeyHandler != null)
{
inputRoot.AccessKeyHandler.MainMenu = this;
}
+
+ _interaction.Attach(this);
}
///
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
- _subscription.Dispose();
+ _interaction.Detach(this);
}
///
- protected override IItemContainerGenerator CreateItemContainerGenerator()
- {
- return new ItemContainerGenerator(this, MenuItem.HeaderProperty, null);
- }
-
- ///
- /// Called when a key is pressed within the menu.
- ///
- /// The event args.
protected override void OnKeyDown(KeyEventArgs e)
{
- bool menuWasOpen = SelectedMenuItem?.IsSubMenuOpen ?? false;
-
- base.OnKeyDown(e);
-
- if (menuWasOpen)
- {
- // If a menu item was open and we navigate to a new one with the arrow keys, open
- // that menu and select the first item.
- var selection = SelectedMenuItem;
-
- if (selection != null && !selection.IsSubMenuOpen)
- {
- selection.IsSubMenuOpen = true;
- selection.SelectedIndex = 0;
- }
- }
- }
-
- ///
- /// Called when the menu loses focus.
- ///
- /// The event args.
- protected override void OnLostFocus(RoutedEventArgs e)
- {
- base.OnLostFocus(e);
- SelectedItem = null;
+ // Don't handle here: let the interaction handler handle it.
}
///
@@ -184,9 +213,7 @@ namespace Avalonia.Controls
/// The event args.
protected virtual void OnSubmenuOpened(RoutedEventArgs e)
{
- var menuItem = e.Source as MenuItem;
-
- if (menuItem != null && menuItem.Parent == this)
+ if (e.Source is MenuItem menuItem && menuItem.Parent == this)
{
foreach (var child in this.GetLogicalChildren().OfType())
{
@@ -199,58 +226,5 @@ namespace Avalonia.Controls
IsOpen = true;
}
-
- ///
- /// Called when the top-level window is deactivated.
- ///
- /// The sender.
- /// The event args.
- private void Deactivated(object sender, EventArgs e)
- {
- Close();
- }
-
- ///
- /// Listens for non-client clicks and closes the menu when one is detected.
- ///
- /// The raw event.
- private void ListenForNonClientClick(RawInputEventArgs e)
- {
- var mouse = e as RawMouseEventArgs;
-
- if (mouse?.Type == RawMouseEventType.NonClientLeftButtonDown)
- {
- Close();
- }
- }
-
- ///
- /// Called when a submenu is clicked somewhere in the menu.
- ///
- /// The event args.
- private void OnMenuClick(RoutedEventArgs e)
- {
- Close();
- FocusManager.Instance.Focus(null);
- e.Handled = true;
- }
-
- ///
- /// Called when the pointer is pressed anywhere on the window.
- ///
- /// The sender.
- /// The event args.
- private void TopLevelPreviewPointerPress(object sender, PointerPressedEventArgs e)
- {
- if (IsOpen)
- {
- var control = e.Source as ILogical;
-
- if (!this.IsLogicalParentOf(control))
- {
- Close();
- }
- }
- }
}
}
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index 96f6fb59b0..7b57783c5a 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using Avalonia.Controls.Generators;
@@ -11,14 +12,13 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
-using Avalonia.Threading;
namespace Avalonia.Controls
{
///
/// A menu item control.
///
- public class MenuItem : HeaderedSelectingItemsControl, ISelectable
+ public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable
{
///
/// Defines the property.
@@ -62,6 +62,18 @@ namespace Avalonia.Controls
public static readonly RoutedEvent ClickEvent =
RoutedEvent.Register(nameof(Click), RoutingStrategies.Bubble);
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent PointerEnterItemEvent =
+ RoutedEvent.Register(nameof(PointerEnterItem), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent PointerLeaveItemEvent =
+ RoutedEvent.Register(nameof(PointerLeaveItem), RoutingStrategies.Bubble);
+
///
/// Defines the event.
///
@@ -72,15 +84,7 @@ namespace Avalonia.Controls
/// The default value for the property.
///
private static readonly ITemplate DefaultPanel =
- new FuncTemplate(() => new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- });
-
- ///
- /// The timer used to display submenus.
- ///
- private IDisposable _submenuTimer;
+ new FuncTemplate(() => new StackPanel());
///
/// The submenu popup.
@@ -96,16 +100,15 @@ namespace Avalonia.Controls
CommandProperty.Changed.Subscribe(CommandChanged);
FocusableProperty.OverrideDefaultValue(true);
IconProperty.Changed.AddClassHandler(x => x.IconChanged);
+ IsSelectedProperty.Changed.AddClassHandler(x => x.IsSelectedChanged);
ItemsPanelProperty.OverrideDefaultValue(DefaultPanel);
ClickEvent.AddClassHandler(x => x.OnClick);
SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened);
IsSubMenuOpenProperty.Changed.AddClassHandler(x => x.SubMenuOpenChanged);
- AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler(x => x.AccessKeyPressed);
}
public MenuItem()
{
-
}
///
@@ -117,6 +120,30 @@ namespace Avalonia.Controls
remove { RemoveHandler(ClickEvent, value); }
}
+ ///
+ /// Occurs when the pointer enters a menu item.
+ ///
+ ///
+ /// A bubbling version of the event for menu items.
+ ///
+ public event EventHandler PointerEnterItem
+ {
+ add { AddHandler(PointerEnterItemEvent, value); }
+ remove { RemoveHandler(PointerEnterItemEvent, value); }
+ }
+
+ ///
+ /// Raised when the pointer leaves a menu item.
+ ///
+ ///
+ /// A bubbling version of the event for menu items.
+ ///
+ public event EventHandler PointerLeaveItem
+ {
+ add { AddHandler(PointerLeaveItemEvent, value); }
+ remove { RemoveHandler(PointerLeaveItemEvent, value); }
+ }
+
///
/// Occurs when a 's submenu is opened.
///
@@ -188,10 +215,71 @@ namespace Avalonia.Controls
public bool HasSubMenu => !Classes.Contains(":empty");
///
- /// Gets a value that indicates whether the is a top-level menu item.
+ /// Gets a value that indicates whether the is a top-level main menu item.
///
public bool IsTopLevel => Parent is Menu;
+ ///
+ bool IMenuItem.IsPointerOverSubMenu => _popup.PopupRoot?.IsPointerOver ?? false;
+
+ ///
+ IMenuElement IMenuItem.Parent => Parent as IMenuElement;
+
+ ///
+ bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap);
+
+ ///
+ IMenuItem IMenuElement.SelectedItem
+ {
+ get
+ {
+ var index = SelectedIndex;
+ return (index != -1) ?
+ (IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
+ null;
+ }
+ set
+ {
+ SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
+ }
+ }
+
+ ///
+ IEnumerable IMenuElement.SubItems
+ {
+ get
+ {
+ return ItemContainerGenerator.Containers
+ .Select(x => x.ContainerControl)
+ .OfType();
+ }
+ }
+
+ ///
+ /// Opens the submenu.
+ ///
+ ///
+ /// This has the same effect as setting to true.
+ ///
+ public void Open() => IsSubMenuOpen = true;
+
+ ///
+ /// Closes the submenu.
+ ///
+ ///
+ /// This has the same effect as setting to false.
+ ///
+ public void Close() => IsSubMenuOpen = false;
+
+ ///
+ void IMenuItem.RaiseClick() => RaiseEvent(new RoutedEventArgs(ClickEvent));
+
+ ///
+ protected override IItemContainerGenerator CreateItemContainerGenerator()
+ {
+ return new MenuItemContainerGenerator(this);
+ }
+
///
/// Called when the is clicked.
///
@@ -205,163 +293,43 @@ namespace Avalonia.Controls
}
}
- ///
- /// Called when the recieves focus.
- ///
- /// The event args.
+ ///
protected override void OnGotFocus(GotFocusEventArgs e)
{
base.OnGotFocus(e);
- IsSelected = true;
+ e.Handled = UpdateSelectionFromEventSource(e.Source, true);
}
///
- protected override IItemContainerGenerator CreateItemContainerGenerator()
- {
- return new MenuItemContainerGenerator(this);
- }
-
- ///
- /// Called when a key is pressed in the .
- ///
- /// The event args.
protected override void OnKeyDown(KeyEventArgs e)
{
- // Some keypresses we want to pass straight to the parent MenuItem/Menu without giving
- // this MenuItem the chance to handle them. This is usually e.g. when the submenu is
- // closed so passing them to the base would try to move the selection in a hidden
- // submenu.
- var passStraightToParent = true;
-
- switch (e.Key)
- {
- case Key.Left:
- if (!IsTopLevel && IsSubMenuOpen)
- {
- IsSubMenuOpen = false;
- e.Handled = true;
- }
-
- passStraightToParent = IsTopLevel || !IsSubMenuOpen;
- break;
-
- case Key.Right:
- if (!IsTopLevel && HasSubMenu && !IsSubMenuOpen)
- {
- SelectedIndex = 0;
- IsSubMenuOpen = true;
- e.Handled = true;
- }
-
- passStraightToParent = IsTopLevel || !IsSubMenuOpen;
- break;
-
- case Key.Enter:
- if (HasSubMenu)
- {
- goto case Key.Right;
- }
- else
- {
- RaiseEvent(new RoutedEventArgs(ClickEvent));
- e.Handled = true;
- }
-
- break;
-
- case Key.Escape:
- if (IsSubMenuOpen)
- {
- IsSubMenuOpen = false;
- e.Handled = true;
- }
-
- break;
- }
-
- if (!passStraightToParent)
- {
- base.OnKeyDown(e);
- }
+ // Don't handle here: let event bubble up to menu.
}
- ///
- /// Called when the pointer enters the .
- ///
- /// The event args.
+ ///
protected override void OnPointerEnter(PointerEventArgs e)
{
base.OnPointerEnter(e);
- var menu = Parent as Menu;
-
- if (menu != null)
- {
- if (menu.IsOpen)
- {
- IsSubMenuOpen = true;
- }
- }
- else if (HasSubMenu && !IsSubMenuOpen)
+ RaiseEvent(new PointerEventArgs
{
- _submenuTimer = DispatcherTimer.Run(
- () => IsSubMenuOpen = true,
- TimeSpan.FromMilliseconds(400));
- }
- else
- {
- var parentItem = Parent as MenuItem;
- if (parentItem != null)
- {
- foreach (var sibling in parentItem.Items
- .OfType()
- .Where(x => x != this && x.IsSubMenuOpen))
- {
- sibling.CloseSubmenus();
- sibling.IsSubMenuOpen = false;
- sibling.IsSelected = false;
- }
- }
- }
+ Device = e.Device,
+ RoutedEvent = PointerEnterItemEvent,
+ Source = this,
+ });
}
- ///
- /// Called when the pointer leaves the .
- ///
- /// The event args.
+ ///
protected override void OnPointerLeave(PointerEventArgs e)
{
base.OnPointerLeave(e);
- if (_submenuTimer != null)
+ RaiseEvent(new PointerEventArgs
{
- _submenuTimer.Dispose();
- _submenuTimer = null;
- }
- }
-
- ///
- /// Called when the pointer is pressed over the .
- ///
- /// The event args.
- protected override void OnPointerPressed(PointerPressedEventArgs e)
- {
- base.OnPointerPressed(e);
-
- if (!HasSubMenu)
- {
- RaiseEvent(new RoutedEventArgs(ClickEvent));
- }
- else if (IsTopLevel)
- {
- IsSubMenuOpen = !IsSubMenuOpen;
- }
- else
- {
- IsSubMenuOpen = true;
- }
-
- e.Handled = true;
+ Device = e.Device,
+ RoutedEvent = PointerLeaveItemEvent,
+ Source = this,
+ });
}
///
@@ -374,7 +342,7 @@ namespace Avalonia.Controls
if (menuItem != null && menuItem.Parent == this)
{
- foreach (var child in Items.OfType())
+ foreach (var child in ((IMenuItem)this).SubItems)
{
if (child != menuItem && child.IsSubMenuOpen)
{
@@ -395,31 +363,12 @@ namespace Avalonia.Controls
_popup.Closed += PopupClosed;
}
- ///
- /// Called when the menu item's access key is pressed.
- ///
- /// The event args.
- private void AccessKeyPressed(RoutedEventArgs e)
- {
- if (HasSubMenu)
- {
- SelectedIndex = 0;
- IsSubMenuOpen = true;
- }
- else
- {
- RaiseEvent(new RoutedEventArgs(ClickEvent));
- }
-
- e.Handled = true;
- }
-
///
/// Closes all submenus of the menu item.
///
private void CloseSubmenus()
{
- foreach (var child in Items.OfType())
+ foreach (var child in ((IMenuItem)this).SubItems)
{
child.IsSubMenuOpen = false;
}
@@ -479,6 +428,18 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Called when the property changes.
+ ///
+ /// The property change event.
+ private void IsSelectedChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ if ((bool)e.NewValue)
+ {
+ Focus();
+ }
+ }
+
///
/// Called when the property changes.
///
diff --git a/src/Avalonia.Controls/Mixins/ContentControlMixin.cs b/src/Avalonia.Controls/Mixins/ContentControlMixin.cs
index 95193c0432..c4da00f5d0 100644
--- a/src/Avalonia.Controls/Mixins/ContentControlMixin.cs
+++ b/src/Avalonia.Controls/Mixins/ContentControlMixin.cs
@@ -3,13 +3,13 @@
using System;
using System.Linq;
+using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
-using Avalonia.Styling;
namespace Avalonia.Controls.Mixins
{
@@ -75,6 +75,12 @@ namespace Avalonia.Controls.Mixins
null,
presenter.GetValue(ContentPresenter.ChildProperty));
+ if (subscriptions.Value.TryGetValue(sender, out IDisposable previousSubscription))
+ {
+ subscription = new CompositeDisposable(previousSubscription, subscription);
+ subscriptions.Value.Remove(sender);
+ }
+
subscriptions.Value.Add(sender, subscription);
}
}
diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
index 59d2949b81..de68eb0ab0 100644
--- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
+++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
@@ -526,7 +526,7 @@ namespace Avalonia.Controls
return result;
}
- // Since the conversion from Value to text using a FormartString may not be parsable,
+ // Since the conversion from Value to text using a FormatString may not be parsable,
// we verify that the already existing text is not the exact same value.
var currentValueText = ConvertValueToText();
if (Equals(currentValueText, text))
@@ -571,7 +571,7 @@ namespace Avalonia.Controls
}
///
- /// Called by OnSpin when the spin direction is SpinDirection.Descrease.
+ /// Called by OnSpin when the spin direction is SpinDirection.Decrease.
///
private void OnDecrement()
{
@@ -890,7 +890,7 @@ namespace Avalonia.Controls
if (_isTextChangedFromUI && !parsedTextIsValid)
{
// Text input was made from the user and the text
- // repesents an invalid value. Disable the spinner in this case.
+ // represents an invalid value. Disable the spinner in this case.
if (Spinner != null)
{
Spinner.ValidSpinDirection = ValidSpinDirections.None;
@@ -995,4 +995,4 @@ namespace Avalonia.Controls
return false;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
new file mode 100644
index 0000000000..a44495b90c
--- /dev/null
+++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
@@ -0,0 +1,459 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Interactivity;
+using Avalonia.LogicalTree;
+using Avalonia.Rendering;
+using Avalonia.Threading;
+
+namespace Avalonia.Controls.Platform
+{
+ ///
+ /// Provides the default keyboard and pointer interaction for menus.
+ ///
+ public class DefaultMenuInteractionHandler : IMenuInteractionHandler
+ {
+ private IDisposable _inputManagerSubscription;
+ private IRenderRoot _root;
+
+ public DefaultMenuInteractionHandler()
+ : this(Input.InputManager.Instance, DefaultDelayRun)
+ {
+ }
+
+ public DefaultMenuInteractionHandler(
+ IInputManager inputManager,
+ Action delayRun)
+ {
+ InputManager = inputManager;
+ DelayRun = delayRun;
+ }
+
+ public virtual void Attach(IMenu menu)
+ {
+ if (Menu != null)
+ {
+ throw new NotSupportedException("DefaultMenuInteractionHandler is already attached.");
+ }
+
+ Menu = menu;
+ Menu.GotFocus += GotFocus;
+ Menu.LostFocus += LostFocus;
+ Menu.KeyDown += KeyDown;
+ Menu.PointerPressed += PointerPressed;
+ Menu.PointerReleased += PointerReleased;
+ Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
+ Menu.AddHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened);
+ Menu.AddHandler(MenuItem.PointerEnterItemEvent, PointerEnter);
+ Menu.AddHandler(MenuItem.PointerLeaveItemEvent, PointerLeave);
+
+ _root = Menu.VisualRoot;
+
+ if (_root is InputElement inputRoot)
+ {
+ inputRoot.AddHandler(InputElement.PointerPressedEvent, RootPointerPressed, RoutingStrategies.Tunnel);
+ }
+
+ if (_root is WindowBase window)
+ {
+ window.Deactivated += WindowDeactivated;
+ }
+
+ _inputManagerSubscription = InputManager.Process.Subscribe(RawInput);
+ }
+
+ public virtual void Detach(IMenu menu)
+ {
+ if (Menu != menu)
+ {
+ throw new NotSupportedException("DefaultMenuInteractionHandler is not attached to the menu.");
+ }
+
+ Menu.GotFocus -= GotFocus;
+ Menu.LostFocus -= LostFocus;
+ Menu.KeyDown -= KeyDown;
+ Menu.PointerPressed -= PointerPressed;
+ Menu.PointerReleased -= PointerReleased;
+ Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
+ Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened);
+ Menu.RemoveHandler(MenuItem.PointerEnterItemEvent, PointerEnter);
+ Menu.RemoveHandler(MenuItem.PointerLeaveItemEvent, PointerLeave);
+
+ if (_root is InputElement inputRoot)
+ {
+ inputRoot.RemoveHandler(InputElement.PointerPressedEvent, RootPointerPressed);
+ }
+
+ if (_root is WindowBase root)
+ {
+ root.Deactivated -= WindowDeactivated;
+ }
+
+ _inputManagerSubscription.Dispose();
+
+ Menu = null;
+ _root = null;
+ }
+
+ protected Action DelayRun { get; }
+
+ protected IInputManager InputManager { get; }
+
+ protected IMenu Menu { get; private set; }
+
+ protected static TimeSpan MenuShowDelay { get; } = TimeSpan.FromMilliseconds(400);
+
+ protected internal virtual void GotFocus(object sender, GotFocusEventArgs e)
+ {
+ var item = GetMenuItem(e.Source as IControl);
+
+ if (item?.Parent != null)
+ {
+ item.SelectedItem = item;
+ }
+ }
+
+ protected internal virtual void LostFocus(object sender, RoutedEventArgs e)
+ {
+ var item = GetMenuItem(e.Source as IControl);
+
+ if (item != null)
+ {
+ item.SelectedItem = null;
+ }
+ }
+
+ protected internal virtual void KeyDown(object sender, KeyEventArgs e)
+ {
+ var item = GetMenuItem(e.Source as IControl);
+
+ if (item != null)
+ {
+ KeyDown(item, e);
+ }
+ }
+
+ protected internal virtual void KeyDown(IMenuItem item, KeyEventArgs e)
+ {
+ Contract.Requires(item != null);
+
+ switch (e.Key)
+ {
+ case Key.Up:
+ case Key.Down:
+ if (item.IsTopLevel)
+ {
+ if (item.HasSubMenu && !item.IsSubMenuOpen)
+ {
+ Open(item, true);
+ e.Handled = true;
+ }
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ case Key.Left:
+ if (item.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen)
+ {
+ parent.Close();
+ parent.Focus();
+ e.Handled = true;
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ case Key.Right:
+ if (!item.IsTopLevel && item.HasSubMenu)
+ {
+ Open(item, true);
+ e.Handled = true;
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ case Key.Enter:
+ if (!item.HasSubMenu)
+ {
+ Click(item);
+ }
+ else
+ {
+ Open(item, true);
+ }
+
+ e.Handled = true;
+ break;
+
+ case Key.Escape:
+ if (item.Parent != null)
+ {
+ item.Parent.Close();
+ item.Parent.Focus();
+ e.Handled = true;
+ }
+ break;
+
+ default:
+ var direction = e.Key.ToNavigationDirection();
+
+ if (direction.HasValue && item.Parent?.MoveSelection(direction.Value, true) == true)
+ {
+ // If the the parent is an IMenu which successfully moved its selection,
+ // and the current menu is open then close the current menu and open the
+ // new menu.
+ if (item.IsSubMenuOpen && item.Parent is IMenu)
+ {
+ item.Close();
+ Open(item.Parent.SelectedItem, true);
+ }
+ e.Handled = true;
+ }
+
+ break;
+ }
+
+ if (!e.Handled && item.Parent is IMenuItem parentItem)
+ {
+ KeyDown(parentItem, e);
+ }
+ }
+
+ protected internal virtual void AccessKeyPressed(object sender, RoutedEventArgs e)
+ {
+ var item = GetMenuItem(e.Source as IControl);
+
+ if (item == null)
+ {
+ return;
+ }
+
+ if (item.HasSubMenu)
+ {
+ Open(item, true);
+ }
+ else
+ {
+ Click(item);
+ }
+
+ e.Handled = true;
+ }
+
+ protected internal virtual void PointerEnter(object sender, PointerEventArgs e)
+ {
+ var item = GetMenuItem(e.Source as IControl);
+
+ if (item?.Parent == null)
+ {
+ return;
+ }
+
+ if (item.IsTopLevel)
+ {
+ if (item.Parent.SelectedItem?.IsSubMenuOpen == true)
+ {
+ item.Parent.SelectedItem.Close();
+ SelectItemAndAncestors(item);
+ Open(item, false);
+ }
+ else
+ {
+ SelectItemAndAncestors(item);
+ }
+ }
+ else
+ {
+ SelectItemAndAncestors(item);
+
+ if (item.HasSubMenu)
+ {
+ OpenWithDelay(item);
+ }
+ else if (item.Parent != null)
+ {
+ foreach (var sibling in item.Parent.SubItems)
+ {
+ if (sibling.IsSubMenuOpen)
+ {
+ CloseWithDelay(sibling);
+ }
+ }
+ }
+ }
+ }
+
+ protected internal virtual void PointerLeave(object sender, PointerEventArgs e)
+ {
+ var item = GetMenuItem(e.Source as IControl);
+
+ if (item?.Parent == null)
+ {
+ return;
+ }
+
+ if (item.Parent.SelectedItem == item)
+ {
+ if (item.IsTopLevel)
+ {
+ if (!((IMenu)item.Parent).IsOpen)
+ {
+ item.Parent.SelectedItem = null;
+ }
+ }
+ else if (!item.HasSubMenu)
+ {
+ item.Parent.SelectedItem = null;
+ }
+ }
+ }
+
+ protected internal virtual void PointerPressed(object sender, PointerPressedEventArgs e)
+ {
+ var item = GetMenuItem(e.Source as IControl);
+
+ if (e.MouseButton == MouseButton.Left && item?.HasSubMenu == true)
+ {
+ Open(item, false);
+ e.Handled = true;
+ }
+ }
+
+ protected internal virtual void PointerReleased(object sender, PointerReleasedEventArgs e)
+ {
+ var item = GetMenuItem(e.Source as IControl);
+
+ if (e.MouseButton == MouseButton.Left && item.HasSubMenu == false)
+ {
+ Click(item);
+ e.Handled = true;
+ }
+ }
+
+ protected internal virtual void MenuOpened(object sender, RoutedEventArgs e)
+ {
+ if (e.Source == Menu)
+ {
+ Menu.MoveSelection(NavigationDirection.First, true);
+ }
+ }
+
+ protected internal virtual void RawInput(RawInputEventArgs e)
+ {
+ var mouse = e as RawMouseEventArgs;
+
+ if (mouse?.Type == RawMouseEventType.NonClientLeftButtonDown)
+ {
+ Menu.Close();
+ }
+ }
+
+ protected internal virtual void RootPointerPressed(object sender, PointerPressedEventArgs e)
+ {
+ if (Menu?.IsOpen == true)
+ {
+ var control = e.Source as ILogical;
+
+ if (!Menu.IsLogicalParentOf(control))
+ {
+ Menu.Close();
+ }
+ }
+ }
+
+ protected internal virtual void WindowDeactivated(object sender, EventArgs e)
+ {
+ Menu.Close();
+ }
+
+ protected void Click(IMenuItem item)
+ {
+ item.RaiseClick();
+ CloseMenu(item);
+ }
+
+ protected void CloseMenu(IMenuItem item)
+ {
+ var current = (IMenuElement)item;
+
+ while (current != null && !(current is IMenu))
+ {
+ current = (current as IMenuItem)?.Parent;
+ }
+
+ current?.Close();
+ }
+
+ protected void CloseWithDelay(IMenuItem item)
+ {
+ void Execute()
+ {
+ if (item.Parent?.SelectedItem != item)
+ {
+ item.Close();
+ }
+ }
+
+ DelayRun(Execute, MenuShowDelay);
+ }
+
+ protected void Open(IMenuItem item, bool selectFirst)
+ {
+ item.Open();
+
+ if (selectFirst)
+ {
+ item.MoveSelection(NavigationDirection.First, true);
+ }
+ }
+
+ protected void OpenWithDelay(IMenuItem item)
+ {
+ void Execute()
+ {
+ if (item.Parent?.SelectedItem == item)
+ {
+ Open(item, false);
+ }
+ }
+
+ DelayRun(Execute, MenuShowDelay);
+ }
+
+ protected void SelectItemAndAncestors(IMenuItem item)
+ {
+ var current = item;
+
+ while (current?.Parent != null)
+ {
+ current.Parent.SelectedItem = current;
+ current = current.Parent as IMenuItem;
+ }
+ }
+
+ protected static IMenuItem GetMenuItem(IControl item)
+ {
+ while (true)
+ {
+ if (item == null)
+ return null;
+ if (item is IMenuItem menuItem)
+ return menuItem;
+ item = item.Parent;
+ }
+ }
+
+ private static void DefaultDelayRun(Action action, TimeSpan timeSpan)
+ {
+ DispatcherTimer.RunOnce(action, timeSpan);
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Platform/ExportWindowingSubsystemAttribute.cs b/src/Avalonia.Controls/Platform/ExportWindowingSubsystemAttribute.cs
index 420c56111f..e958b7aa15 100644
--- a/src/Avalonia.Controls/Platform/ExportWindowingSubsystemAttribute.cs
+++ b/src/Avalonia.Controls/Platform/ExportWindowingSubsystemAttribute.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Avalonia.Platform
{
diff --git a/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs
new file mode 100644
index 0000000000..dd8503f768
--- /dev/null
+++ b/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs
@@ -0,0 +1,19 @@
+namespace Avalonia.Controls.Platform
+{
+ ///
+ /// Handles user interaction for menus.
+ ///
+ public interface IMenuInteractionHandler
+ {
+ ///
+ /// Attaches the interaction handler to a menu.
+ ///
+ /// The menu.
+ void Attach(IMenu menu);
+
+ ///
+ /// Detaches the interaction handler from the attached menu.
+ ///
+ void Detach(IMenu menu);
+ }
+}
diff --git a/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs b/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs
index a84a58906e..ecbc6d2234 100644
--- a/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs
+++ b/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs
@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Avalonia.Media.Imaging;
+using System.IO;
namespace Avalonia.Platform
{
diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
index 9ba68f584e..e788b3c73e 100644
--- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Platform
public interface IWindowBaseImpl : ITopLevelImpl
{
///
- /// Shows the toplevel.
+ /// Shows the top level.
///
void Show();
@@ -62,7 +62,7 @@ namespace Avalonia.Platform
Size MaxClientSize { get; }
///
- /// Sets the client size of the toplevel.
+ /// Sets the client size of the top level.
///
void Resize(Size clientSize);
@@ -82,4 +82,4 @@ namespace Avalonia.Platform
///
IScreenImpl Screen { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs
index 5dcd0a39e8..5c2c1a8da3 100644
--- a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs
+++ b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs
@@ -1,9 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
namespace Avalonia.Platform
{
public interface IWindowingPlatform
diff --git a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
index 4dc96a074d..62cd012d51 100644
--- a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
+++ b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Avalonia.Platform;
+using Avalonia.Platform;
namespace Avalonia.Controls.Platform.Surfaces
{
diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
index 1d5a187a73..ba2c7c91d8 100644
--- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
@@ -6,7 +6,6 @@ using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Animation;
-using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Data;
@@ -251,4 +250,4 @@ namespace Avalonia.Controls.Presenters
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Presenters/IPresenter.cs b/src/Avalonia.Controls/Presenters/IPresenter.cs
index e06be4e82b..f78ba4c19d 100644
--- a/src/Avalonia.Controls/Presenters/IPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/IPresenter.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Primitives;
-using Avalonia.Styling;
namespace Avalonia.Controls.Presenters
{
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
index e293cff211..c5344b29d9 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
@@ -8,7 +8,6 @@ using System.Reactive.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
-using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters
{
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index b98f26b87f..f31a48b2a0 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
@@ -390,7 +390,7 @@ namespace Avalonia.Controls.Presenters
/// The delta of the move.
///
/// If the move is less than a page, then this method moves the containers for the items
- /// that are still visible to the correct place, and recyles and moves the others. For
+ /// that are still visible to the correct place, and recycles and moves the others. For
/// example: if there are 20 items and 10 containers visible and the user scrolls 5
/// items down, then the bottom 5 containers will be moved to the top and the top 5 will
/// be moved to the bottom and recycled to display the newly visible item. Updates
diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
index f8d62a1cbf..500c7aa187 100644
--- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
@@ -143,13 +143,6 @@ namespace Avalonia.Controls.Presenters
Virtualizer = ItemVirtualizer.Create(this);
((ILogicalScrollable)this).InvalidateScroll?.Invoke();
- if (!Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty))
- {
- KeyboardNavigation.SetDirectionalNavigation(
- (InputElement)Panel,
- KeyboardNavigationMode.Contained);
- }
-
KeyboardNavigation.SetTabNavigation(
(InputElement)Panel,
KeyboardNavigation.GetTabNavigation(this));
@@ -175,4 +168,4 @@ namespace Avalonia.Controls.Presenters
((ILogicalScrollable)this).InvalidateScroll?.Invoke();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
index 8d0c6f16cb..2ef7941b55 100644
--- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
@@ -7,7 +7,6 @@ using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
-using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters
@@ -319,4 +318,4 @@ namespace Avalonia.Controls.Presenters
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index 636c836da5..3181e76c3f 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -4,7 +4,6 @@
using System;
using System.Reactive.Linq;
using Avalonia.Media;
-using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.VisualTree;
@@ -116,7 +115,7 @@ namespace Avalonia.Controls.Presenters
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
- // issue #600: set constaint before any FormattedText manipulation
+ // issue #600: set constraint before any FormattedText manipulation
// see base.Render(...) implementation
FormattedText.Constraint = Bounds.Size;
diff --git a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
index 82ab929d53..c5aa73e56a 100644
--- a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
@@ -1,8 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Linq;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 005717d681..5e79711f5a 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -6,11 +6,10 @@ using System.Linq;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
+using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
-using Avalonia.Rendering;
using Avalonia.VisualTree;
-using Avalonia.Layout;
namespace Avalonia.Controls.Primitives
{
@@ -473,4 +472,4 @@ namespace Avalonia.Controls.Primitives
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index 0ae4be5550..fdec9febd3 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -2,12 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Linq;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Presenters;
using Avalonia.Interactivity;
-using Avalonia.Layout;
-using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs
index b46562e99e..76df94cdb8 100644
--- a/src/Avalonia.Controls/Primitives/RangeBase.cs
+++ b/src/Avalonia.Controls/Primitives/RangeBase.cs
@@ -130,7 +130,7 @@ namespace Avalonia.Controls.Primitives
}
///
- /// Throws an exception if the double valus is NaN or Inf.
+ /// Throws an exception if the double value is NaN or Inf.
///
/// The value.
/// The name of the property being set.
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index c8425a0f80..bb39b005cc 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -11,7 +11,6 @@ using Avalonia.Controls.Generators;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
-using Avalonia.Metadata;
using Avalonia.Styling;
using Avalonia.VisualTree;
@@ -457,6 +456,42 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
+ /// Moves the selection in the specified direction relative to the current selection.
+ ///
+ /// The direction to move.
+ /// Whether to wrap when the selection reaches the first or last item.
+ /// True if the selection was moved; otherwise false.
+ protected bool MoveSelection(NavigationDirection direction, bool wrap)
+ {
+ var from = SelectedIndex != -1 ? ItemContainerGenerator.ContainerFromIndex(SelectedIndex) : null;
+ return MoveSelection(from, direction, wrap);
+ }
+
+ ///
+ /// Moves the selection in the specified direction relative to the specified container.
+ ///
+ /// The container which serves as a starting point for the movement.
+ /// The direction to move.
+ /// Whether to wrap when the selection reaches the first or last item.
+ /// True if the selection was moved; otherwise false.
+ protected bool MoveSelection(IControl from, NavigationDirection direction, bool wrap)
+ {
+ if (Presenter?.Panel is INavigableContainer container &&
+ GetNextControl(container, direction, from, wrap) is IControl next)
+ {
+ var index = ItemContainerGenerator.IndexFromContainer(next);
+
+ if (index != -1)
+ {
+ SelectedIndex = index;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
///
/// Updates the selection for an item based on user interaction.
///
diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs
index b0f9ab1f85..b01ddd5dba 100644
--- a/src/Avalonia.Controls/Primitives/Thumb.cs
+++ b/src/Avalonia.Controls/Primitives/Thumb.cs
@@ -4,7 +4,6 @@
using System;
using Avalonia.Input;
using Avalonia.Interactivity;
-using Avalonia.Layout;
namespace Avalonia.Controls.Primitives
{
diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs
index b7db352c74..085db75ce1 100644
--- a/src/Avalonia.Controls/ProgressBar.cs
+++ b/src/Avalonia.Controls/ProgressBar.cs
@@ -1,12 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Reactive.Linq;
-using Avalonia.Animation;
using Avalonia.Controls.Primitives;
-using Avalonia.Layout;
namespace Avalonia.Controls
{
diff --git a/src/Avalonia.Controls/Remote/RemoteServer.cs b/src/Avalonia.Controls/Remote/RemoteServer.cs
index 9c0f3464a5..e116316904 100644
--- a/src/Avalonia.Controls/Remote/RemoteServer.cs
+++ b/src/Avalonia.Controls/Remote/RemoteServer.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Avalonia.Controls.Embedding;
using Avalonia.Controls.Remote.Server;
using Avalonia.Platform;
diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs
index ea8c3ebe52..2d4f2e6b52 100644
--- a/src/Avalonia.Controls/Remote/RemoteWidget.cs
+++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs
@@ -1,6 +1,5 @@
using System;
using System.Runtime.InteropServices;
-using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Remote.Protocol;
@@ -76,4 +75,4 @@ namespace Avalonia.Controls.Remote
base.Render(context);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
index cf4cec9268..b302f2f5ec 100644
--- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
using Avalonia.Controls.Embedding.Offscreen;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
@@ -94,10 +91,10 @@ namespace Avalonia.Controls.Remote.Server
RenderIfNeeded();
}
- protected virtual Size Measure(Size constaint)
+ protected virtual Size Measure(Size constraint)
{
var l = (ILayoutable) InputRoot;
- l.Measure(constaint);
+ l.Measure(constraint);
return l.DesiredSize;
}
diff --git a/src/Avalonia.Controls/Separator.cs b/src/Avalonia.Controls/Separator.cs
index 2028a5cfbb..84b2a33d7b 100644
--- a/src/Avalonia.Controls/Separator.cs
+++ b/src/Avalonia.Controls/Separator.cs
@@ -1,11 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls
diff --git a/src/Avalonia.Controls/Shapes/Path.cs b/src/Avalonia.Controls/Shapes/Path.cs
index 08bed79b3a..e0952d3e9b 100644
--- a/src/Avalonia.Controls/Shapes/Path.cs
+++ b/src/Avalonia.Controls/Shapes/Path.cs
@@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
using Avalonia.Media;
namespace Avalonia.Controls.Shapes
diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs
index a1848a95b1..604051ef28 100644
--- a/src/Avalonia.Controls/Shapes/Shape.cs
+++ b/src/Avalonia.Controls/Shapes/Shape.cs
@@ -4,7 +4,6 @@
using System;
using System.Reflection;
using Avalonia.Collections;
-using Avalonia.Controls;
using Avalonia.Media;
namespace Avalonia.Controls.Shapes
diff --git a/src/Avalonia.Controls/Spinner.cs b/src/Avalonia.Controls/Spinner.cs
index e00ff3823c..5ee13f45c4 100644
--- a/src/Avalonia.Controls/Spinner.cs
+++ b/src/Avalonia.Controls/Spinner.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Text;
using Avalonia.Interactivity;
namespace Avalonia.Controls
@@ -54,7 +52,7 @@ namespace Avalonia.Controls
public SpinDirection Direction { get; }
///
- /// Get or set whheter the spin event originated from a mouse wheel event.
+ /// Get or set whether the spin event originated from a mouse wheel event.
///
public bool UsingMouseWheel{ get; }
diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs
index b0ccd8a3d1..645cdbd926 100644
--- a/src/Avalonia.Controls/StackPanel.cs
+++ b/src/Avalonia.Controls/StackPanel.cs
@@ -56,11 +56,49 @@ namespace Avalonia.Controls
///
/// The movement direction.
/// The control from which movement begins.
+ /// Whether to wrap around when the first or last item is reached.
/// The control.
- IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from)
+ IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap)
{
- var fromControl = from as IControl;
- return (fromControl != null) ? GetControlInDirection(direction, fromControl) : null;
+ var result = GetControlInDirection(direction, from as IControl);
+
+ if (result == null && wrap)
+ {
+ if (Orientation == Orientation.Vertical)
+ {
+ switch (direction)
+ {
+ case NavigationDirection.Up:
+ case NavigationDirection.Previous:
+ case NavigationDirection.PageUp:
+ result = GetControlInDirection(NavigationDirection.Last, null);
+ break;
+ case NavigationDirection.Down:
+ case NavigationDirection.Next:
+ case NavigationDirection.PageDown:
+ result = GetControlInDirection(NavigationDirection.First, null);
+ break;
+ }
+ }
+ else
+ {
+ switch (direction)
+ {
+ case NavigationDirection.Left:
+ case NavigationDirection.Previous:
+ case NavigationDirection.PageUp:
+ result = GetControlInDirection(NavigationDirection.Last, null);
+ break;
+ case NavigationDirection.Right:
+ case NavigationDirection.Next:
+ case NavigationDirection.PageDown:
+ result = GetControlInDirection(NavigationDirection.First, null);
+ break;
+ }
+ }
+ }
+
+ return result;
}
///
@@ -72,7 +110,7 @@ namespace Avalonia.Controls
protected virtual IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{
var horiz = Orientation == Orientation.Horizontal;
- int index = Children.IndexOf((IControl)from);
+ int index = from != null ? Children.IndexOf(from) : -1;
switch (direction)
{
@@ -83,22 +121,22 @@ namespace Avalonia.Controls
index = Children.Count - 1;
break;
case NavigationDirection.Next:
- ++index;
+ if (index != -1) ++index;
break;
case NavigationDirection.Previous:
- --index;
+ if (index != -1) --index;
break;
case NavigationDirection.Left:
- index = horiz ? index - 1 : -1;
+ if (index != -1) index = horiz ? index - 1 : -1;
break;
case NavigationDirection.Right:
- index = horiz ? index + 1 : -1;
+ if (index != -1) index = horiz ? index + 1 : -1;
break;
case NavigationDirection.Up:
- index = horiz ? -1 : index - 1;
+ if (index != -1) index = horiz ? -1 : index - 1;
break;
case NavigationDirection.Down:
- index = horiz ? -1 : index + 1;
+ if (index != -1) index = horiz ? -1 : index + 1;
break;
default:
index = -1;
diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs
index 7154c0e558..9339aa6924 100644
--- a/src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs
+++ b/src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs
@@ -42,7 +42,7 @@ namespace Avalonia.Controls.Templates
}
///
- /// Casts a stongly typed match function to a weakly typed one.
+ /// Casts a strongly typed match function to a weakly typed one.
///
/// The strongly typed function.
/// The weakly typed function.
@@ -52,7 +52,7 @@ namespace Avalonia.Controls.Templates
}
///
- /// Casts a stongly typed build function to a weakly typed one.
+ /// Casts a strongly typed build function to a weakly typed one.
///
/// The strong data type.
/// The strongly typed function.
@@ -62,4 +62,4 @@ namespace Avalonia.Controls.Templates
return o => f((T)o);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Templates/FuncMemberSelector.cs b/src/Avalonia.Controls/Templates/FuncMemberSelector.cs
index b8f2c2e311..5ab186261e 100644
--- a/src/Avalonia.Controls/Templates/FuncMemberSelector.cs
+++ b/src/Avalonia.Controls/Templates/FuncMemberSelector.cs
@@ -25,11 +25,11 @@ namespace Avalonia.Controls.Templates
///
/// Selects a member of an object.
///
- /// The obeject.
+ /// The object.
/// The selected member.
public object Select(object o)
{
return (o is TObject) ? _selector((TObject)o) : default(TMember);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
index 2e55dd3552..e7c9cf8608 100644
--- a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
+++ b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
@@ -9,7 +9,7 @@ using Avalonia.Data;
namespace Avalonia.Controls.Templates
{
///
- /// A template used to build hierachical data.
+ /// A template used to build hierarchical data.
///
public class FuncTreeDataTemplate : FuncDataTemplate, ITreeDataTemplate
{
diff --git a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs
index d7d6a87769..4ca96f60bd 100644
--- a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs
+++ b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs
@@ -7,7 +7,7 @@ using System.Collections;
namespace Avalonia.Controls.Templates
{
///
- /// A template used to build hierachical data.
+ /// A template used to build hierarchical data.
///
/// The type of the template's data.
public class FuncTreeDataTemplate : FuncTreeDataTemplate
diff --git a/src/Avalonia.Controls/Templates/IDataTemplateHost.cs b/src/Avalonia.Controls/Templates/IDataTemplateHost.cs
index 5cc12581d4..267938b3bd 100644
--- a/src/Avalonia.Controls/Templates/IDataTemplateHost.cs
+++ b/src/Avalonia.Controls/Templates/IDataTemplateHost.cs
@@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
namespace Avalonia.Controls.Templates
{
diff --git a/src/Avalonia.Controls/Templates/IMemberSelector.cs b/src/Avalonia.Controls/Templates/IMemberSelector.cs
index b7172fa492..e1ec42a849 100644
--- a/src/Avalonia.Controls/Templates/IMemberSelector.cs
+++ b/src/Avalonia.Controls/Templates/IMemberSelector.cs
@@ -11,8 +11,8 @@ namespace Avalonia.Controls.Templates
///
/// Selects a member of an object.
///
- /// The obeject.
+ /// The object.
/// The selected member.
object Select(object o);
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Templates/ITreeDataTemplate.cs b/src/Avalonia.Controls/Templates/ITreeDataTemplate.cs
index 580839be97..9ed8987681 100644
--- a/src/Avalonia.Controls/Templates/ITreeDataTemplate.cs
+++ b/src/Avalonia.Controls/Templates/ITreeDataTemplate.cs
@@ -6,7 +6,7 @@ using Avalonia.Data;
namespace Avalonia.Controls.Templates
{
///
- /// Interface representing a template used to build hierachical data.
+ /// Interface representing a template used to build hierarchical data.
///
public interface ITreeDataTemplate : IDataTemplate
{
@@ -20,4 +20,4 @@ namespace Avalonia.Controls.Templates
///
InstancedBinding ItemsSelector(object item);
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index fd46efa76f..e91d2e8fa7 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -21,7 +21,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty BackgroundProperty =
Border.BackgroundProperty.AddOwner();
- // TODO: Define these attached properties elswhere (e.g. on a Text class) and AddOwner
+ // TODO: Define these attached properties elsewhere (e.g. on a Text class) and AddOwner
// them into TextBlock.
///
@@ -406,4 +406,4 @@ namespace Avalonia.Controls
InvalidateMeasure();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs
index 10e964d014..28d1ba5e0f 100644
--- a/src/Avalonia.Controls/ToolTip.cs
+++ b/src/Avalonia.Controls/ToolTip.cs
@@ -55,7 +55,7 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterAttached("ShowDelay", 400);
///
- /// Stores the curernt instance in the control.
+ /// Stores the current instance in the control.
///
private static readonly AttachedProperty ToolTipProperty =
AvaloniaProperty.RegisterAttached("ToolTip");
diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs
index bfd7ef0f33..384a9db0cf 100644
--- a/src/Avalonia.Controls/ToolTipService.cs
+++ b/src/Avalonia.Controls/ToolTipService.cs
@@ -5,7 +5,7 @@ using Avalonia.Threading;
namespace Avalonia.Controls
{
///
- /// Handeles interaction with controls.
+ /// Handles interaction with controls.
///
internal sealed class ToolTipService
{
@@ -95,4 +95,4 @@ namespace Avalonia.Controls
_timer = null;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 9fdc097c3f..1161ded25f 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -2,9 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Reactive.Disposables;
using System.Reactive.Linq;
-using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.Raw;
@@ -225,7 +223,7 @@ namespace Avalonia.Controls
protected virtual IRenderTarget CreateRenderTarget()
{
if(PlatformImpl == null)
- throw new InvalidOperationException("Cann't create render target, PlatformImpl is null (might be already disposed)");
+ throw new InvalidOperationException("Can't create render target, PlatformImpl is null (might be already disposed)");
return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces);
}
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 2e1c011685..3003dab85e 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
@@ -14,7 +13,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls
{
///
- /// Displays a hierachical tree of data.
+ /// Displays a hierarchical tree of data.
///
public class TreeView : ItemsControl, ICustomKeyboardNavigation
{
@@ -136,6 +135,92 @@ namespace Avalonia.Controls
}
}
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ var direction = e.Key.ToNavigationDirection();
+
+ if (direction?.IsDirectional() == true && !e.Handled)
+ {
+ if (SelectedItem != null)
+ {
+ var next = GetContainerInDirection(
+ GetContainerFromEventSource(e.Source) as TreeViewItem,
+ direction.Value,
+ true);
+
+ if (next != null)
+ {
+ FocusManager.Instance.Focus(next, NavigationMethod.Directional);
+ e.Handled = true;
+ }
+ }
+ else
+ {
+ SelectedItem = ElementAt(Items, 0);
+ }
+ }
+ }
+
+ private TreeViewItem GetContainerInDirection(
+ TreeViewItem from,
+ NavigationDirection direction,
+ bool intoChildren)
+ {
+ IItemContainerGenerator parentGenerator;
+
+ if (from?.Parent is TreeView treeView)
+ {
+ parentGenerator = treeView.ItemContainerGenerator;
+ }
+ else if (from?.Parent is TreeViewItem item)
+ {
+ parentGenerator = item.ItemContainerGenerator;
+ }
+ else
+ {
+ return null;
+ }
+
+ var index = parentGenerator.IndexFromContainer(from);
+ var parent = from.Parent as ItemsControl;
+ TreeViewItem result = null;
+
+ switch (direction)
+ {
+ case NavigationDirection.Up:
+ if (index > 0)
+ {
+ var previous = (TreeViewItem)parentGenerator.ContainerFromIndex(index - 1);
+ result = previous.IsExpanded ?
+ (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1) :
+ previous;
+ }
+ else
+ {
+ result = from.Parent as TreeViewItem;
+ }
+
+ break;
+
+ case NavigationDirection.Down:
+ if (from.IsExpanded && intoChildren)
+ {
+ result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0);
+ }
+ else if (index < parent?.ItemCount - 1)
+ {
+ result = (TreeViewItem)parentGenerator.ContainerFromIndex(index + 1);
+ }
+ else if (parent is TreeViewItem parentItem)
+ {
+ return GetContainerInDirection(parentItem, direction, false);
+ }
+ break;
+ }
+
+ return result;
+ }
+
///
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index bed27ef033..0886c05038 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -32,10 +32,7 @@ namespace Avalonia.Controls
ListBoxItem.IsSelectedProperty.AddOwner();
private static readonly ITemplate DefaultPanel =
- new FuncTemplate(() => new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- });
+ new FuncTemplate(() => new StackPanel());
private TreeView _treeView;
private bool _isExpanded;
@@ -127,7 +124,7 @@ namespace Avalonia.Controls
}
}
- base.OnKeyDown(e);
+ // Don't call base.OnKeyDown - let events bubble up to containing TreeView.
}
}
}
diff --git a/src/Avalonia.Controls/Utils/AncestorFinder.cs b/src/Avalonia.Controls/Utils/AncestorFinder.cs
index 6ee284e05d..d02202ca02 100644
--- a/src/Avalonia.Controls/Utils/AncestorFinder.cs
+++ b/src/Avalonia.Controls/Utils/AncestorFinder.cs
@@ -1,13 +1,10 @@
-using System;
-using System.Collections.Generic;
+using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
namespace Avalonia.Controls.Utils
{
diff --git a/src/Avalonia.Controls/Utils/GridLayout.cs b/src/Avalonia.Controls/Utils/GridLayout.cs
index 10a94a8c82..363428b289 100644
--- a/src/Avalonia.Controls/Utils/GridLayout.cs
+++ b/src/Avalonia.Controls/Utils/GridLayout.cs
@@ -376,7 +376,7 @@ namespace Avalonia.Controls.Utils
// 1. Determine all one-span column's desired widths or row's desired heights.
// 2. Order the multi-span conventions by its last index
// (Notice that the sorted data is much smaller than the source.)
- // 3. Determine each multi-span last index by calculating the maximun desired size.
+ // 3. Determine each multi-span last index by calculating the maximum desired size.
// Before we determine the behavior of this method, we just aggregate the one-span * columns.
diff --git a/src/Avalonia.Controls/Utils/IEnumerableUtils.cs b/src/Avalonia.Controls/Utils/IEnumerableUtils.cs
index 361857aeb7..40ebd406c3 100644
--- a/src/Avalonia.Controls/Utils/IEnumerableUtils.cs
+++ b/src/Avalonia.Controls/Utils/IEnumerableUtils.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections;
-using System.Globalization;
using System.Linq;
namespace Avalonia.Controls.Utils
diff --git a/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs b/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs
index 43c8a5aa6c..4d814170c6 100644
--- a/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs
+++ b/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs
@@ -4,15 +4,12 @@
// All other rights reserved.
using System;
+using System.Collections;
using System.Linq;
-using System.Collections.Generic;
-using System.Text;
using Avalonia.Controls.Primitives;
-using Avalonia.Interactivity;
using Avalonia.Input;
+using Avalonia.Interactivity;
using Avalonia.LogicalTree;
-using System.Collections;
-using System.Diagnostics;
namespace Avalonia.Controls.Utils
{
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index bd07cf6740..39b4f05545 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -86,7 +86,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register(nameof(Icon));
///
- /// Defines the proeprty.
+ /// Defines the property.
///
public static readonly DirectProperty WindowStartupLocationProperty =
AvaloniaProperty.RegisterDirect(
@@ -100,7 +100,7 @@ namespace Avalonia.Controls
private readonly NameScope _nameScope = new NameScope();
private object _dialogResult;
private readonly Size _maxPlatformClientSize;
- private WindowStartupLocation _windowStartupLoction;
+ private WindowStartupLocation _windowStartupLocation;
///
/// Initializes static members of the class.
@@ -237,8 +237,8 @@ namespace Avalonia.Controls
///
public WindowStartupLocation WindowStartupLocation
{
- get { return _windowStartupLoction; }
- set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLoction, value); }
+ get { return _windowStartupLocation; }
+ set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLocation, value); }
}
///
@@ -409,7 +409,7 @@ namespace Avalonia.Controls
/// The type of the result produced by the dialog.
///
/// .
- /// A task that can be used to retrive the result of the dialog when it closes.
+ /// A task that can be used to retrieve the result of the dialog when it closes.
///
public Task ShowDialog()
{
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index c0b664ebc3..f609432545 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -1,10 +1,7 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Layout;
diff --git a/src/Avalonia.Controls/WindowIcon.cs b/src/Avalonia.Controls/WindowIcon.cs
index 26195e6d76..9e4329372a 100644
--- a/src/Avalonia.Controls/WindowIcon.cs
+++ b/src/Avalonia.Controls/WindowIcon.cs
@@ -1,11 +1,6 @@
-using Avalonia.Platform;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.IO;
using Avalonia.Media.Imaging;
+using Avalonia.Platform;
namespace Avalonia.Controls
{
diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs
index 745de95bca..8ee0636124 100644
--- a/src/Avalonia.Controls/WrapPanel.cs
+++ b/src/Avalonia.Controls/WrapPanel.cs
@@ -47,8 +47,9 @@ namespace Avalonia.Controls
///
/// The movement direction.
/// The control from which movement begins.
+ /// Whether to wrap around when the first or last item is reached.
/// The control.
- IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from)
+ IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap)
{
var horiz = Orientation == Orientation.Horizontal;
int index = Children.IndexOf((IControl)from);
@@ -131,7 +132,7 @@ namespace Avalonia.Controls
double accumulatedV = 0;
var uvFinalSize = CreateUVSize(finalSize);
var lineSize = CreateUVSize();
- int firstChildInLineindex = 0;
+ int firstChildInLineIndex = 0;
for (int index = 0; index < Children.Count; index++)
{
var child = Children[index];
@@ -143,32 +144,32 @@ namespace Avalonia.Controls
}
else // moving to next line
{
- var controlsInLine = GetContolsBetween(firstChildInLineindex, index);
+ var controlsInLine = GetControlsBetween(firstChildInLineIndex, index);
ArrangeLine(accumulatedV, lineSize.V, controlsInLine);
accumulatedV += lineSize.V;
lineSize = childSize;
- firstChildInLineindex = index;
+ firstChildInLineIndex = index;
}
}
- if (firstChildInLineindex < Children.Count)
+ if (firstChildInLineIndex < Children.Count)
{
- var controlsInLine = GetContolsBetween(firstChildInLineindex, Children.Count);
+ var controlsInLine = GetControlsBetween(firstChildInLineIndex, Children.Count);
ArrangeLine(accumulatedV, lineSize.V, controlsInLine);
}
return finalSize;
}
- private IEnumerable GetContolsBetween(int first, int last)
+ private IEnumerable GetControlsBetween(int first, int last)
{
return Children.Skip(first).Take(last - first);
}
- private void ArrangeLine(double v, double lineV, IEnumerable contols)
+ private void ArrangeLine(double v, double lineV, IEnumerable controls)
{
double u = 0;
bool isHorizontal = (Orientation == Orientation.Horizontal);
- foreach (var child in contols)
+ foreach (var child in controls)
{
var childSize = CreateUVSize(child.DesiredSize);
var x = isHorizontal ? u : v;
@@ -180,9 +181,9 @@ namespace Avalonia.Controls
}
}
///
- /// Used to not not write sepearate code for horizontal and vertical orientation.
+ /// Used to not not write separate code for horizontal and vertical orientation.
/// U is direction in line. (x if orientation is horizontal)
- /// V is direction of lines. (y if orientation is horizonral)
+ /// V is direction of lines. (y if orientation is horizontal)
///
[DebuggerDisplay("U = {U} V = {V}")]
private struct UVSize
@@ -250,4 +251,4 @@ namespace Avalonia.Controls
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Input/AccessKeyHandler.cs b/src/Avalonia.Input/AccessKeyHandler.cs
index 7baa4103d7..b78e5a9f98 100644
--- a/src/Avalonia.Input/AccessKeyHandler.cs
+++ b/src/Avalonia.Input/AccessKeyHandler.cs
@@ -53,10 +53,32 @@ namespace Avalonia.Input
///
private IInputElement _restoreFocusElement;
+ ///
+ /// The window's main menu.
+ ///
+ private IMainMenu _mainMenu;
+
///
/// Gets or sets the window's main menu.
///
- public IMainMenu MainMenu { get; set; }
+ public IMainMenu MainMenu
+ {
+ get => _mainMenu;
+ set
+ {
+ if (_mainMenu != null)
+ {
+ _mainMenu.MenuClosed -= MainMenuClosed;
+ }
+
+ _mainMenu = value;
+
+ if (_mainMenu != null)
+ {
+ _mainMenu.MenuClosed += MainMenuClosed;
+ }
+ }
+ }
///
/// Sets the owner of the access key handler.
@@ -160,13 +182,7 @@ namespace Avalonia.Input
{
bool menuIsOpen = MainMenu?.IsOpen == true;
- if (e.Key == Key.Escape && menuIsOpen)
- {
- // When the Escape key is pressed with the main menu open, close it.
- CloseMenu();
- e.Handled = true;
- }
- else if ((e.Modifiers & InputModifiers.Alt) != 0 || menuIsOpen)
+ if ((e.Modifiers & InputModifiers.Alt) != 0 || menuIsOpen)
{
// If any other key is pressed with the Alt key held down, or the main menu is open,
// find all controls who have registered that access key.
@@ -245,5 +261,10 @@ namespace Avalonia.Input
MainMenu.Close();
_owner.ShowAccessKeys = _showingAccessKeys = false;
}
+
+ private void MainMenuClosed(object sender, EventArgs e)
+ {
+ _owner.ShowAccessKeys = false;
+ }
}
}
diff --git a/src/Avalonia.Input/IInputElement.cs b/src/Avalonia.Input/IInputElement.cs
index 5acb6aa777..c9924dbffb 100644
--- a/src/Avalonia.Input/IInputElement.cs
+++ b/src/Avalonia.Input/IInputElement.cs
@@ -15,7 +15,7 @@ namespace Avalonia.Input
///
/// Occurs when the control receives focus.
///
- event EventHandler GotFocus;
+ event EventHandler GotFocus;
///
/// Occurs when the control loses focus.
diff --git a/src/Avalonia.Input/IMainMenu.cs b/src/Avalonia.Input/IMainMenu.cs
index 39d19a0a76..a3373191a8 100644
--- a/src/Avalonia.Input/IMainMenu.cs
+++ b/src/Avalonia.Input/IMainMenu.cs
@@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
+using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Input
@@ -24,5 +26,10 @@ namespace Avalonia.Input
/// Opens the menu in response to the Alt/F10 key.
///
void Open();
+
+ ///
+ /// Occurs when the main menu closes.
+ ///
+ event EventHandler MenuClosed;
}
}
diff --git a/src/Avalonia.Input/INavigableContainer.cs b/src/Avalonia.Input/INavigableContainer.cs
index 13d734bd0b..df434bca70 100644
--- a/src/Avalonia.Input/INavigableContainer.cs
+++ b/src/Avalonia.Input/INavigableContainer.cs
@@ -13,7 +13,8 @@ namespace Avalonia.Input
///
/// The movement direction.
/// The control from which movement begins.
+ /// Whether to wrap around when the first or last item is reached.
/// The control.
- IInputElement GetControl(NavigationDirection direction, IInputElement from);
+ IInputElement GetControl(NavigationDirection direction, IInputElement from, bool wrap);
}
}
diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs
index 82e626f9c6..3aff5d0a8b 100644
--- a/src/Avalonia.Input/InputElement.cs
+++ b/src/Avalonia.Input/InputElement.cs
@@ -177,7 +177,7 @@ namespace Avalonia.Input
///
/// Occurs when the control receives focus.
///
- public event EventHandler GotFocus
+ public event EventHandler GotFocus
{
add { AddHandler(GotFocusEvent, value); }
remove { RemoveHandler(GotFocusEvent, value); }
diff --git a/src/Avalonia.Input/KeyboardNavigation.cs b/src/Avalonia.Input/KeyboardNavigation.cs
index cbd9a74f4c..0277876e24 100644
--- a/src/Avalonia.Input/KeyboardNavigation.cs
+++ b/src/Avalonia.Input/KeyboardNavigation.cs
@@ -8,19 +8,6 @@ namespace Avalonia.Input
///
public static class KeyboardNavigation
{
- ///
- /// Defines the DirectionalNavigation attached property.
- ///
- ///
- /// The DirectionalNavigation attached property defines how pressing arrow keys causes
- /// focus to be navigated between the children of the container.
- ///
- public static readonly AttachedProperty DirectionalNavigationProperty =
- AvaloniaProperty.RegisterAttached(
- "DirectionalNavigation",
- typeof(KeyboardNavigation),
- KeyboardNavigationMode.None);
-
///
/// Defines the TabNavigation attached property.
///
@@ -46,26 +33,6 @@ namespace Avalonia.Input
"TabOnceActiveElement",
typeof(KeyboardNavigation));
- ///
- /// Gets the for a container.
- ///
- /// The container.
- /// The for the container.
- public static KeyboardNavigationMode GetDirectionalNavigation(InputElement element)
- {
- return element.GetValue(DirectionalNavigationProperty);
- }
-
- ///
- /// Sets the for a container.
- ///
- /// The container.
- /// The for the container.
- public static void SetDirectionalNavigation(InputElement element, KeyboardNavigationMode value)
- {
- element.SetValue(DirectionalNavigationProperty, value);
- }
-
///
/// Gets the for a container.
///
diff --git a/src/Avalonia.Input/KeyboardNavigationHandler.cs b/src/Avalonia.Input/KeyboardNavigationHandler.cs
index bf2b61d08b..bc3098a7fb 100644
--- a/src/Avalonia.Input/KeyboardNavigationHandler.cs
+++ b/src/Avalonia.Input/KeyboardNavigationHandler.cs
@@ -85,7 +85,7 @@ namespace Avalonia.Input
}
else
{
- return DirectionalNavigation.GetNext(element, direction);
+ throw new NotSupportedException();
}
}
@@ -122,47 +122,12 @@ namespace Avalonia.Input
{
var current = FocusManager.Instance.Current;
- if (current != null)
+ if (current != null && e.Key == Key.Tab)
{
- NavigationDirection? direction = null;
-
- switch (e.Key)
- {
- case Key.Tab:
- direction = (e.Modifiers & InputModifiers.Shift) == 0 ?
- NavigationDirection.Next : NavigationDirection.Previous;
- break;
- case Key.Up:
- direction = NavigationDirection.Up;
- break;
- case Key.Down:
- direction = NavigationDirection.Down;
- break;
- case Key.Left:
- direction = NavigationDirection.Left;
- break;
- case Key.Right:
- direction = NavigationDirection.Right;
- break;
- case Key.PageUp:
- direction = NavigationDirection.PageUp;
- break;
- case Key.PageDown:
- direction = NavigationDirection.PageDown;
- break;
- case Key.Home:
- direction = NavigationDirection.First;
- break;
- case Key.End:
- direction = NavigationDirection.Last;
- break;
- }
-
- if (direction.HasValue)
- {
- Move(current, direction.Value, e.Modifiers);
- e.Handled = true;
- }
+ var direction = (e.Modifiers & InputModifiers.Shift) == 0 ?
+ NavigationDirection.Next : NavigationDirection.Previous;
+ Move(current, direction, e.Modifiers);
+ e.Handled = true;
}
}
}
diff --git a/src/Avalonia.Input/Navigation/DirectionalNavigation.cs b/src/Avalonia.Input/Navigation/DirectionalNavigation.cs
deleted file mode 100644
index 75cb3a39e8..0000000000
--- a/src/Avalonia.Input/Navigation/DirectionalNavigation.cs
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Input.Navigation
-{
- ///
- /// The implementation for default directional navigation.
- ///
- public static class DirectionalNavigation
- {
- ///
- /// Gets the next control in the specified navigation direction.
- ///
- /// The element.
- /// The navigation direction.
- ///
- /// The next element in the specified direction, or null if
- /// was the last in the requested direction.
- ///
- public static IInputElement GetNext(
- IInputElement element,
- NavigationDirection direction)
- {
- Contract.Requires(element != null);
- Contract.Requires(
- direction != NavigationDirection.Next &&
- direction != NavigationDirection.Previous);
-
- var container = element.GetVisualParent();
-
- if (container != null)
- {
- var mode = KeyboardNavigation.GetDirectionalNavigation((InputElement)container);
-
- switch (mode)
- {
- case KeyboardNavigationMode.Continue:
- return GetNextInContainer(element, container, direction) ??
- GetFirstInNextContainer(element, element, direction);
- case KeyboardNavigationMode.Cycle:
- return GetNextInContainer(element, container, direction) ??
- GetFocusableDescendant(container, direction);
- case KeyboardNavigationMode.Contained:
- return GetNextInContainer(element, container, direction);
- default:
- return null;
- }
- }
- else
- {
- return GetFocusableDescendants(element).FirstOrDefault();
- }
- }
-
- ///
- /// Returns a value indicting whether the specified direction is forward.
- ///
- /// The direction.
- /// True if the direction is forward.
- private static bool IsForward(NavigationDirection direction)
- {
- return direction == NavigationDirection.Next ||
- direction == NavigationDirection.Last ||
- direction == NavigationDirection.Right ||
- direction == NavigationDirection.Down;
- }
-
- ///
- /// Gets the first or last focusable descendant of the specified element.
- ///
- /// The element.
- /// The direction to search.
- /// The element or null if not found.##
- private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
- {
- return IsForward(direction) ?
- GetFocusableDescendants(container).FirstOrDefault() :
- GetFocusableDescendants(container).LastOrDefault();
- }
-
- ///
- /// Gets the focusable descendants of the specified element.
- ///
- /// The element.
- /// The element's focusable descendants.
- private static IEnumerable GetFocusableDescendants(IInputElement element)
- {
- var children = element.GetVisualChildren().OfType();
-
- foreach (var child in children)
- {
- if (child.CanFocus())
- {
- yield return child;
- }
-
- if (child.CanFocusDescendants())
- {
- foreach (var descendant in GetFocusableDescendants(child))
- {
- yield return descendant;
- }
- }
- }
- }
-
- ///
- /// Gets the next item that should be focused in the specified container.
- ///
- /// The starting element/
- /// The container.
- /// The direction.
- /// The next element, or null if the element is the last.
- private static IInputElement GetNextInContainer(
- IInputElement element,
- IInputElement container,
- NavigationDirection direction)
- {
- if (direction == NavigationDirection.Down)
- {
- var descendant = GetFocusableDescendants(element).FirstOrDefault();
-
- if (descendant != null)
- {
- return descendant;
- }
- }
-
- if (container != null)
- {
- var navigable = container as INavigableContainer;
-
- if (navigable != null)
- {
- while (element != null)
- {
- element = navigable.GetControl(direction, element);
-
- if (element != null && element.CanFocus())
- {
- break;
- }
- }
- }
- else
- {
- // TODO: Do a spatial search here if the container doesn't implement
- // INavigableContainer.
- element = null;
- }
-
- if (element != null && direction == NavigationDirection.Up)
- {
- var descendant = GetFocusableDescendants(element).LastOrDefault();
-
- if (descendant != null)
- {
- return descendant;
- }
- }
-
- return element;
- }
-
- return null;
- }
-
- ///
- /// Gets the first item that should be focused in the next container.
- ///
- /// The element being navigated away from.
- /// The container.
- /// The direction of the search.
- /// The first element, or null if there are no more elements.
- private static IInputElement GetFirstInNextContainer(
- IInputElement element,
- IInputElement container,
- NavigationDirection direction)
- {
- var parent = container.GetVisualParent();
- var isForward = IsForward(direction);
- IInputElement next = null;
-
- if (parent != null)
- {
- if (!isForward && parent.CanFocus())
- {
- return parent;
- }
-
- var siblings = parent.GetVisualChildren()
- .OfType()
- .Where(FocusExtensions.CanFocusDescendants);
- var sibling = isForward ?
- siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() :
- siblings.TakeWhile(x => x != container).LastOrDefault();
-
- if (sibling != null)
- {
- if (sibling is ICustomKeyboardNavigation custom)
- {
- var (handled, customNext) = custom.GetNext(element, direction);
-
- if (handled)
- {
- return customNext;
- }
- }
-
- if (sibling.CanFocus())
- {
- next = sibling;
- }
- else
- {
- next = isForward ?
- GetFocusableDescendants(sibling).FirstOrDefault() :
- GetFocusableDescendants(sibling).LastOrDefault();
- }
- }
-
- if (next == null)
- {
- next = GetFirstInNextContainer(element, parent, direction);
- }
- }
- else
- {
- next = isForward ?
- GetFocusableDescendants(container).FirstOrDefault() :
- GetFocusableDescendants(container).LastOrDefault();
- }
-
- return next;
- }
- }
-}
diff --git a/src/Avalonia.Input/Navigation/TabNavigation.cs b/src/Avalonia.Input/Navigation/TabNavigation.cs
index a9d5b83073..18db2a9173 100644
--- a/src/Avalonia.Input/Navigation/TabNavigation.cs
+++ b/src/Avalonia.Input/Navigation/TabNavigation.cs
@@ -168,7 +168,7 @@ namespace Avalonia.Input.Navigation
{
while (element != null)
{
- element = navigable.GetControl(direction, element);
+ element = navigable.GetControl(direction, element, false);
if (element != null && element.CanFocus())
{
diff --git a/src/Avalonia.Input/NavigationDirection.cs b/src/Avalonia.Input/NavigationDirection.cs
index fbaa7e74c7..406890b767 100644
--- a/src/Avalonia.Input/NavigationDirection.cs
+++ b/src/Avalonia.Input/NavigationDirection.cs
@@ -58,4 +58,74 @@ namespace Avalonia.Input
///
PageDown,
}
+
+ public static class NavigationDirectionExtensions
+ {
+ ///
+ /// Checks whether a represents a tab movement.
+ ///
+ /// The direction.
+ ///
+ /// True if the direction represents a tab movement (
+ /// or ); otherwise false.
+ ///
+ public static bool IsTab(this NavigationDirection direction)
+ {
+ return direction == NavigationDirection.Next ||
+ direction == NavigationDirection.Previous;
+ }
+
+ ///
+ /// Checks whether a represents a directional movement.
+ ///
+ /// The direction.
+ ///
+ /// True if the direction represents a directional movement (any value except
+ /// and );
+ /// otherwise false.
+ ///
+ public static bool IsDirectional(this NavigationDirection direction)
+ {
+ return direction > NavigationDirection.Previous ||
+ direction <= NavigationDirection.PageDown;
+ }
+
+ ///
+ /// Converts a keypress into a .
+ ///
+ /// The key.
+ /// The keyboard modifiers.
+ ///
+ /// A if the keypress represents a navigation keypress.
+ ///
+ public static NavigationDirection? ToNavigationDirection(
+ this Key key,
+ InputModifiers modifiers = InputModifiers.None)
+ {
+ switch (key)
+ {
+ case Key.Tab:
+ return (modifiers & InputModifiers.Shift) != 0 ?
+ NavigationDirection.Next : NavigationDirection.Previous;
+ case Key.Up:
+ return NavigationDirection.Up;
+ case Key.Down:
+ return NavigationDirection.Down;
+ case Key.Left:
+ return NavigationDirection.Left;
+ case Key.Right:
+ return NavigationDirection.Right;
+ case Key.Home:
+ return NavigationDirection.First;
+ case Key.End:
+ return NavigationDirection.Last;
+ case Key.PageUp:
+ return NavigationDirection.PageUp;
+ case Key.PageDown:
+ return NavigationDirection.PageDown;
+ default:
+ return null;
+ }
+ }
+ }
}
diff --git a/src/Avalonia.Interactivity/RoutedEvent.cs b/src/Avalonia.Interactivity/RoutedEvent.cs
index 61bb567d54..2d752133c1 100644
--- a/src/Avalonia.Interactivity/RoutedEvent.cs
+++ b/src/Avalonia.Interactivity/RoutedEvent.cs
@@ -72,7 +72,9 @@ namespace Avalonia.Interactivity
{
Contract.Requires(name != null);
- return new RoutedEvent(name, routingStrategy, typeof(TOwner));
+ var routedEvent = new RoutedEvent(name, routingStrategy, typeof(TOwner));
+ RoutedEventRegistry.Instance.Register(typeof(TOwner), routedEvent);
+ return routedEvent;
}
public static RoutedEvent Register(
@@ -83,7 +85,9 @@ namespace Avalonia.Interactivity
{
Contract.Requires(name != null);
- return new RoutedEvent(name, routingStrategy, ownerType);
+ var routedEvent = new RoutedEvent(name, routingStrategy, ownerType);
+ RoutedEventRegistry.Instance.Register(ownerType, routedEvent);
+ return routedEvent;
}
public IDisposable AddClassHandler(
diff --git a/src/Avalonia.Interactivity/RoutedEventRegistry.cs b/src/Avalonia.Interactivity/RoutedEventRegistry.cs
new file mode 100644
index 0000000000..34c970a806
--- /dev/null
+++ b/src/Avalonia.Interactivity/RoutedEventRegistry.cs
@@ -0,0 +1,90 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+
+namespace Avalonia.Interactivity
+{
+ ///
+ /// Tracks registered s.
+ ///
+ public class RoutedEventRegistry
+ {
+ private readonly Dictionary> _registeredRoutedEvents =
+ new Dictionary>();
+
+ ///
+ /// Gets the instance.
+ ///
+ public static RoutedEventRegistry Instance { get; }
+ = new RoutedEventRegistry();
+
+ ///
+ /// Registers a on a type.
+ ///
+ /// The type.
+ /// The event.
+ ///
+ /// You won't usually want to call this method directly, instead use the
+ ///
+ /// method.
+ ///
+ public void Register(Type type, RoutedEvent @event)
+ {
+ Contract.Requires(type != null);
+ Contract.Requires(@event != null);
+
+ if (!_registeredRoutedEvents.TryGetValue(type, out var list))
+ {
+ list = new List();
+ _registeredRoutedEvents.Add(type, list);
+ }
+ list.Add(@event);
+ }
+
+ ///
+ /// Returns all routed events, that are currently registered in the event registry.
+ ///
+ /// All routed events, that are currently registered in the event registry.
+ public IEnumerable GetAllRegistered()
+ {
+ foreach (var events in _registeredRoutedEvents.Values)
+ {
+ foreach (var e in events)
+ {
+ yield return e;
+ }
+ }
+ }
+
+ ///
+ /// Returns all routed events registered with the provided type.
+ /// If the type is not found or does not provide any routed events, an empty list is returned.
+ ///
+ /// The type.
+ /// All routed events registered with the provided type.
+ public IReadOnlyList GetRegistered(Type type)
+ {
+ Contract.Requires(type != null);
+
+ if (_registeredRoutedEvents.TryGetValue(type, out var events))
+ {
+ return events;
+ }
+
+ return Array.Empty();
+ }
+
+ ///
+ /// Returns all routed events registered with the provided type.
+ /// If the type is not found or does not provide any routed events, an empty list is returned.
+ ///
+ /// The type.
+ /// All routed events registered with the provided type.
+ public IReadOnlyList GetRegistered()
+ {
+ return GetRegistered(typeof(TOwner));
+ }
+ }
+}
diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml
index 53965db016..8a2ed2a802 100644
--- a/src/Avalonia.Themes.Default/MenuItem.xaml
+++ b/src/Avalonia.Themes.Default/MenuItem.xaml
@@ -127,11 +127,6 @@
-
-
@@ -139,4 +134,4 @@
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/Separator.xaml b/src/Avalonia.Themes.Default/Separator.xaml
index 3b3a9e9749..6312a14df5 100644
--- a/src/Avalonia.Themes.Default/Separator.xaml
+++ b/src/Avalonia.Themes.Default/Separator.xaml
@@ -1,6 +1,7 @@
-
\ No newline at end of file
+
diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs
index 4f18c682b4..cb43873cee 100644
--- a/src/Markup/Avalonia.Markup/Data/Binding.cs
+++ b/src/Markup/Avalonia.Markup/Data/Binding.cs
@@ -150,7 +150,9 @@ namespace Avalonia.Data
}
else if (RelativeSource.Mode == RelativeSourceMode.Self)
{
- observer = CreateSourceObserver(target, node);
+ observer = CreateSourceObserver(
+ (target as IStyledElement) ?? (anchor as IStyledElement),
+ node);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
index 9ef1e9f0d2..3cf886ade4 100644
--- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
@@ -11,6 +11,7 @@ using Avalonia.VisualTree;
using Xunit;
using System.Collections.ObjectModel;
using Avalonia.UnitTests;
+using Avalonia.Input;
namespace Avalonia.Controls.UnitTests
{
@@ -494,6 +495,77 @@ namespace Avalonia.Controls.UnitTests
Assert.NotNull(NameScope.GetNameScope((TextBlock)container.Child));
}
+ [Fact]
+ public void Focuses_Next_Item_On_Key_Down()
+ {
+ using (UnitTestApplication.Start(TestServices.RealFocus))
+ {
+ var items = new object[]
+ {
+ new Button(),
+ new Button(),
+ };
+
+ var target = new ItemsControl
+ {
+ Template = GetTemplate(),
+ Items = items,
+ };
+
+ var root = new TestRoot { Child = target };
+
+ target.ApplyTemplate();
+ target.Presenter.ApplyTemplate();
+ target.Presenter.Panel.Children[0].Focus();
+
+ target.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Down,
+ });
+
+ Assert.Equal(
+ target.Presenter.Panel.Children[1],
+ FocusManager.Instance.Current);
+ }
+ }
+
+ [Fact]
+ public void Does_Not_Focus_Non_Focusable_Item_On_Key_Down()
+ {
+ using (UnitTestApplication.Start(TestServices.RealFocus))
+ {
+ var items = new object[]
+ {
+ new Button(),
+ new Button { Focusable = false },
+ new Button(),
+ };
+
+ var target = new ItemsControl
+ {
+ Template = GetTemplate(),
+ Items = items,
+ };
+
+ var root = new TestRoot { Child = target };
+
+ target.ApplyTemplate();
+ target.Presenter.ApplyTemplate();
+ target.Presenter.Panel.Children[0].Focus();
+
+ target.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Down,
+ });
+
+ Assert.Equal(
+ target.Presenter.Panel.Children[2],
+ FocusManager.Instance.Current);
+ }
+ }
+
private class Item
{
public Item(string value)
diff --git a/tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs b/tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs
new file mode 100644
index 0000000000..f06553411c
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs
@@ -0,0 +1,106 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Collections;
+using Avalonia.Controls.Mixins;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.LogicalTree;
+using Moq;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests.Mixins
+{
+ public class ContentControlMixinTests
+ {
+ [Fact]
+ public void Multiple_Mixin_Usages_Should_Not_Throw()
+ {
+ var target = new TestControl()
+ {
+ Template = new FuncControlTemplate(_ => new Panel
+ {
+ Children =
+ {
+ new ContentPresenter { Name = "Content_1_Presenter" },
+ new ContentPresenter { Name = "Content_2_Presenter" }
+ }
+ })
+ };
+
+ var ex = Record.Exception(() => target.ApplyTemplate());
+
+ Assert.Null(ex);
+ }
+
+ [Fact]
+ public void Replacing_Template_Releases_Events()
+ {
+ var p1 = new ContentPresenter { Name = "Content_1_Presenter" };
+ var p2 = new ContentPresenter { Name = "Content_2_Presenter" };
+ var target = new TestControl
+ {
+ Template = new FuncControlTemplate(_ => new Panel
+ {
+ Children =
+ {
+ p1,
+ p2
+ }
+ })
+ };
+ target.ApplyTemplate();
+
+ Control tc;
+
+ p1.Content = tc = new Control();
+ p1.UpdateChild();
+ Assert.Contains(tc, target.GetLogicalChildren());
+
+ p2.Content = tc = new Control();
+ p2.UpdateChild();
+ Assert.Contains(tc, target.GetLogicalChildren());
+
+ target.Template = null;
+
+ p1.Content = tc = new Control();
+ p1.UpdateChild();
+ Assert.DoesNotContain(tc, target.GetLogicalChildren());
+
+ p2.Content = tc = new Control();
+ p2.UpdateChild();
+ Assert.DoesNotContain(tc, target.GetLogicalChildren());
+
+ }
+
+ private class TestControl : TemplatedControl
+ {
+ public static readonly StyledProperty Content1Property =
+ AvaloniaProperty.Register(nameof(Content1));
+
+ public static readonly StyledProperty Content2Property =
+ AvaloniaProperty.Register(nameof(Content2));
+
+ static TestControl()
+ {
+ ContentControlMixin.Attach(Content1Property, x => x.LogicalChildren, "Content_1_Presenter");
+ ContentControlMixin.Attach(Content2Property, x => x.LogicalChildren, "Content_2_Presenter");
+ }
+
+ public object Content1
+ {
+ get { return GetValue(Content1Property); }
+ set { SetValue(Content1Property, value); }
+ }
+
+ public object Content2
+ {
+ get { return GetValue(Content2Property); }
+ set { SetValue(Content2Property, value); }
+ }
+ }
+ }
+}
diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
new file mode 100644
index 0000000000..e17279013d
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
@@ -0,0 +1,507 @@
+using System;
+using Avalonia.Controls.Platform;
+using Avalonia.Input;
+using Moq;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests.Platform
+{
+ public class DefaultMenuInteractionHandlerTests
+ {
+ public class TopLevel
+ {
+ [Fact]
+ public void Up_Opens_MenuItem_With_SubMenu()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var item = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true);
+ var e = new KeyEventArgs { Key = Key.Up, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(item).Verify(x => x.Open());
+ Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true));
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Down_Opens_MenuItem_With_SubMenu()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var item = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true);
+ var e = new KeyEventArgs { Key = Key.Down, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(item).Verify(x => x.Open());
+ Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true));
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Right_Selects_Next_MenuItem()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of(x => x.MoveSelection(NavigationDirection.Right, true) == true);
+ var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu);
+ var e = new KeyEventArgs { Key = Key.Right, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(menu).Verify(x => x.MoveSelection(NavigationDirection.Right, true));
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Left_Selects_Previous_MenuItem()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of(x => x.MoveSelection(NavigationDirection.Left, true) == true);
+ var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu);
+ var e = new KeyEventArgs { Key = Key.Left, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(menu).Verify(x => x.MoveSelection(NavigationDirection.Left, true));
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Enter_On_Item_With_No_SubMenu_Causes_Click()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of();
+ var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu);
+ var e = new KeyEventArgs { Key = Key.Enter, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(item).Verify(x => x.RaiseClick());
+ Mock.Get(menu).Verify(x => x.Close());
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Enter_On_Item_With_SubMenu_Opens_SubMenu()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of();
+ var item = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var e = new KeyEventArgs { Key = Key.Enter, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(item).Verify(x => x.Open());
+ Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true));
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Escape_Closes_Parent_Menu()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of();
+ var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu);
+ var e = new KeyEventArgs { Key = Key.Escape, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(menu).Verify(x => x.Close());
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void PointerEnter_Opens_Item_When_Old_Item_Is_Open()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = new Mock();
+ var item = Mock.Of(x =>
+ x.IsSubMenuOpen == true &&
+ x.IsTopLevel == true &&
+ x.HasSubMenu == true &&
+ x.Parent == menu.Object);
+ var nextItem = Mock.Of(x =>
+ x.IsTopLevel == true &&
+ x.HasSubMenu == true &&
+ x.Parent == menu.Object);
+ var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = nextItem };
+
+ menu.SetupGet(x => x.SelectedItem).Returns(item);
+
+ target.PointerEnter(nextItem, e);
+
+ Mock.Get(item).Verify(x => x.Close());
+ menu.VerifySet(x => x.SelectedItem = nextItem);
+ Mock.Get(nextItem).Verify(x => x.Open());
+ Mock.Get(nextItem).Verify(x => x.MoveSelection(NavigationDirection.First, true), Times.Never);
+ Assert.False(e.Handled);
+
+ }
+
+ [Fact]
+ public void PointerLeave_Deselects_Item_When_Menu_Not_Open()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = new Mock();
+ var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu.Object);
+ var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+
+ menu.SetupGet(x => x.SelectedItem).Returns(item);
+ target.PointerLeave(item, e);
+
+ menu.VerifySet(x => x.SelectedItem = null);
+ Assert.False(e.Handled);
+ }
+
+ [Fact]
+ public void PointerLeave_Doesnt_Deselect_Item_When_Menu_Open()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = new Mock();
+ var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu.Object);
+ var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+
+ menu.SetupGet(x => x.IsOpen).Returns(true);
+ menu.SetupGet(x => x.SelectedItem).Returns(item);
+ target.PointerLeave(item, e);
+
+ menu.VerifySet(x => x.SelectedItem = null, Times.Never);
+ Assert.False(e.Handled);
+ }
+ }
+
+ public class NonTopLevel
+ {
+ [Fact]
+ public void Up_Selects_Previous_MenuItem()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var e = new KeyEventArgs { Key = Key.Up, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(parentItem).Verify(x => x.MoveSelection(NavigationDirection.Up, true));
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Down_Selects_Next_MenuItem()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var e = new KeyEventArgs { Key = Key.Down, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(parentItem).Verify(x => x.MoveSelection(NavigationDirection.Down, true));
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Left_Closes_Parent_SubMenu()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var parentItem = Mock.Of(x => x.HasSubMenu == true && x.IsSubMenuOpen == true);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var e = new KeyEventArgs { Key = Key.Left, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(parentItem).Verify(x => x.Close());
+ Mock.Get(parentItem).Verify(x => x.Focus());
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Right_With_SubMenu_Items_Opens_SubMenu()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true);
+ var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true);
+ var e = new KeyEventArgs { Key = Key.Right, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(item).Verify(x => x.Open());
+ Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true));
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Right_On_TopLevel_Child_Navigates_TopLevel_Selection()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = new Mock();
+ var parentItem = Mock.Of(x =>
+ x.IsSubMenuOpen == true &&
+ x.IsTopLevel == true &&
+ x.HasSubMenu == true &&
+ x.Parent == menu.Object);
+ var nextItem = Mock.Of(x =>
+ x.IsTopLevel == true &&
+ x.HasSubMenu == true &&
+ x.Parent == menu.Object);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var e = new KeyEventArgs { Key = Key.Right, Source = item };
+
+ menu.Setup(x => x.MoveSelection(NavigationDirection.Right, true))
+ .Callback(() => menu.SetupGet(x => x.SelectedItem).Returns(nextItem))
+ .Returns(true);
+
+ target.KeyDown(item, e);
+
+ menu.Verify(x => x.MoveSelection(NavigationDirection.Right, true));
+ Mock.Get(parentItem).Verify(x => x.Close());
+ Mock.Get(nextItem).Verify(x => x.Open());
+ Mock.Get(nextItem).Verify(x => x.MoveSelection(NavigationDirection.First, true));
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Enter_On_Item_With_No_SubMenu_Causes_Click()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var e = new KeyEventArgs { Key = Key.Enter, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(item).Verify(x => x.RaiseClick());
+ Mock.Get(menu).Verify(x => x.Close());
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Enter_On_Item_With_SubMenu_Opens_SubMenu()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true);
+ var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true);
+ var e = new KeyEventArgs { Key = Key.Enter, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(item).Verify(x => x.Open());
+ Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true));
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Escape_Closes_Parent_MenuItem()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var e = new KeyEventArgs { Key = Key.Escape, Source = item };
+
+ target.KeyDown(item, e);
+
+ Mock.Get(parentItem).Verify(x => x.Close());
+ Mock.Get(parentItem).Verify(x => x.Focus());
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void PointerEnter_Selects_Item()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
+
+ target.PointerEnter(item, e);
+
+ Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item);
+ Assert.False(e.Handled);
+ }
+
+ [Fact]
+ public void PointerEnter_Opens_Submenu_After_Delay()
+ {
+ var timer = new TestTimer();
+ var target = new DefaultMenuInteractionHandler(null, timer.RunOnce);
+ var menu = Mock.Of();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true);
+ var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
+
+ target.PointerEnter(item, e);
+ Mock.Get(item).Verify(x => x.Open(), Times.Never);
+
+ timer.Pulse();
+ Mock.Get(item).Verify(x => x.Open());
+
+ Assert.False(e.Handled);
+ }
+
+ [Fact]
+ public void PointerEnter_Closes_Sibling_Submenu_After_Delay()
+ {
+ var timer = new TestTimer();
+ var target = new DefaultMenuInteractionHandler(null, timer.RunOnce);
+ var menu = Mock.Of();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var sibling = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsSubMenuOpen == true);
+ var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
+
+ Mock.Get(parentItem).SetupGet(x => x.SubItems).Returns(new[] { item, sibling });
+
+ target.PointerEnter(item, e);
+ Mock.Get(sibling).Verify(x => x.Close(), Times.Never);
+
+ timer.Pulse();
+ Mock.Get(sibling).Verify(x => x.Close());
+
+ Assert.False(e.Handled);
+ }
+
+ [Fact]
+ public void PointerLeave_Deselects_Item()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+
+ Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(item);
+ target.PointerLeave(item, e);
+
+ Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null);
+ Assert.False(e.Handled);
+ }
+
+ [Fact]
+ public void PointerLeave_Doesnt_Deselect_Sibling()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var sibling = Mock.Of(x => x.Parent == parentItem);
+ var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+
+ Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(sibling);
+ target.PointerLeave(item, e);
+
+ Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null, Times.Never);
+ Assert.False(e.Handled);
+ }
+
+ [Fact]
+ public void PointerLeave_Doesnt_Deselect_Item_If_Pointer_Over_Submenu()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true);
+ var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+
+ target.PointerLeave(item, e);
+
+ Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null, Times.Never);
+ Assert.False(e.Handled);
+ }
+
+ [Fact]
+ public void PointerReleased_On_Item_With_No_SubMenu_Causes_Click()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var item = Mock.Of(x => x.Parent == parentItem);
+ var e = new PointerReleasedEventArgs { MouseButton = MouseButton.Left, Source = item };
+
+ target.PointerReleased(item, e);
+
+ Mock.Get(item).Verify(x => x.RaiseClick());
+ Mock.Get(menu).Verify(x => x.Close());
+ Assert.True(e.Handled);
+ }
+
+ [Fact]
+ public void Selection_Is_Correct_When_Pointer_Temporarily_Exits_Item_To_Select_SubItem()
+ {
+ var timer = new TestTimer();
+ var target = new DefaultMenuInteractionHandler(null, timer.RunOnce);
+ var menu = Mock.Of();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true);
+ var childItem = Mock.Of(x => x.Parent == item);
+ var enter = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
+ var leave = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+
+ // Pointer enters item; item is selected.
+ target.PointerEnter(item, enter);
+ Assert.True(timer.ActionIsQueued);
+ Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item);
+ Mock.Get(parentItem).ResetCalls();
+
+ // SubMenu shown after a delay.
+ timer.Pulse();
+ Mock.Get(item).Verify(x => x.Open());
+ Mock.Get(item).SetupGet(x => x.IsSubMenuOpen).Returns(true);
+ Mock.Get(item).ResetCalls();
+
+ // Pointer briefly exits item, but submenu remains open.
+ target.PointerLeave(item, leave);
+ Mock.Get(item).Verify(x => x.Close(), Times.Never);
+ Mock.Get(item).ResetCalls();
+
+ // Pointer enters child item; is selected.
+ enter.Source = childItem;
+ target.PointerEnter(childItem, enter);
+ Mock.Get(item).VerifySet(x => x.SelectedItem = childItem);
+ Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item);
+ Mock.Get(item).ResetCalls();
+ Mock.Get(parentItem).ResetCalls();
+ }
+
+ [Fact]
+ public void PointerPressed_On_Item_With_SubMenu_Causes_Opens_Submenu()
+ {
+ var target = new DefaultMenuInteractionHandler();
+ var menu = Mock.Of();
+ var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
+ var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true);
+ var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
+
+ target.PointerPressed(item, e);
+
+ Mock.Get(item).Verify(x => x.Open());
+ Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true), Times.Never);
+ Assert.True(e.Handled);
+ }
+ }
+
+ private class TestTimer
+ {
+ private Action _action;
+
+ public bool ActionIsQueued => _action != null;
+
+ public void Pulse()
+ {
+ _action();
+ _action = null;
+ }
+
+ public void RunOnce(Action action, TimeSpan timeSpan)
+ {
+ if (_action != null)
+ {
+ throw new NotSupportedException("Action already set.");
+ }
+
+ _action = action;
+ }
+ }
+ }
+}
diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
index b0ae3df8a2..70a40faed3 100644
--- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
@@ -219,7 +219,7 @@ namespace Avalonia.Controls.UnitTests
scrollable.Setup(x => x.IsLogicalScrollEnabled).Returns(true);
((ISetLogicalParent)target).SetParent(presenter.Object);
- ((INavigableContainer)target).GetControl(NavigationDirection.Next, from);
+ ((INavigableContainer)target).GetControl(NavigationDirection.Next, from, false);
scrollable.Verify(x => x.GetControlInDirection(NavigationDirection.Next, from));
}
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs
deleted file mode 100644
index b81b724e2a..0000000000
--- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs
+++ /dev/null
@@ -1,799 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using Avalonia.Controls;
-using Xunit;
-
-namespace Avalonia.Input.UnitTests
-{
- public class KeyboardNavigationTests_Arrows
- {
- [Fact]
- public void Down_Continue_Returns_Down_Control_In_Container()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button1" },
- (current = new Button { Name = "Button2" }),
- (next = new Button { Name = "Button3" }),
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Down_Continue_Returns_First_Control_In_Down_Sibling_Container()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button1" },
- new Button { Name = "Button2" },
- (current = new Button { Name = "Button3" }),
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- (next = new Button { Name = "Button4" }),
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Down_Continue_Returns_Down_Sibling()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button1" },
- new Button { Name = "Button2" },
- (current = new Button { Name = "Button3" }),
- }
- },
- (next = new Button { Name = "Button4" }),
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Down_Continue_Returns_First_Control_In_Down_Uncle_Container()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button1" },
- new Button { Name = "Button2" },
- (current = new Button { Name = "Button3" }),
- }
- },
- },
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- (next = new Button { Name = "Button4" }),
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Down_Continue_Returns_Child_Of_Top_Level()
- {
- Button next;
-
- var top = new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- (next = new Button { Name = "Button1" }),
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(top, NavigationDirection.Down);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Down_Continue_Wraps()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- (next = new Button { Name = "Button1" }),
- new Button { Name = "Button2" },
- new Button { Name = "Button3" },
- }
- },
- },
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- (current = new Button { Name = "Button6" }),
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Down_Cycle_Returns_Down_Control_In_Container()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children =
- {
- new Button { Name = "Button1" },
- (current = new Button { Name = "Button2" }),
- (next = new Button { Name = "Button3" }),
- }
- },
- new StackPanel
- {
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Down_Cycle_Wraps_To_First()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children =
- {
- (next = new Button { Name = "Button1" }),
- new Button { Name = "Button2" },
- (current = new Button { Name = "Button3" }),
- }
- },
- new StackPanel
- {
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Down_Contained_Returns_Down_Control_In_Container()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- new Button { Name = "Button1" },
- (current = new Button { Name = "Button2" }),
- (next = new Button { Name = "Button3" }),
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Down_Contained_Stops_At_End()
- {
- Button current;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- new Button { Name = "Button1" },
- new Button { Name = "Button2" },
- (current = new Button { Name = "Button3" }),
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down);
-
- Assert.Null(result);
- }
-
- [Fact]
- public void Down_None_Does_Nothing()
- {
- Button current;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.None,
- Children =
- {
- new Button { Name = "Button1" },
- (current = new Button { Name = "Button2" }),
- new Button { Name = "Button3" },
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down);
-
- Assert.Null(result);
- }
-
- [Fact]
- public void Up_Continue_Returns_Up_Control_In_Container()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button1" },
- (next = new Button { Name = "Button2" }),
- (current = new Button { Name = "Button3" }),
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Up_Continue_Returns_Last_Control_In_Up_Sibling_Container()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button1" },
- new Button { Name = "Button2" },
- (next = new Button { Name = "Button3" }),
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- (current = new Button { Name = "Button4" }),
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Up_Continue_Returns_Last_Child_Of_Sibling()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button1" },
- new Button { Name = "Button2" },
- (next = new Button { Name = "Button3" }),
- }
- },
- (current = new Button { Name = "Button4" }),
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Up_Continue_Returns_Last_Control_In_Up_Nephew_Container()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button1" },
- new Button { Name = "Button2" },
- (next = new Button { Name = "Button3" }),
- }
- },
- },
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- (current = new Button { Name = "Button4" }),
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Up_Continue_Wraps()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- (current = new Button { Name = "Button1" }),
- new Button { Name = "Button2" },
- new Button { Name = "Button3" },
- }
- },
- },
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- (next = new Button { Name = "Button6" }),
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Up_Continue_Returns_Parent()
- {
- Button current;
-
- var top = new Decorator
- {
- Focusable = true,
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Child = current = new Button
- {
- Name = "Button",
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Equal(top, result);
- }
-
- [Fact]
- public void Up_Cycle_Returns_Up_Control_In_Container()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children =
- {
- (next = new Button { Name = "Button1" }),
- (current = new Button { Name = "Button2" }),
- new Button { Name = "Button3" },
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Up_Cycle_Wraps_To_Last()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children =
- {
- (current = new Button { Name = "Button1" }),
- new Button { Name = "Button2" },
- (next = new Button { Name = "Button3" }),
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Up_Contained_Returns_Up_Control_In_Container()
- {
- Button current;
- Button next;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- (next = new Button { Name = "Button1" }),
- (current = new Button { Name = "Button2" }),
- new Button { Name = "Button3" },
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Equal(next, result);
- }
-
- [Fact]
- public void Up_Contained_Stops_At_Beginning()
- {
- Button current;
-
- var top = new StackPanel
- {
- Children =
- {
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- (current = new Button { Name = "Button1" }),
- new Button { Name = "Button2" },
- new Button { Name = "Button3" },
- }
- },
- new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- new Button { Name = "Button4" },
- new Button { Name = "Button5" },
- new Button { Name = "Button6" },
- }
- },
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Null(result);
- }
-
- [Fact]
- public void Up_Contained_Doesnt_Return_Child_Control()
- {
- Decorator current;
-
- var top = new StackPanel
- {
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children =
- {
- (current = new Decorator
- {
- Focusable = true,
- Child = new Button(),
- })
- }
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up);
-
- Assert.Null(result);
- }
- }
-}
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs
index a090dcd18d..ab0f5e2155 100644
--- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs
+++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs
@@ -140,37 +140,6 @@ namespace Avalonia.Input.UnitTests
Assert.Same(next, result);
}
- [Fact]
- public void Right_Should_Custom_Navigate_From_Outside()
- {
- Button current;
- Button next;
- var target = new CustomNavigatingStackPanel
- {
- Children =
- {
- new Button { Content = "Button 1" },
- new Button { Content = "Button 2" },
- (next = new Button { Content = "Button 3" }),
- },
- NextControl = next,
- };
-
- var root = new StackPanel
- {
- Children =
- {
- (current = new Button { Content = "Outside" }),
- target,
- },
- [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- };
-
- var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Right);
-
- Assert.Same(next, result);
- }
-
[Fact]
public void Tab_Should_Navigate_Outside_When_Null_Returned_As_Next()
{
diff --git a/tests/Avalonia.Interactivity.UnitTests/RoutedEventRegistryTests.cs b/tests/Avalonia.Interactivity.UnitTests/RoutedEventRegistryTests.cs
new file mode 100644
index 0000000000..b9ebdea064
--- /dev/null
+++ b/tests/Avalonia.Interactivity.UnitTests/RoutedEventRegistryTests.cs
@@ -0,0 +1,49 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Xunit;
+
+namespace Avalonia.Interactivity.UnitTests
+{
+ public class RoutedEventRegistryTests
+ {
+ [Fact]
+ public void Pointer_Events_Should_Be_Registered()
+ {
+ var expectedEvents = new List { InputElement.PointerPressedEvent, InputElement.PointerReleasedEvent };
+ var registeredEvents = RoutedEventRegistry.Instance.GetRegistered();
+ Assert.Contains(registeredEvents, expectedEvents.Contains);
+ }
+
+ [Fact]
+ public void ClickEvent_Should_Be_Registered_On_Button()
+ {
+ var expectedEvents = new List { Button.ClickEvent };
+ var registeredEvents = RoutedEventRegistry.Instance.GetRegistered();
+ Assert.Contains(registeredEvents, expectedEvents.Contains);
+ }
+
+ [Fact]
+ public void ClickEvent_Should_Not_Be_Registered_On_ContentControl()
+ {
+ // force ContentControl type to be loaded
+ new ContentControl();
+ var expectedEvents = new List { Button.ClickEvent };
+ var registeredEvents = RoutedEventRegistry.Instance.GetRegistered();
+ Assert.DoesNotContain(registeredEvents, expectedEvents.Contains);
+ }
+
+ [Fact]
+ public void InputElement_Events_Should_Not_Be_Registered_On_Button()
+ {
+ // force Button type to be loaded
+ new Button();
+ var expectedEvents = new List { InputElement.PointerPressedEvent, InputElement.PointerReleasedEvent };
+ var registeredEvents = RoutedEventRegistry.Instance.GetRegistered();
+ Assert.DoesNotContain(registeredEvents, expectedEvents.Contains);
+ }
+ }
+}