Browse Source

Merge pull request #3 from AvaloniaUI/master

Merge
pull/1874/head
William David Cossey 8 years ago
committed by GitHub
parent
commit
d63fdc4ac0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages.cake
  2. 3
      readme.md
  3. 9
      scripts/ReplaceNugetCache.ps1
  4. 1
      src/Avalonia.Base/AvaloniaLocator.cs
  5. 2
      src/Avalonia.Base/AvaloniaObject.cs
  6. 4
      src/Avalonia.Base/AvaloniaProperty.cs
  7. 3
      src/Avalonia.Base/Collections/AvaloniaList.cs
  8. 2
      src/Avalonia.Base/Collections/AvaloniaListConverter.cs
  9. 1
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  10. 4
      src/Avalonia.Base/Collections/IAvaloniaList.cs
  11. 4
      src/Avalonia.Base/Collections/IAvaloniaReadOnlyList.cs
  12. 4
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
  13. 10
      src/Avalonia.Base/Data/BindingChainException.cs
  14. 1
      src/Avalonia.Base/Data/BindingNotification.cs
  15. 2
      src/Avalonia.Base/Data/BindingOperations.cs
  16. 6
      src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs
  17. 3
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  18. 1
      src/Avalonia.Base/Data/Converters/IValueConverter.cs
  19. 3
      src/Avalonia.Base/Data/Converters/StringConverters.cs
  20. 2
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  21. 3
      src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
  22. 1
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  23. 1
      src/Avalonia.Base/Data/Core/ExpressionParseException.cs
  24. 6
      src/Avalonia.Base/Data/Core/ITransformNode.cs
  25. 2
      src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs
  26. 4
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  27. 1
      src/Avalonia.Base/Data/Core/LogicalNotNode.cs
  28. 5
      src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs
  29. 2
      src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs
  30. 2
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  31. 3
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  32. 8
      src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs
  33. 2
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  34. 1
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  35. 2
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  36. 2
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  37. 5
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  38. 2
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  39. 7
      src/Avalonia.Base/Data/Core/SettableNode.cs
  40. 2
      src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs
  41. 2
      src/Avalonia.Base/Data/IBinding.cs
  42. 2
      src/Avalonia.Base/Logging/Logger.cs
  43. 4
      src/Avalonia.Base/Platform/IRuntimePlatform.cs
  44. 1
      src/Avalonia.Base/PriorityLevel.cs
  45. 6
      src/Avalonia.Base/PriorityValue.cs
  46. 1
      src/Avalonia.Base/PropertyMetadata.cs
  47. 7
      src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
  48. 2
      src/Avalonia.Base/Utilities/CharacterReader.cs
  49. 3
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  50. 2
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  51. 4
      src/Avalonia.Base/Utilities/MathUtilities.cs
  52. 2
      src/Avalonia.Base/Utilities/SingleOrDictionary.cs
  53. 1
      src/Avalonia.Base/Utilities/SingleOrQueue.cs
  54. 11
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  55. 4
      src/Avalonia.Base/Utilities/WeakTimer.cs
  56. 6
      src/Avalonia.Base/ValueStore.cs
  57. 6
      src/Avalonia.Controls/AppBuilderBase.cs
  58. 1
      src/Avalonia.Controls/Application.cs
  59. 14
      src/Avalonia.Controls/AutoCompleteBox.cs
  60. 3
      src/Avalonia.Controls/Border.cs
  61. 6
      src/Avalonia.Controls/Calendar/Calendar.cs
  62. 3
      src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
  63. 1
      src/Avalonia.Controls/Calendar/CalendarDateRange.cs
  64. 4
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  65. 3
      src/Avalonia.Controls/Calendar/CalendarExtensions.cs
  66. 6
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  67. 11
      src/Avalonia.Controls/Calendar/DatePicker.cs
  68. 3
      src/Avalonia.Controls/Canvas.cs
  69. 148
      src/Avalonia.Controls/ContextMenu.cs
  70. 16
      src/Avalonia.Controls/Control.cs
  71. 1
      src/Avalonia.Controls/DataValidationErrors.cs
  72. 1
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  73. 5
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
  74. 3
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  75. 1
      src/Avalonia.Controls/Expander.cs
  76. 4
      src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
  77. 4
      src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs
  78. 2
      src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
  79. 8
      src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs
  80. 2
      src/Avalonia.Controls/Generators/TreeContainerIndex.cs
  81. 1
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  82. 5
      src/Avalonia.Controls/GridLength.cs
  83. 16
      src/Avalonia.Controls/GridSplitter.cs
  84. 5
      src/Avalonia.Controls/HotkeyManager.cs
  85. 4
      src/Avalonia.Controls/IApplicationLifecycle.cs
  86. 5
      src/Avalonia.Controls/IControl.cs
  87. 20
      src/Avalonia.Controls/IMenu.cs
  88. 39
      src/Avalonia.Controls/IMenuElement.cs
  89. 39
      src/Avalonia.Controls/IMenuItem.cs
  90. 2
      src/Avalonia.Controls/IScrollable.cs
  91. 2
      src/Avalonia.Controls/IVirtualizingPanel.cs
  92. 89
      src/Avalonia.Controls/ItemsControl.cs
  93. 250
      src/Avalonia.Controls/Menu.cs
  94. 301
      src/Avalonia.Controls/MenuItem.cs
  95. 8
      src/Avalonia.Controls/Mixins/ContentControlMixin.cs
  96. 8
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  97. 459
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  98. 4
      src/Avalonia.Controls/Platform/ExportWindowingSubsystemAttribute.cs
  99. 19
      src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs
  100. 8
      src/Avalonia.Controls/Platform/IPlatformIconLoader.cs

2
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
},
///////////////////////////////////////////////////////////////////////////////

3
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).

9
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\

1
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

2
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;

4
src/Avalonia.Base/AvaloniaProperty.cs

@ -231,7 +231,7 @@ namespace Avalonia
}
/// <summary>
/// Tests two <see cref="AvaloniaProperty"/>s for unequality.
/// Tests two <see cref="AvaloniaProperty"/>s for inequality.
/// </summary>
/// <param name="a">The first property.</param>
/// <param name="b">The second property.</param>
@ -558,4 +558,4 @@ namespace Avalonia
public override string ToString() => "(unset)";
}
}
}
}

3
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();
}
}
}
}

2
src/Avalonia.Base/Collections/AvaloniaListConverter.cs

@ -40,4 +40,4 @@ namespace Avalonia.Collections
return result;
}
}
}
}

1
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

4
src/Avalonia.Base/Collections/IAvaloniaList.cs

@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace Avalonia.Collections
{
/// <summary>
/// A notiftying list.
/// A notifying list.
/// </summary>
/// <typeparam name="T">The type of the items in the list.</typeparam>
public interface IAvaloniaList<T> : IList<T>, IAvaloniaReadOnlyList<T>
@ -64,4 +64,4 @@ namespace Avalonia.Collections
/// <param name="count">The number of items to remove.</param>
void RemoveRange(int index, int count);
}
}
}

4
src/Avalonia.Base/Collections/IAvaloniaReadOnlyList.cs

@ -8,10 +8,10 @@ using System.ComponentModel;
namespace Avalonia.Collections
{
/// <summary>
/// A read-only notiftying list.
/// A read-only notifying list.
/// </summary>
/// <typeparam name="T">The type of the items in the list.</typeparam>
public interface IAvaloniaReadOnlyList<out T> : IReadOnlyList<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
}
}
}

4
src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs

@ -25,7 +25,7 @@ namespace Avalonia.Collections
}
/// <summary>
/// Subcribes to the CollectionChanged event using a weak subscription.
/// Subscribes to the CollectionChanged event using a weak subscription.
/// </summary>
/// <param name="collection">The collection.</param>
/// <param name="handler">
@ -44,7 +44,7 @@ namespace Avalonia.Collections
}
/// <summary>
/// Subcribes to the CollectionChanged event using a weak subscription.
/// Subscribes to the CollectionChanged event using a weak subscription.
/// </summary>
/// <param name="collection">The collection.</param>
/// <param name="handler">

10
src/Avalonia.Base/Data/BindingChainException.cs

@ -6,7 +6,7 @@ using System;
namespace Avalonia.Data
{
/// <summary>
/// An exception returned through <see cref="BindingNotification"/> signalling that a
/// An exception returned through <see cref="BindingNotification"/> signaling that a
/// requested binding expression could not be evaluated because of a null in one of the links
/// of the binding chain.
/// </summary>
@ -15,14 +15,14 @@ namespace Avalonia.Data
private string _message;
/// <summary>
/// Initalizes a new instance of the <see cref="BindingChainException"/> class.
/// Initializes a new instance of the <see cref="BindingChainException"/> class.
/// </summary>
public BindingChainException()
{
}
/// <summary>
/// Initalizes a new instance of the <see cref="BindingChainException"/> class.
/// Initializes a new instance of the <see cref="BindingChainException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
public BindingChainException(string message)
@ -31,7 +31,7 @@ namespace Avalonia.Data
}
/// <summary>
/// Initalizes a new instance of the <see cref="BindingChainException"/> class.
/// Initializes a new instance of the <see cref="BindingChainException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
/// <param name="expression">The expression.</param>
@ -51,7 +51,7 @@ namespace Avalonia.Data
public string Expression { get; protected set; }
/// <summary>
/// Gets the point in the expression at which the error occured.
/// Gets the point in the expression at which the error occurred.
/// </summary>
public string ExpressionErrorPoint { get; protected set; }

1
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
{

2
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 <paramref name="anchor"/> parameter
/// can be used to provice this context.
/// can be used to provide this context.
/// </param>
/// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
public static IDisposable Apply(

6
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
{

3
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
{

1
src/Avalonia.Base/Data/Converters/IValueConverter.cs

@ -3,7 +3,6 @@
using System;
using System.Globalization;
using Avalonia.Data;
namespace Avalonia.Data.Converters
{

3
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
{

2
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

3
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

1
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;

1
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
{

6
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
{

2
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
{

4
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

1
src/Avalonia.Base/Data/Core/LogicalNotNode.cs

@ -3,7 +3,6 @@
using System;
using System.Globalization;
using Avalonia.Data;
namespace Avalonia.Data.Core
{

5
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
{

2
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
{

2
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
{

3
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;

8
src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs → 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
/// <see cref="BindingNotification"/>s.
/// </remarks>
public abstract class DataValidatiorBase : PropertyAccessorBase, IObserver<object>
public abstract class DataValidationBase : PropertyAccessorBase, IObserver<object>
{
private readonly IPropertyAccessor _inner;
/// <summary>
/// Initializes a new instance of the <see cref="DataValidatiorBase"/> class.
/// Initializes a new instance of the <see cref="DataValidationBase"/> class.
/// </summary>
/// <param name="inner">The inner property accessor.</param>
protected DataValidatiorBase(IPropertyAccessor inner)
protected DataValidationBase(IPropertyAccessor inner)
{
_inner = inner;
}
@ -77,4 +77,4 @@ namespace Avalonia.Data.Core.Plugins
PublishValue(notification);
}
}
}
}

2
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)

1
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
{

2
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<DataErrorsChangedEventArgs>
private class Validator : DataValidationBase, IWeakSubscriber<DataErrorsChangedEventArgs>
{
WeakReference _reference;
string _name;

2
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

5
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
{

2
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
{

7
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
{

2
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

2
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 <paramref name="anchor"/> parameter
/// can be used to provice this context.
/// can be used to provide this context.
/// </param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param>
/// <returns>

2
src/Avalonia.Base/Logging/Logger.cs

@ -11,7 +11,7 @@ namespace Avalonia.Logging
public static class Logger
{
/// <summary>
/// Gets or sets the application-defined sink that recieves the messages.
/// Gets or sets the application-defined sink that receives the messages.
/// </summary>
public static ILogSink Sink { get; set; }

4
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
{

1
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
{

6
src/Avalonia.Base/PriorityValue.cs

@ -12,12 +12,12 @@ using Avalonia.Utilities;
namespace Avalonia
{
/// <summary>
/// Maintains a list of prioritised bindings together with a current value.
/// Maintains a list of prioritized bindings together with a current value.
/// </summary>
/// <remarks>
/// Bindings, in the form of <see cref="IObservable{Object}"/>s are added to the object using
/// the <see cref="Add"/> method. With the observable is passed a priority, where lower values
/// represent higher priorites. The current <see cref="Value"/> is selected from the highest
/// represent higher priorities. The current <see cref="Value"/> is selected from the highest
/// priority binding that doesn't return <see cref="AvaloniaProperty.UnsetValue"/>. 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
}
/// <summary>
/// Updates the current <see cref="Value"/> and notifies all subscibers.
/// Updates the current <see cref="Value"/> and notifies all subscribers.
/// </summary>
/// <param name="value">The value to set.</param>
/// <param name="priority">The priority level that the value came from.</param>

1
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

7
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();
}
}
}
}

2
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
{

3
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
{

2
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
{

4
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
{

2
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
{

1
src/Avalonia.Base/Utilities/SingleOrQueue.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Utilities
{

11
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
/// <param name="to">The type to cast to.</param>
/// <param name="value">The value to cast.</param>
/// <param name="culture">The culture to use.</param>
/// <param name="result">If sucessful, contains the cast value.</param>
/// <returns>True if the cast was sucessful, otherwise false.</returns>
/// <param name="result">If successful, contains the cast value.</param>
/// <returns>True if the cast was successful, otherwise false.</returns>
public static bool TryConvert(Type to, object value, CultureInfo culture, out object result)
{
if (value == null)
@ -208,8 +207,8 @@ namespace Avalonia.Utilities
/// </summary>
/// <param name="to">The type to cast to.</param>
/// <param name="value">The value to cast.</param>
/// <param name="result">If sucessful, contains the cast value.</param>
/// <returns>True if the cast was sucessful, otherwise false.</returns>
/// <param name="result">If successful, contains the cast value.</param>
/// <returns>True if the cast was successful, otherwise false.</returns>
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.
/// </summary>
/// <returns>
/// True if the type is numberic; otherwise false.
/// True if the type is numeric; otherwise false.
/// </returns>
/// <remarks>
/// Boolean is not considered numeric.

4
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

6
src/Avalonia.Base/ValueStore.cs

@ -168,14 +168,14 @@ namespace Avalonia
return value;
}
private DeferredSetter<object> _defferedSetter;
private DeferredSetter<object> _deferredSetter;
public DeferredSetter<object> Setter
{
get
{
return _defferedSetter ??
(_defferedSetter = new DeferredSetter<object>());
return _deferredSetter ??
(_deferredSetter = new DeferredSetter<object>());
}
}
}

6
src/Avalonia.Controls/AppBuilderBase.cs

@ -57,14 +57,14 @@ namespace Avalonia.Controls
public Action<TAppBuilder> AfterSetupCallback { get; private set; } = builder => { };
/// <summary>
/// Gets or sets a method to call before Startis called on the <see cref="Application"/>.
/// Gets or sets a method to call before Start is called on the <see cref="Application"/>.
/// </summary>
public Action<TAppBuilder> BeforeStartCallback { get; private set; } = builder => { };
protected AppBuilderBase(IRuntimePlatform platform, Action<TAppBuilder> platformSevices)
protected AppBuilderBase(IRuntimePlatform platform, Action<TAppBuilder> platformServices)
{
RuntimePlatform = platform;
RuntimePlatformServicesInitializer = () => platformSevices((TAppBuilder)this);
RuntimePlatformServicesInitializer = () => platformServices((TAppBuilder)this);
}
/// <summary>

1
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;

14
src/Avalonia.Controls/AutoCompleteBox.cs

@ -352,8 +352,8 @@ namespace Avalonia.Controls
private Func<string, CancellationToken, Task<IEnumerable<object>>> _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;
}
}

3
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);
}
}
}
}

6
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
{

3
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
{

1
src/Avalonia.Controls/Calendar/CalendarDateRange.cs

@ -4,7 +4,6 @@
// All other rights reserved.
using System;
using System.Diagnostics;
namespace Avalonia.Controls
{

4
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
{

3
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
{

6
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
{

11
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);

3
src/Avalonia.Controls/Canvas.cs

@ -136,8 +136,9 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param>
/// <param name="wrap">Whether to wrap around when the first or last item is reached.</param>
/// <returns>The control.</returns>
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from)
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap)
{
// TODO: Implement this
return null;

148
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<ContextMenu, bool> IsOpenProperty =
AvaloniaProperty.RegisterDirect<ContextMenu, bool>(nameof(IsOpen), o => o.IsOpen);
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenu"/> class.
/// </summary>
public ContextMenu()
{
_interaction = AvaloniaLocator.Current.GetService<IMenuInteractionHandler>() ??
new DefaultMenuInteractionHandler();
}
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenu"/> class.
/// </summary>
/// <param name="interactionHandler">The menu interaction handler.</param>
public ContextMenu(IMenuInteractionHandler interactionHandler)
{
Contract.Requires<ArgumentNullException>(interactionHandler != null);
_interaction = interactionHandler;
}
/// <summary>
/// Initializes static members of the <see cref="ContextMenu"/> class.
@ -27,8 +48,6 @@ namespace Avalonia.Controls
static ContextMenu()
{
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
}
/// <summary>
@ -36,6 +55,36 @@ namespace Avalonia.Controls
/// </summary>
public bool IsOpen => _isOpen;
/// <inheritdoc/>
IMenuInteractionHandler IMenu.InteractionHandler => _interaction;
/// <inheritdoc/>
IMenuItem IMenuElement.SelectedItem
{
get
{
var index = SelectedIndex;
return (index != -1) ?
(IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
null;
}
set
{
SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
}
}
/// <inheritdoc/>
IEnumerable<IMenuItem> IMenuElement.SubItems
{
get
{
return ItemContainerGenerator.Containers
.Select(x => x.ContainerControl)
.OfType<IMenuItem>();
}
}
/// <summary>
/// Occurs when the value of the
/// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" />
@ -50,7 +99,6 @@ namespace Avalonia.Controls
/// </summary>
public event CancelEventHandler ContextMenuClosing;
/// <summary>
/// Called when the <see cref="Control.ContextMenu"/> property changes on a control.
/// </summary>
@ -71,62 +119,53 @@ namespace Avalonia.Controls
}
/// <summary>
/// Called when a submenu is clicked somewhere in the menu.
/// Opens the menu.
/// </summary>
/// <param name="e">The event args.</param>
private void OnContextMenuClick(RoutedEventArgs e)
{
Hide();
FocusManager.Instance.Focus(null);
e.Handled = true;
}
public void Open() => Open(null);
/// <summary>
/// Closes the menu.
/// Opens a context menu on the specified control.
/// </summary>
public void Hide()
/// <param name="control">The control.</param>
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);
}
/// <summary>
/// Shows a context menu for the specified control.
/// Closes the menu.
/// </summary>
/// <param name="control">The control.</param>
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();
}
}
}

16
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
}
/// <summary>
/// Gets the element that recieves the focus adorner.
/// Gets the element that receives the focus adorner.
/// </summary>
/// <returns>The control that recieves the focus adorner.</returns>
/// <returns>The control that receives the focus adorner.</returns>
protected virtual IControl GetTemplateFocusTarget()
{
return this;

1
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;

1
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;

5
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

3
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;

1
src/Avalonia.Controls/Expander.cs

@ -1,6 +1,5 @@
using Avalonia.Animation;
using Avalonia.Controls.Primitives;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{

4
src/Avalonia.Controls/Generators/IItemContainerGenerator.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls.Generators
Type ContainerType { get; }
/// <summary>
/// Signalled whenever new containers are materialized.
/// Signaled whenever new containers are materialized.
/// </summary>
event EventHandler<ItemContainerEventArgs> Materialized;
@ -110,4 +110,4 @@ namespace Avalonia.Controls.Generators
/// <returns>The index of the container, or -1 if not found.</returns>
int IndexFromContainer(IControl container);
}
}
}

4
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
{
/// <summary>
@ -15,4 +13,4 @@ namespace Avalonia.Controls.Generators
/// </summary>
TreeContainerIndex Index { get; }
}
}
}

2
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;

8
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<MenuItem>
{

2
src/Avalonia.Controls/Generators/TreeContainerIndex.cs

@ -22,7 +22,7 @@ namespace Avalonia.Controls.Generators
private readonly Dictionary<IControl, object> _containerToItem = new Dictionary<IControl, object>();
/// <summary>
/// Signalled whenever new containers are materialized.
/// Signaled whenever new containers are materialized.
/// </summary>
public event EventHandler<ItemContainerEventArgs> Materialized;

1
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;

5
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
}
}
}
}
}

16
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<DefinitionBase>().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<DefinitionBase>().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;
}
}
}
}

5
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;

4
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
{

5
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; }
}
}
}

20
src/Avalonia.Controls/IMenu.cs

@ -0,0 +1,20 @@
using Avalonia.Controls.Platform;
namespace Avalonia.Controls
{
/// <summary>
/// Represents a <see cref="Menu"/> or <see cref="ContextMenu"/>.
/// </summary>
public interface IMenu : IMenuElement
{
/// <summary>
/// Gets the menu interaction handler.
/// </summary>
IMenuInteractionHandler InteractionHandler { get; }
/// <summary>
/// Gets a value indicating whether the menu is open.
/// </summary>
bool IsOpen { get; }
}
}

39
src/Avalonia.Controls/IMenuElement.cs

@ -0,0 +1,39 @@
using System.Collections.Generic;
using Avalonia.Input;
namespace Avalonia.Controls
{
/// <summary>
/// Represents an <see cref="IMenu"/> or <see cref="IMenuItem"/>.
/// </summary>
public interface IMenuElement : IControl
{
/// <summary>
/// Gets or sets the currently selected submenu item.
/// </summary>
IMenuItem SelectedItem { get; set; }
/// <summary>
/// Gets the submenu items.
/// </summary>
IEnumerable<IMenuItem> SubItems { get; }
/// <summary>
/// Opens the menu or menu item.
/// </summary>
void Open();
/// <summary>
/// Closes the menu or menu item.
/// </summary>
void Close();
/// <summary>
/// Moves the submenu selection in the specified direction.
/// </summary>
/// <param name="direction">The direction.</param>
/// <param name="wrap">Whether to wrap after the first or last item.</param>
/// <returns>True if the selection was moved; otherwise false.</returns>
bool MoveSelection(NavigationDirection direction, bool wrap);
}
}

39
src/Avalonia.Controls/IMenuItem.cs

@ -0,0 +1,39 @@
namespace Avalonia.Controls
{
/// <summary>
/// Represents a <see cref="MenuItem"/>.
/// </summary>
public interface IMenuItem : IMenuElement
{
/// <summary>
/// Gets or sets a value that indicates whether the item has a submenu.
/// </summary>
bool HasSubMenu { get; }
/// <summary>
/// Gets a value indicating whether the mouse is currently over the menu item's submenu.
/// </summary>
bool IsPointerOverSubMenu { get; }
/// <summary>
/// Gets or sets a value that indicates whether the submenu of the <see cref="MenuItem"/> is
/// open.
/// </summary>
bool IsSubMenuOpen { get; set; }
/// <summary>
/// Gets a value that indicates whether the <see cref="MenuItem"/> is a top-level main menu item.
/// </summary>
bool IsTopLevel { get; }
/// <summary>
/// Gets the parent <see cref="IMenuElement"/>.
/// </summary>
new IMenuElement Parent { get; }
/// <summary>
/// Raises a click event on the menu item.
/// </summary>
void RaiseClick();
}
}

2
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
{

2
src/Avalonia.Controls/IVirtualizingPanel.cs

@ -14,7 +14,7 @@ namespace Avalonia.Controls
/// Gets or sets the controller for the virtualizing panel.
/// </summary>
/// <remarks>
/// 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.

89
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
}
/// <summary>
/// Caled when the <see cref="Items"/> property changes.
/// Handles directional navigation within the <see cref="ItemsControl"/>.
/// </summary>
/// <param name="e">The key events.</param>
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<IInputElement>()
.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);
}
/// <summary>
/// Called when the <see cref="Items"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
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;
}
}
}

250
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
{
/// <summary>
/// A top-level menu control.
/// </summary>
public class Menu : SelectingItemsControl, IFocusScope, IMainMenu
public class Menu : SelectingItemsControl, IFocusScope, IMainMenu, IMenu
{
/// <summary>
/// Defines the default items panel used by a <see cref="Menu"/>.
/// </summary>
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel { Orientation = Orientation.Horizontal });
/// <summary>
/// Defines the <see cref="IsOpen"/> property.
/// </summary>
@ -34,12 +27,42 @@ namespace Avalonia.Controls
nameof(IsOpen),
o => o.IsOpen);
/// <summary>
/// Defines the <see cref="MenuOpened"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> MenuOpenedEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(MenuOpened), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="MenuClosed"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> MenuClosedEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(MenuClosed), RoutingStrategies.Bubble);
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel { Orientation = Orientation.Horizontal });
private readonly IMenuInteractionHandler _interaction;
private bool _isOpen;
/// <summary>
/// Tracks event handlers added to the root of the visual tree.
/// Initializes a new instance of the <see cref="Menu"/> class.
/// </summary>
public Menu()
{
_interaction = AvaloniaLocator.Current.GetService<IMenuInteractionHandler>() ??
new DefaultMenuInteractionHandler();
}
/// <summary>
/// Initializes a new instance of the <see cref="Menu"/> class.
/// </summary>
private IDisposable _subscription;
/// <param name="interactionHandler">The menu interaction handler.</param>
public Menu(IMenuInteractionHandler interactionHandler)
{
Contract.Requires<ArgumentNullException>(interactionHandler != null);
_interaction = interactionHandler;
}
/// <summary>
/// Initializes static members of the <see cref="Menu"/> class.
@ -47,7 +70,6 @@ namespace Avalonia.Controls
static Menu()
{
ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel);
MenuItem.ClickEvent.AddClassHandler<Menu>(x => x.OnMenuClick, handledEventsToo: true);
MenuItem.SubmenuOpenedEvent.AddClassHandler<Menu>(x => x.OnSubmenuOpened);
}
@ -60,18 +82,52 @@ namespace Avalonia.Controls
private set { SetAndRaise(IsOpenProperty, ref _isOpen, value); }
}
/// <summary>
/// Gets the selected <see cref="MenuItem"/> container.
/// </summary>
private MenuItem SelectedMenuItem
/// <inheritdoc/>
IMenuInteractionHandler IMenu.InteractionHandler => _interaction;
/// <inheritdoc/>
IMenuItem IMenuElement.SelectedItem
{
get
{
var index = SelectedIndex;
return (index != -1) ?
(MenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
(IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
null;
}
set
{
SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
}
}
/// <inheritdoc/>
IEnumerable<IMenuItem> IMenuElement.SubItems
{
get
{
return ItemContainerGenerator.Containers
.Select(x => x.ContainerControl)
.OfType<IMenuItem>();
}
}
/// <summary>
/// Occurs when a <see cref="Menu"/> is opened.
/// </summary>
public event EventHandler<RoutedEventArgs> MenuOpened
{
add { AddHandler(MenuOpenedEvent, value); }
remove { RemoveHandler(MenuOpenedEvent, value); }
}
/// <summary>
/// Occurs when a <see cref="Menu"/> is closed.
/// </summary>
public event EventHandler<RoutedEventArgs> MenuClosed
{
add { AddHandler(MenuClosedEvent, value); }
remove { RemoveHandler(MenuClosedEvent, value); }
}
/// <summary>
@ -79,13 +135,22 @@ namespace Avalonia.Controls
/// </summary>
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,
});
}
}
/// <summary>
@ -93,9 +158,25 @@ namespace Avalonia.Controls
/// </summary>
public void Open()
{
SelectedIndex = 0;
SelectedMenuItem.Focus();
IsOpen = true;
if (!IsOpen)
{
IsOpen = true;
RaiseEvent(new RoutedEventArgs
{
RoutedEvent = MenuOpenedEvent,
Source = this,
});
}
}
/// <inheritdoc/>
bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap);
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<MenuItem>(this, MenuItem.HeaderProperty, null);
}
/// <inheritdoc/>
@ -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);
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_subscription.Dispose();
_interaction.Detach(this);
}
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<MenuItem>(this, MenuItem.HeaderProperty, null);
}
/// <summary>
/// Called when a key is pressed within the menu.
/// </summary>
/// <param name="e">The event args.</param>
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;
}
}
}
/// <summary>
/// Called when the menu loses focus.
/// </summary>
/// <param name="e">The event args.</param>
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
SelectedItem = null;
// Don't handle here: let the interaction handler handle it.
}
/// <summary>
@ -184,9 +213,7 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
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<MenuItem>())
{
@ -199,58 +226,5 @@ namespace Avalonia.Controls
IsOpen = true;
}
/// <summary>
/// Called when the top-level window is deactivated.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The event args.</param>
private void Deactivated(object sender, EventArgs e)
{
Close();
}
/// <summary>
/// Listens for non-client clicks and closes the menu when one is detected.
/// </summary>
/// <param name="e">The raw event.</param>
private void ListenForNonClientClick(RawInputEventArgs e)
{
var mouse = e as RawMouseEventArgs;
if (mouse?.Type == RawMouseEventType.NonClientLeftButtonDown)
{
Close();
}
}
/// <summary>
/// Called when a submenu is clicked somewhere in the menu.
/// </summary>
/// <param name="e">The event args.</param>
private void OnMenuClick(RoutedEventArgs e)
{
Close();
FocusManager.Instance.Focus(null);
e.Handled = true;
}
/// <summary>
/// Called when the pointer is pressed anywhere on the window.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The event args.</param>
private void TopLevelPreviewPointerPress(object sender, PointerPressedEventArgs e)
{
if (IsOpen)
{
var control = e.Source as ILogical;
if (!this.IsLogicalParentOf(control))
{
Close();
}
}
}
}
}

301
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
{
/// <summary>
/// A menu item control.
/// </summary>
public class MenuItem : HeaderedSelectingItemsControl, ISelectable
public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable
{
/// <summary>
/// Defines the <see cref="Command"/> property.
@ -62,6 +62,18 @@ namespace Avalonia.Controls
public static readonly RoutedEvent<RoutedEventArgs> ClickEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(Click), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="PointerEnterItem"/> event.
/// </summary>
public static readonly RoutedEvent<PointerEventArgs> PointerEnterItemEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>(nameof(PointerEnterItem), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="PointerLeaveItem"/> event.
/// </summary>
public static readonly RoutedEvent<PointerEventArgs> PointerLeaveItemEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>(nameof(PointerLeaveItem), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="SubmenuOpened"/> event.
/// </summary>
@ -72,15 +84,7 @@ namespace Avalonia.Controls
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
});
/// <summary>
/// The timer used to display submenus.
/// </summary>
private IDisposable _submenuTimer;
new FuncTemplate<IPanel>(() => new StackPanel());
/// <summary>
/// The submenu popup.
@ -96,16 +100,15 @@ namespace Avalonia.Controls
CommandProperty.Changed.Subscribe(CommandChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged);
IsSelectedProperty.Changed.AddClassHandler<MenuItem>(x => x.IsSelectedChanged);
ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
ClickEvent.AddClassHandler<MenuItem>(x => x.OnClick);
SubmenuOpenedEvent.AddClassHandler<MenuItem>(x => x.OnSubmenuOpened);
IsSubMenuOpenProperty.Changed.AddClassHandler<MenuItem>(x => x.SubMenuOpenChanged);
AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<MenuItem>(x => x.AccessKeyPressed);
}
public MenuItem()
{
}
/// <summary>
@ -117,6 +120,30 @@ namespace Avalonia.Controls
remove { RemoveHandler(ClickEvent, value); }
}
/// <summary>
/// Occurs when the pointer enters a menu item.
/// </summary>
/// <remarks>
/// A bubbling version of the <see cref="InputElement.PointerEnter"/> event for menu items.
/// </remarks>
public event EventHandler<PointerEventArgs> PointerEnterItem
{
add { AddHandler(PointerEnterItemEvent, value); }
remove { RemoveHandler(PointerEnterItemEvent, value); }
}
/// <summary>
/// Raised when the pointer leaves a menu item.
/// </summary>
/// <remarks>
/// A bubbling version of the <see cref="InputElement.PointerLeave"/> event for menu items.
/// </remarks>
public event EventHandler<PointerEventArgs> PointerLeaveItem
{
add { AddHandler(PointerLeaveItemEvent, value); }
remove { RemoveHandler(PointerLeaveItemEvent, value); }
}
/// <summary>
/// Occurs when a <see cref="MenuItem"/>'s submenu is opened.
/// </summary>
@ -188,10 +215,71 @@ namespace Avalonia.Controls
public bool HasSubMenu => !Classes.Contains(":empty");
/// <summary>
/// Gets a value that indicates whether the <see cref="MenuItem"/> is a top-level menu item.
/// Gets a value that indicates whether the <see cref="MenuItem"/> is a top-level main menu item.
/// </summary>
public bool IsTopLevel => Parent is Menu;
/// <inheritdoc/>
bool IMenuItem.IsPointerOverSubMenu => _popup.PopupRoot?.IsPointerOver ?? false;
/// <inheritdoc/>
IMenuElement IMenuItem.Parent => Parent as IMenuElement;
/// <inheritdoc/>
bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap);
/// <inheritdoc/>
IMenuItem IMenuElement.SelectedItem
{
get
{
var index = SelectedIndex;
return (index != -1) ?
(IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
null;
}
set
{
SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
}
}
/// <inheritdoc/>
IEnumerable<IMenuItem> IMenuElement.SubItems
{
get
{
return ItemContainerGenerator.Containers
.Select(x => x.ContainerControl)
.OfType<IMenuItem>();
}
}
/// <summary>
/// Opens the submenu.
/// </summary>
/// <remarks>
/// This has the same effect as setting <see cref="IsSubMenuOpen"/> to true.
/// </remarks>
public void Open() => IsSubMenuOpen = true;
/// <summary>
/// Closes the submenu.
/// </summary>
/// <remarks>
/// This has the same effect as setting <see cref="IsSubMenuOpen"/> to false.
/// </remarks>
public void Close() => IsSubMenuOpen = false;
/// <inheritdoc/>
void IMenuItem.RaiseClick() => RaiseEvent(new RoutedEventArgs(ClickEvent));
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
}
/// <summary>
/// Called when the <see cref="MenuItem"/> is clicked.
/// </summary>
@ -205,163 +293,43 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="MenuItem"/> recieves focus.
/// </summary>
/// <param name="e">The event args.</param>
/// <inheritdoc/>
protected override void OnGotFocus(GotFocusEventArgs e)
{
base.OnGotFocus(e);
IsSelected = true;
e.Handled = UpdateSelectionFromEventSource(e.Source, true);
}
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
}
/// <summary>
/// Called when a key is pressed in the <see cref="MenuItem"/>.
/// </summary>
/// <param name="e">The event args.</param>
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.
}
/// <summary>
/// Called when the pointer enters the <see cref="MenuItem"/>.
/// </summary>
/// <param name="e">The event args.</param>
/// <inheritdoc/>
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<MenuItem>()
.Where(x => x != this && x.IsSubMenuOpen))
{
sibling.CloseSubmenus();
sibling.IsSubMenuOpen = false;
sibling.IsSelected = false;
}
}
}
Device = e.Device,
RoutedEvent = PointerEnterItemEvent,
Source = this,
});
}
/// <summary>
/// Called when the pointer leaves the <see cref="MenuItem"/>.
/// </summary>
/// <param name="e">The event args.</param>
/// <inheritdoc/>
protected override void OnPointerLeave(PointerEventArgs e)
{
base.OnPointerLeave(e);
if (_submenuTimer != null)
RaiseEvent(new PointerEventArgs
{
_submenuTimer.Dispose();
_submenuTimer = null;
}
}
/// <summary>
/// Called when the pointer is pressed over the <see cref="MenuItem"/>.
/// </summary>
/// <param name="e">The event args.</param>
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,
});
}
/// <summary>
@ -374,7 +342,7 @@ namespace Avalonia.Controls
if (menuItem != null && menuItem.Parent == this)
{
foreach (var child in Items.OfType<MenuItem>())
foreach (var child in ((IMenuItem)this).SubItems)
{
if (child != menuItem && child.IsSubMenuOpen)
{
@ -395,31 +363,12 @@ namespace Avalonia.Controls
_popup.Closed += PopupClosed;
}
/// <summary>
/// Called when the menu item's access key is pressed.
/// </summary>
/// <param name="e">The event args.</param>
private void AccessKeyPressed(RoutedEventArgs e)
{
if (HasSubMenu)
{
SelectedIndex = 0;
IsSubMenuOpen = true;
}
else
{
RaiseEvent(new RoutedEventArgs(ClickEvent));
}
e.Handled = true;
}
/// <summary>
/// Closes all submenus of the menu item.
/// </summary>
private void CloseSubmenus()
{
foreach (var child in Items.OfType<MenuItem>())
foreach (var child in ((IMenuItem)this).SubItems)
{
child.IsSubMenuOpen = false;
}
@ -479,6 +428,18 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="IsSelected"/> property changes.
/// </summary>
/// <param name="e">The property change event.</param>
private void IsSelectedChanged(AvaloniaPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
Focus();
}
}
/// <summary>
/// Called when the <see cref="IsSubMenuOpen"/> property changes.
/// </summary>

8
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);
}
}

8
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
}
/// <summary>
/// Called by OnSpin when the spin direction is SpinDirection.Descrease.
/// Called by OnSpin when the spin direction is SpinDirection.Decrease.
/// </summary>
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;
}
}
}
}

459
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
{
/// <summary>
/// Provides the default keyboard and pointer interaction for menus.
/// </summary>
public class DefaultMenuInteractionHandler : IMenuInteractionHandler
{
private IDisposable _inputManagerSubscription;
private IRenderRoot _root;
public DefaultMenuInteractionHandler()
: this(Input.InputManager.Instance, DefaultDelayRun)
{
}
public DefaultMenuInteractionHandler(
IInputManager inputManager,
Action<Action, TimeSpan> 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<Action, TimeSpan> 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<ArgumentNullException>(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);
}
}
}

4
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
{

19
src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs

@ -0,0 +1,19 @@
namespace Avalonia.Controls.Platform
{
/// <summary>
/// Handles user interaction for menus.
/// </summary>
public interface IMenuInteractionHandler
{
/// <summary>
/// Attaches the interaction handler to a menu.
/// </summary>
/// <param name="menu">The menu.</param>
void Attach(IMenu menu);
/// <summary>
/// Detaches the interaction handler from the attached menu.
/// </summary>
void Detach(IMenu menu);
}
}

8
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
{

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save