Browse Source

Merge remote-tracking branch 'origin/master' into pr/894-buttons-are-disabled-when-there-is-a-null-in-the-binding-chain-for-command-3

Conflicts:
	tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs
pull/894/head
Steven Kirk 9 years ago
parent
commit
bf76c8423e
  1. 3
      .gitignore
  2. 25
      build.cake
  3. 2
      build/Moq.props
  4. 6
      build/XUnit.props
  5. 2
      docs/tutorial/from-wpf.md
  6. 1
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  7. 1
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  8. 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  9. 2
      src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs
  10. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  11. 3
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  12. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  13. 123
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  14. 38
      src/Avalonia.Base/Data/BindingNotification.cs
  15. 2
      src/Avalonia.Controls/Button.cs
  16. 13
      src/Avalonia.Controls/Control.cs
  17. 7
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  18. 38
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  19. 4
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  20. 4
      src/Avalonia.Controls/Primitives/Popup.cs
  21. 11
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  22. 4
      src/Avalonia.Controls/Templates/TemplateExtensions.cs
  23. 41
      src/Avalonia.Controls/ToolTip.cs
  24. 5
      src/Avalonia.Controls/TopLevel.cs
  25. 15
      src/Avalonia.Controls/Window.cs
  26. 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  27. 6
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  28. 3
      src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs
  29. 74
      src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs
  30. 4
      src/Avalonia.Diagnostics/ViewModels/LogicalTreeNode.cs
  31. 11
      src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs
  32. 10
      src/Avalonia.Diagnostics/ViewModels/TreeNode.cs
  33. 24
      src/Avalonia.Diagnostics/ViewModels/TreePageViewModel.cs
  34. 32
      src/Avalonia.Diagnostics/ViewModels/ViewModelBase.cs
  35. 7
      src/Avalonia.Diagnostics/ViewModels/VisualTreeNode.cs
  36. 20
      src/Avalonia.Diagnostics/Views/ControlDetailsView.cs
  37. 30
      src/Avalonia.Diagnostics/Views/PropertyChangedExtenions.cs
  38. 5
      src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs
  39. 3
      src/Avalonia.HtmlRenderer/HtmlControl.cs
  40. 7
      src/Avalonia.Input/IInputDevice.cs
  41. 8
      src/Avalonia.Input/IInputRoot.cs
  42. 3
      src/Avalonia.Input/IKeyboardDevice.cs
  43. 1
      src/Avalonia.Input/InputManager.cs
  44. 12
      src/Avalonia.Input/KeyboardDevice.cs
  45. 31
      src/Avalonia.Input/MouseDevice.cs
  46. 46
      src/Avalonia.Input/Navigation/DirectionalNavigation.cs
  47. 6
      src/Avalonia.Input/Navigation/FocusExtensions.cs
  48. 46
      src/Avalonia.Input/Navigation/TabNavigation.cs
  49. 97
      src/Avalonia.Layout/LayoutManager.cs
  50. 18
      src/Avalonia.Layout/Layoutable.cs
  51. 10
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  52. 6
      src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs
  53. 14
      src/Avalonia.Styling/Styling/DescendentSelector.cs
  54. 6
      src/Avalonia.Styling/Styling/Selectors.cs
  55. 2
      src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
  56. 2
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  57. 13
      src/Avalonia.Visuals/Vector.cs
  58. 6
      src/Avalonia.Visuals/Visual.cs
  59. 16
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  60. 1
      src/Gtk/Avalonia.Gtk/GtkPlatform.cs
  61. 2
      src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs
  62. 3
      src/Gtk/Avalonia.Gtk/TopLevelImpl.cs
  63. 1
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  64. 5
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  65. 2
      src/Gtk/Avalonia.Gtk3/Interop/GObject.cs
  66. 2
      src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs
  67. 3
      src/Gtk/Avalonia.Gtk3/SystemDialogs.cs
  68. 1
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  69. 1
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  70. 6
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs
  71. 1
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  72. 4
      src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs
  73. 2
      src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs
  74. 10
      src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorGrammar.cs
  75. 6
      src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorParser.cs
  76. 4
      src/Markup/Avalonia.Markup/Data/BindingExpression.cs
  77. 2
      src/Skia/Avalonia.Skia/BitmapImpl.cs
  78. 2
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  79. 2
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  80. 15
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  81. 50
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  82. 15
      src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs
  83. 7
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  84. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs
  85. 2
      src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs
  86. 1
      src/Windows/Avalonia.Win32/Win32Platform.cs
  87. 6
      src/Windows/Avalonia.Win32/WindowFramebuffer.cs
  88. 2
      src/Windows/Avalonia.Win32/WindowImpl.cs
  89. 4
      src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs
  90. 2
      src/iOS/Avalonia.iOS/TopLevelImpl.cs
  91. 1
      src/iOS/Avalonia.iOS/iOSPlatform.cs
  92. 1
      tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
  93. 134
      tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs
  94. 2
      tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs
  95. 3
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  96. 65
      tests/Avalonia.Benchmarks/Layout/Measure.cs
  97. 1
      tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
  98. 4
      tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
  99. 2
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  100. 291
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

3
.gitignore

@ -162,7 +162,8 @@ $RECYCLE.BIN/
################# #################
## Cake ## Cake
################# #################
tools/ tools/*
!tools/packages.config
.nuget .nuget
artifacts/ artifacts/
nuget nuget

25
build.cake

@ -11,7 +11,7 @@
// TOOLS // TOOLS
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#tool "nuget:?package=xunit.runner.console&version=2.1.0" #tool "nuget:?package=xunit.runner.console&version=2.2.0"
#tool "nuget:?package=OpenCover" #tool "nuget:?package=OpenCover"
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -98,7 +98,6 @@ Task("Clean")
CleanDirectory(parameters.TestsRoot); CleanDirectory(parameters.TestsRoot);
}); });
Task("Restore-NuGet-Packages") Task("Restore-NuGet-Packages")
.IsDependentOn("Clean") .IsDependentOn("Clean")
.WithCriteria(parameters.IsRunningOnWindows) .WithCriteria(parameters.IsRunningOnWindows)
@ -171,23 +170,25 @@ void RunCoreTest(string dir, Parameters parameters, bool net461Only)
continue; continue;
Information("Running for " + fw); Information("Running for " + fw);
DotNetCoreTest(System.IO.Path.Combine(dir, System.IO.Path.GetFileName(dir)+".csproj"), DotNetCoreTest(System.IO.Path.Combine(dir, System.IO.Path.GetFileName(dir)+".csproj"),
new DotNetCoreTestSettings{Framework = fw}); new DotNetCoreTestSettings {
Configuration = parameters.Configuration,
Framework = fw
});
} }
} }
Task("Run-Net-Core-Unit-Tests") Task("Run-Net-Core-Unit-Tests")
.IsDependentOn("Clean") .IsDependentOn("Clean")
.Does(() => { .Does(() => {
RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false); RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false);
//RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false);
//RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
}); });
Task("Run-Unit-Tests") Task("Run-Unit-Tests")

2
build/Moq.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="Moq" Version="4.7.1" /> <PackageReference Include="Moq" Version="4.7.25" />
</ItemGroup> </ItemGroup>
</Project> </Project>

6
build/XUnit.props

@ -7,7 +7,9 @@
<PackageReference Include="xunit.extensibility.core" Version="2.2.0" /> <PackageReference Include="xunit.extensibility.core" Version="2.2.0" />
<PackageReference Include="xunit.extensibility.execution" Version="2.2.0" /> <PackageReference Include="xunit.extensibility.execution" Version="2.2.0" />
<PackageReference Include="xunit.runner.console" Version="2.2.0" /> <PackageReference Include="xunit.runner.console" Version="2.2.0" />
<PackageReference Condition="'$(TargetFramework)' == 'net461'" Include="xunit.runner.visualstudio" Version="2.2.0" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Condition="'$(TargetFramework)' == 'netcoreapp1.1'" Include="Microsoft.NET.Test.Sdk" Version="15.0.0" /> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp1.1'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
docs/tutorial/from-wpf.md

@ -33,7 +33,7 @@ placed in a `DataTemplates` collection on each control (and on `Application`):
<TextBox Text="{Binding Name}"/> <TextBox Text="{Binding Name}"/>
</Border> </Border>
</DataTemplate> </DataTemplate>
</UserControl.Styles> </UserControl.DataTemplates>
<!-- Assuming that DataContext.Foo is an object of type <!-- Assuming that DataContext.Foo is an object of type
MyApp.ViewModels.FooViewModel then a red border with a corner MyApp.ViewModels.FooViewModel then a red border with a corner
radius of 8 containing a TextBox will be displayed here --> radius of 8 containing a TextBox will be displayed here -->

1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -22,6 +22,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />

1
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@ -181,5 +181,6 @@
</ItemGroup> </ItemGroup>
<Import Project="..\..\..\build\Rx.props" /> <Import Project="..\..\..\build\Rx.props" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\..\build\SkiaSharp.props" />
<Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" /> <Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
</Project> </Project>

1
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -51,7 +51,6 @@ namespace Avalonia.Android
.Bind<IClipboard>().ToTransient<ClipboardImpl>() .Bind<IClipboard>().ToTransient<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>() .Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>() .Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
.Bind<IMouseDevice>().ToSingleton<AndroidMouseDevice>()
.Bind<IPlatformSettings>().ToConstant(Instance) .Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory) .Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface()) .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())

2
src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs

@ -4,6 +4,8 @@ namespace Avalonia.Android.Platform.Input
{ {
public class AndroidMouseDevice : MouseDevice public class AndroidMouseDevice : MouseDevice
{ {
public static AndroidMouseDevice Instance { get; } = new AndroidMouseDevice();
public AndroidMouseDevice() public AndroidMouseDevice()
{ {

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@ -44,7 +44,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public int Width { get; } public int Width { get; }
public int Height { get; } public int Height { get; }
public int RowBytes { get; } public int RowBytes { get; }
public Size Dpi { get; } = new Size(96, 96); public Vector Dpi { get; } = new Vector(96, 96);
public PixelFormat Format { get; } public PixelFormat Format { get; }
[DllImport("android")] [DllImport("android")]

3
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -10,6 +10,7 @@ using Avalonia.Platform;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Android.Platform.Input;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
@ -65,6 +66,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
} }
} }
public IMouseDevice MouseDevice => AndroidMouseDevice.Instance;
public Action Closed { get; set; } public Action Closed { get; set; }
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }

2
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@ -71,7 +71,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
if (x <= _point.X && r >= _point.X && y <= _point.Y && b >= _point.Y) if (x <= _point.X && r >= _point.X && y <= _point.Y && b >= _point.Y)
{ {
var inputRoot = _getInputRoot(); var inputRoot = _getInputRoot();
var mouseDevice = MouseDevice.Instance; var mouseDevice = Avalonia.Android.Platform.Input.AndroidMouseDevice.Instance;
//in order the controls to work in a predictable way //in order the controls to work in a predictable way
//we need to generate mouse move before first mouse down event //we need to generate mouse move before first mouse down event

123
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
@ -59,7 +60,8 @@ namespace Avalonia.Collections
/// the index in the collection and the item. /// the index in the collection and the item.
/// </param> /// </param>
/// <param name="reset"> /// <param name="reset">
/// An action called when the collection is reset. /// An action called when the collection is reset. This will be followed by calls to
/// <paramref name="added"/> for each item present in the collection after the reset.
/// </param> /// </param>
/// <returns>A disposable used to terminate the subscription.</returns> /// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable ForEachItem<T>( public static IDisposable ForEachItem<T>(
@ -68,112 +70,38 @@ namespace Avalonia.Collections
Action<int, T> removed, Action<int, T> removed,
Action reset) Action reset)
{ {
int index; void Add(int index, IList items)
NotifyCollectionChangedEventHandler handler = (_, e) =>
{ {
switch (e.Action) foreach (T item in items)
{ {
case NotifyCollectionChangedAction.Add: added(index++, item);
index = e.NewStartingIndex;
foreach (T item in e.NewItems)
{
added(index++, item);
}
break;
case NotifyCollectionChangedAction.Replace:
index = e.OldStartingIndex;
foreach (T item in e.OldItems)
{
removed(index++, item);
}
index = e.NewStartingIndex;
foreach (T item in e.NewItems)
{
added(index++, item);
}
break;
case NotifyCollectionChangedAction.Remove:
index = e.OldStartingIndex;
foreach (T item in e.OldItems)
{
removed(index++, item);
}
break;
case NotifyCollectionChangedAction.Reset:
if (reset == null)
{
throw new InvalidOperationException(
"Reset called on collection without reset handler.");
}
reset();
break;
} }
}; }
index = 0; void Remove(int index, IList items)
foreach (T i in collection)
{ {
added(index++, i); for (var i = items.Count - 1; i >= 0; --i)
{
removed(index + i, (T)items[i]);
}
} }
collection.CollectionChanged += handler;
return Disposable.Create(() => collection.CollectionChanged -= handler);
}
/// <summary>
/// Invokes an action for each item in a collection and subsequently each item added or
/// removed from the collection.
/// </summary>
/// <typeparam name="T">The type of the collection items.</typeparam>
/// <param name="collection">The collection.</param>
/// <param name="added">
/// An action called initially with all items in the collection and subsequently with a
/// list of items added to the collection. The parameters passed are the index of the
/// first item added to the collection and the items added.
/// </param>
/// <param name="removed">
/// An action called with all items removed from the collection. The parameters passed
/// are the index of the first item removed from the collection and the items removed.
/// </param>
/// <param name="reset">
/// An action called when the collection is reset.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable ForEachItem<T>(
this IAvaloniaReadOnlyList<T> collection,
Action<int, IEnumerable<T>> added,
Action<int, IEnumerable<T>> removed,
Action reset)
{
NotifyCollectionChangedEventHandler handler = (_, e) => NotifyCollectionChangedEventHandler handler = (_, e) =>
{ {
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
added(e.NewStartingIndex, e.NewItems.Cast<T>()); Add(e.NewStartingIndex, e.NewItems);
break; break;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Replace:
removed(e.OldStartingIndex, e.OldItems.Cast<T>()); Remove(e.OldStartingIndex, e.OldItems);
added(e.NewStartingIndex, e.NewItems.Cast<T>()); Add(e.NewStartingIndex, e.NewItems);
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
removed(e.OldStartingIndex, e.OldItems.Cast<T>()); Remove(e.OldStartingIndex, e.OldItems);
break; break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
@ -184,16 +112,31 @@ namespace Avalonia.Collections
} }
reset(); reset();
Add(0, (IList)collection);
break; break;
} }
}; };
added(0, collection); Add(0, (IList)collection);
collection.CollectionChanged += handler; collection.CollectionChanged += handler;
return Disposable.Create(() => collection.CollectionChanged -= handler); return Disposable.Create(() => collection.CollectionChanged -= handler);
} }
public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(
this IAvaloniaReadOnlyList<TSource> collection,
Func<TSource, TDerived> select)
{
var result = new AvaloniaList<TDerived>();
collection.ForEachItem(
(i, item) => result.Insert(i, select(item)),
(i, item) => result.RemoveAt(i),
() => result.Clear());
return result;
}
/// <summary> /// <summary>
/// Listens for property changed events from all items in a collection. /// Listens for property changed events from all items in a collection.
/// </summary> /// </summary>

38
src/Avalonia.Base/Data/BindingNotification.cs

@ -45,11 +45,7 @@ namespace Avalonia.Data
public static readonly BindingNotification UnsetValue = public static readonly BindingNotification UnsetValue =
new BindingNotification(AvaloniaProperty.UnsetValue); new BindingNotification(AvaloniaProperty.UnsetValue);
// Null cannot be held in WeakReference as it's indistinguishable from an expired value so private object _value;
// use this value in its place.
private static readonly object NullValue = new object();
private WeakReference<object> _value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BindingNotification"/> class. /// Initializes a new instance of the <see cref="BindingNotification"/> class.
@ -57,7 +53,7 @@ namespace Avalonia.Data
/// <param name="value">The binding value.</param> /// <param name="value">The binding value.</param>
public BindingNotification(object value) public BindingNotification(object value)
{ {
_value = new WeakReference<object>(value ?? NullValue); _value = value;
} }
/// <summary> /// <summary>
@ -74,6 +70,7 @@ namespace Avalonia.Data
Error = error; Error = error;
ErrorType = errorType; ErrorType = errorType;
_value = AvaloniaProperty.UnsetValue;
} }
/// <summary> /// <summary>
@ -85,7 +82,7 @@ namespace Avalonia.Data
public BindingNotification(Exception error, BindingErrorType errorType, object fallbackValue) public BindingNotification(Exception error, BindingErrorType errorType, object fallbackValue)
: this(error, errorType) : this(error, errorType)
{ {
_value = new WeakReference<object>(fallbackValue ?? NullValue); _value = fallbackValue;
} }
/// <summary> /// <summary>
@ -96,31 +93,12 @@ namespace Avalonia.Data
/// If this property is read when <see cref="HasValue"/> is false then it will return /// If this property is read when <see cref="HasValue"/> is false then it will return
/// <see cref="AvaloniaProperty.UnsetValue"/>. /// <see cref="AvaloniaProperty.UnsetValue"/>.
/// </remarks> /// </remarks>
public object Value public object Value => _value;
{
get
{
if (_value != null)
{
object result;
if (_value.TryGetTarget(out result))
{
return result == NullValue ? null : result;
}
}
// There's the possibility of a race condition in that HasValue can return true,
// and then the value is GC'd before Value is read. We should be ok though as
// we return UnsetValue which should be a safe alternative.
return AvaloniaProperty.UnsetValue;
}
}
/// <summary> /// <summary>
/// Gets a value indicating whether <see cref="Value"/> should be pushed to the target. /// Gets a value indicating whether <see cref="Value"/> should be pushed to the target.
/// </summary> /// </summary>
public bool HasValue => _value != null; public bool HasValue => _value != AvaloniaProperty.UnsetValue;
/// <summary> /// <summary>
/// Gets the error that occurred on the source, if any. /// Gets the error that occurred on the source, if any.
@ -249,7 +227,7 @@ namespace Avalonia.Data
/// </summary> /// </summary>
public void ClearValue() public void ClearValue()
{ {
_value = null; _value = AvaloniaProperty.UnsetValue;
} }
/// <summary> /// <summary>
@ -257,7 +235,7 @@ namespace Avalonia.Data
/// </summary> /// </summary>
public void SetValue(object value) public void SetValue(object value)
{ {
_value = new WeakReference<object>(value ?? NullValue); _value = value;
} }
/// <inheritdoc/> /// <inheritdoc/>

2
src/Avalonia.Controls/Button.cs

@ -238,7 +238,7 @@ namespace Avalonia.Controls
PseudoClasses.Remove(":pressed"); PseudoClasses.Remove(":pressed");
e.Handled = true; e.Handled = true;
if (ClickMode == ClickMode.Release && Classes.Contains(":pointerover")) if (ClickMode == ClickMode.Release && new Rect(Bounds.Size).Contains(e.GetPosition(this)))
{ {
RaiseClickEvent(); RaiseClickEvent();
} }

13
src/Avalonia.Controls/Control.cs

@ -118,6 +118,7 @@ namespace Avalonia.Controls
public Control() public Control()
{ {
_nameScope = this as INameScope; _nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot;
} }
/// <summary> /// <summary>
@ -369,6 +370,12 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc/>
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnAttachedToLogicalTreeCore(e);
}
/// <inheritdoc/> /// <inheritdoc/>
void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
@ -418,7 +425,7 @@ namespace Avalonia.Controls
if (_isAttachedToLogicalTree) if (_isAttachedToLogicalTree)
{ {
var oldRoot = FindStyleRoot(old); var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
if (oldRoot == null) if (oldRoot == null)
{ {
@ -436,7 +443,7 @@ namespace Avalonia.Controls
_parent = (IControl)parent; _parent = (IControl)parent;
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true) if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
{ {
var newRoot = FindStyleRoot(this); var newRoot = FindStyleRoot(this);
@ -469,7 +476,7 @@ namespace Avalonia.Controls
{ {
if (!IsInitialized) if (!IsInitialized)
{ {
foreach (var i in this.GetSelfAndVisualDescendents()) foreach (var i in this.GetSelfAndVisualDescendants())
{ {
var c = i as IControl; var c = i as IControl;

7
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using JetBrains.Annotations;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -93,5 +94,11 @@ namespace Avalonia.Platform
/// Gets or sets a method called when the underlying implementation is destroyed. /// Gets or sets a method called when the underlying implementation is destroyed.
/// </summary> /// </summary>
Action Closed { get; set; } Action Closed { get; set; }
/// <summary>
/// Gets a mouse device associated with toplevel
/// </summary>
[CanBeNull]
IMouseDevice MouseDevice { get; }
} }
} }

38
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
{ {
@ -88,6 +89,7 @@ namespace Avalonia.Controls.Presenters
static ContentPresenter() static ContentPresenter()
{ {
ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged); ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged); TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
} }
@ -313,27 +315,22 @@ namespace Avalonia.Controls.Presenters
if (content != null && newChild == null) if (content != null && newChild == null)
{ {
// We have content and it isn't a control, so first try to recycle the existing var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
// child control to display the new data by querying if the template that created
// the child can recycle items and that it also matches the new data. // We have content and it isn't a control, so if the new data template is the same
if (oldChild != null && // as the old data template, try to recycle the existing child control to display
_dataTemplate != null && // the new data.
_dataTemplate.SupportsRecycling && if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
_dataTemplate.Match(content))
{ {
newChild = oldChild; newChild = oldChild;
} }
else else
{ {
// We couldn't recycle an existing control so find a data template for the data _dataTemplate = dataTemplate;
// and use it to create a control.
_dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
newChild = _dataTemplate.Build(content); newChild = _dataTemplate.Build(content);
// Try to give the new control its own name scope. // Give the new control its own name scope.
var controlResult = newChild as Control; if (newChild is Control controlResult)
if (controlResult != null)
{ {
NameScope.SetNameScope(controlResult, new NameScope()); NameScope.SetNameScope(controlResult, new NameScope());
} }
@ -424,6 +421,19 @@ namespace Avalonia.Controls.Presenters
private void ContentChanged(AvaloniaPropertyChangedEventArgs e) private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{ {
_createdChild = false; _createdChild = false;
if (((ILogical)this).IsAttachedToLogicalTree)
{
UpdateChild();
}
else if (Child != null)
{
VisualChildren.Remove(Child);
LogicalChildren.Remove(Child);
Child = null;
_dataTemplate = null;
}
InvalidateMeasure(); InvalidateMeasure();
} }

4
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -111,7 +111,7 @@ namespace Avalonia.Controls.Presenters
/// <param name="target">The target visual.</param> /// <param name="target">The target visual.</param>
/// <param name="targetRect">The portion of the target visual to bring into view.</param> /// <param name="targetRect">The portion of the target visual to bring into view.</param>
/// <returns>True if the scroll offset was changed; otherwise false.</returns> /// <returns>True if the scroll offset was changed; otherwise false.</returns>
public bool BringDescendentIntoView(IVisual target, Rect targetRect) public bool BringDescendantIntoView(IVisual target, Rect targetRect)
{ {
if (Child == null) if (Child == null)
{ {
@ -262,7 +262,7 @@ namespace Avalonia.Controls.Presenters
private void BringIntoViewRequested(object sender, RequestBringIntoViewEventArgs e) private void BringIntoViewRequested(object sender, RequestBringIntoViewEventArgs e)
{ {
e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect); e.Handled = BringDescendantIntoView(e.TargetObject, e.TargetRect);
} }
private void ChildChanged(AvaloniaPropertyChangedEventArgs e) private void ChildChanged(AvaloniaPropertyChangedEventArgs e)

4
src/Avalonia.Controls/Primitives/Popup.cs

@ -340,11 +340,11 @@ namespace Avalonia.Controls.Primitives
switch (mode) switch (mode)
{ {
case PlacementMode.Pointer: case PlacementMode.Pointer:
if (MouseDevice.Instance != null) if(PopupRoot != null)
{ {
// Scales the Horizontal and Vertical offset to screen co-ordinates. // Scales the Horizontal and Vertical offset to screen co-ordinates.
var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling); var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling);
return MouseDevice.Instance.Position + screenOffset; return (((IInputRoot)PopupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
} }
return default(Point); return default(Point);

11
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -285,6 +285,17 @@ namespace Avalonia.Controls.Primitives
return this; return this;
} }
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (VisualChildren.Count > 0)
{
((ILogical)VisualChildren[0]).NotifyAttachedToLogicalTree(e);
}
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {

4
src/Avalonia.Controls/Templates/TemplateExtensions.cs

@ -31,9 +31,9 @@ namespace Avalonia.Controls.Templates
if (child.TemplatedParent != null) if (child.TemplatedParent != null)
{ {
foreach (var descendent in GetTemplateChildren(child, templatedParent)) foreach (var descendant in GetTemplateChildren(child, templatedParent))
{ {
yield return descendent; yield return descendant;
} }
} }
} }

41
src/Avalonia.Controls/ToolTip.cs

@ -105,17 +105,21 @@ namespace Avalonia.Controls
{ {
if (control != null && control.IsVisible && control.GetVisualRoot() != null) if (control != null && control.IsVisible && control.GetVisualRoot() != null)
{ {
if (s_popup != null) var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control);
var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
if (s_popup == null)
{ {
throw new AvaloniaInternalException("Previous ToolTip not disposed."); s_popup = new PopupRoot();
s_popup.Content = new ToolTip();
}
else
{
((ISetLogicalParent)s_popup).SetParent(null);
} }
var cp = MouseDevice.Instance?.GetPosition(control);
var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
s_popup = new PopupRoot();
((ISetLogicalParent)s_popup).SetParent(control); ((ISetLogicalParent)s_popup).SetParent(control);
s_popup.Content = new ToolTip { Content = GetTip(control) }; ((ToolTip)s_popup.Content).Content = GetTip(control);
s_popup.Position = position; s_popup.Position = position;
s_popup.Show(); s_popup.Show();
@ -147,16 +151,23 @@ namespace Avalonia.Controls
{ {
if (s_popup != null) if (s_popup != null)
{ {
// Clear the ToolTip's Content in case it has control content: this will DisposeTooltip();
// reset its visual parent allowing it to be used again. s_show.OnNext(null);
((ToolTip)s_popup.Content).Content = null;
// Dispose of the popup.
s_popup.Dispose();
s_popup = null;
} }
}
}
private static void DisposeTooltip()
{
if (s_popup != null)
{
// Clear the ToolTip's Content in case it has control content: this will
// reset its visual parent allowing it to be used again.
((ToolTip)s_popup.Content).Content = null;
s_show.OnNext(null); // Dispose of the popup.
s_popup.Dispose();
s_popup = null;
} }
} }
} }

5
src/Avalonia.Controls/TopLevel.cs

@ -163,6 +163,9 @@ namespace Avalonia.Controls
set { SetValue(PointerOverElementProperty, value); } set { SetValue(PointerOverElementProperty, value); }
} }
/// <inheritdoc/>
IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window. /// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary> /// </summary>
@ -253,7 +256,7 @@ namespace Avalonia.Controls
/// <param name="scaling">The window scaling.</param> /// <param name="scaling">The window scaling.</param>
protected virtual void HandleScalingChanged(double scaling) protected virtual void HandleScalingChanged(double scaling)
{ {
foreach (ILayoutable control in this.GetSelfAndVisualDescendents()) foreach (ILayoutable control in this.GetSelfAndVisualDescendants())
{ {
control.InvalidateMeasure(); control.InvalidateMeasure();
} }

15
src/Avalonia.Controls/Window.cs

@ -47,12 +47,12 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope
{ {
private static IList<Window> s_windows = new List<Window>(); private static List<Window> s_windows = new List<Window>();
/// <summary> /// <summary>
/// Retrieves an enumeration of all Windows in the currently running application. /// Retrieves an enumeration of all Windows in the currently running application.
/// </summary> /// </summary>
public static IList<Window> OpenWindows => s_windows; public static IReadOnlyList<Window> OpenWindows => s_windows;
/// <summary> /// <summary>
/// Defines the <see cref="SizeToContent"/> property. /// Defines the <see cref="SizeToContent"/> property.
@ -238,6 +238,11 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public override void Show() public override void Show()
{ {
if (IsVisible)
{
return;
}
s_windows.Add(this); s_windows.Add(this);
EnsureInitialized(); EnsureInitialized();
@ -272,6 +277,11 @@ namespace Avalonia.Controls
/// </returns> /// </returns>
public Task<TResult> ShowDialog<TResult>() public Task<TResult> ShowDialog<TResult>()
{ {
if (IsVisible)
{
throw new InvalidOperationException("The window is already being shown.");
}
s_windows.Add(this); s_windows.Add(this);
EnsureInitialized(); EnsureInitialized();
@ -360,6 +370,7 @@ namespace Avalonia.Controls
protected override void HandleClosed() protected override void HandleClosed()
{ {
IsVisible = false; IsVisible = false;
s_windows.Remove(this);
base.HandleClosed(); base.HandleClosed();
} }

1
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -34,7 +34,6 @@
<ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" /> <ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> <ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" /> <ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" /> <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />

6
src/Avalonia.Diagnostics/DevTools.xaml.cs

@ -11,7 +11,6 @@ using Avalonia.Input.Raw;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia namespace Avalonia
{ {
@ -74,7 +73,7 @@ namespace Avalonia.Diagnostics
Content = devTools, Content = devTools,
DataTemplates = new DataTemplates DataTemplates = new DataTemplates
{ {
new ViewLocator<ReactiveObject>(), new ViewLocator<ViewModelBase>(),
} }
}; };
@ -107,7 +106,8 @@ namespace Avalonia.Diagnostics
if ((e.Modifiers) == modifiers) if ((e.Modifiers) == modifiers)
{ {
var point = MouseDevice.Instance.GetPosition(Root);
var point = (Root.VisualRoot as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default(Point);
var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible)) var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible))
.FirstOrDefault(); .FirstOrDefault();

3
src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -4,11 +4,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class ControlDetailsViewModel : ReactiveObject internal class ControlDetailsViewModel : ViewModelBase
{ {
public ControlDetailsViewModel(IVisual control) public ControlDetailsViewModel(IVisual control)
{ {

74
src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs

@ -5,32 +5,50 @@ using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class DevToolsViewModel : ReactiveObject internal class DevToolsViewModel : ViewModelBase
{ {
private ReactiveObject _content; private ViewModelBase _content;
private int _selectedTab; private int _selectedTab;
private TreePageViewModel _logicalTree; private TreePageViewModel _logicalTree;
private TreePageViewModel _visualTree; private TreePageViewModel _visualTree;
private string _focusedControl;
private readonly ObservableAsPropertyHelper<string> _focusedControl; private string _pointerOverElement;
private readonly ObservableAsPropertyHelper<string> _pointerOverElement;
public DevToolsViewModel(IControl root) public DevToolsViewModel(IControl root)
{ {
_logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root)); _logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root));
_visualTree = new TreePageViewModel(VisualTreeNode.Create(root)); _visualTree = new TreePageViewModel(VisualTreeNode.Create(root));
this.WhenAnyValue(x => x.SelectedTab).Subscribe(index => UpdateFocusedControl();
KeyboardDevice.Instance.PropertyChanged += (s, e) =>
{ {
switch (index) if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
{
UpdateFocusedControl();
}
};
root.GetObservable(TopLevel.PointerOverElementProperty)
.Subscribe(x => PointerOverElement = x?.GetType().Name);
}
public ViewModelBase Content
{
get { return _content; }
private set { RaiseAndSetIfChanged(ref _content, value); }
}
public int SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
switch (value)
{ {
case 0: case 0:
Content = _logicalTree; Content = _logicalTree;
@ -39,34 +57,23 @@ namespace Avalonia.Diagnostics.ViewModels
Content = _visualTree; Content = _visualTree;
break; break;
} }
});
_focusedControl = KeyboardDevice.Instance RaisePropertyChanged();
.WhenAnyValue(x => x.FocusedElement) }
.Select(x => x?.GetType().Name)
.ToProperty(this, x => x.FocusedControl);
_pointerOverElement = root.GetObservable(TopLevel.PointerOverElementProperty)
.Select(x => x?.GetType().Name)
.ToProperty(this, x => x.PointerOverElement);
} }
public ReactiveObject Content public string FocusedControl
{ {
get { return _content; } get { return _focusedControl; }
private set { this.RaiseAndSetIfChanged(ref _content, value); } private set { RaiseAndSetIfChanged(ref _focusedControl, value); }
} }
public int SelectedTab public string PointerOverElement
{ {
get { return _selectedTab; } get { return _pointerOverElement; }
set { this.RaiseAndSetIfChanged(ref _selectedTab, value); } private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); }
} }
public string FocusedControl => _focusedControl.Value;
public string PointerOverElement => _pointerOverElement.Value;
public void SelectControl(IControl control) public void SelectControl(IControl control)
{ {
var tree = Content as TreePageViewModel; var tree = Content as TreePageViewModel;
@ -76,5 +83,10 @@ namespace Avalonia.Diagnostics.ViewModels
tree.SelectControl(control); tree.SelectControl(control);
} }
} }
private void UpdateFocusedControl()
{
_focusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
}
} }
} }

4
src/Avalonia.Diagnostics/ViewModels/LogicalTreeNode.cs

@ -2,9 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
@ -13,7 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels
public LogicalTreeNode(ILogical logical, TreeNode parent) public LogicalTreeNode(ILogical logical, TreeNode parent)
: base((Control)logical, parent) : base((Control)logical, parent)
{ {
Children = logical.LogicalChildren.CreateDerivedCollection(x => new LogicalTreeNode(x, this)); Children = logical.LogicalChildren.CreateDerivedList(x => new LogicalTreeNode(x, this));
} }
public static LogicalTreeNode[] Create(object control) public static LogicalTreeNode[] Create(object control)

11
src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs

@ -3,16 +3,13 @@
using System; using System;
using Avalonia.Data; using Avalonia.Data;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class PropertyDetails : ReactiveObject internal class PropertyDetails : ViewModelBase
{ {
private object _value; private object _value;
private string _priority; private string _priority;
private string _diagnostic; private string _diagnostic;
public PropertyDetails(AvaloniaObject o, AvaloniaProperty property) public PropertyDetails(AvaloniaObject o, AvaloniaProperty property)
@ -41,19 +38,19 @@ namespace Avalonia.Diagnostics.ViewModels
public string Priority public string Priority
{ {
get { return _priority; } get { return _priority; }
private set { this.RaiseAndSetIfChanged(ref _priority, value); } private set { RaiseAndSetIfChanged(ref _priority, value); }
} }
public string Diagnostic public string Diagnostic
{ {
get { return _diagnostic; } get { return _diagnostic; }
private set { this.RaiseAndSetIfChanged(ref _diagnostic, value); } private set { RaiseAndSetIfChanged(ref _diagnostic, value); }
} }
public object Value public object Value
{ {
get { return _value; } get { return _value; }
private set { this.RaiseAndSetIfChanged(ref _value, value); } private set { RaiseAndSetIfChanged(ref _value, value); }
} }
} }
} }

10
src/Avalonia.Diagnostics/ViewModels/TreeNode.cs

@ -5,13 +5,13 @@ using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class TreeNode : ReactiveObject internal class TreeNode : ViewModelBase
{ {
private string _classes; private string _classes;
private bool _isExpanded; private bool _isExpanded;
@ -47,7 +47,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public IReadOnlyReactiveList<TreeNode> Children public IAvaloniaReadOnlyList<TreeNode> Children
{ {
get; get;
protected set; protected set;
@ -56,7 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels
public string Classes public string Classes
{ {
get { return _classes; } get { return _classes; }
private set { this.RaiseAndSetIfChanged(ref _classes, value); } private set { RaiseAndSetIfChanged(ref _classes, value); }
} }
public IVisual Visual public IVisual Visual
@ -67,7 +67,7 @@ namespace Avalonia.Diagnostics.ViewModels
public bool IsExpanded public bool IsExpanded
{ {
get { return _isExpanded; } get { return _isExpanded; }
set { this.RaiseAndSetIfChanged(ref _isExpanded, value); } set { RaiseAndSetIfChanged(ref _isExpanded, value); }
} }
public TreeNode Parent public TreeNode Parent

24
src/Avalonia.Diagnostics/ViewModels/TreePageViewModel.cs

@ -1,25 +1,19 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reactive.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class TreePageViewModel : ReactiveObject internal class TreePageViewModel : ViewModelBase
{ {
private TreeNode _selected; private TreeNode _selected;
private ControlDetailsViewModel _details;
private readonly ObservableAsPropertyHelper<ControlDetailsViewModel> _details;
public TreePageViewModel(TreeNode[] nodes) public TreePageViewModel(TreeNode[] nodes)
{ {
Nodes = nodes; Nodes = nodes;
_details = this.WhenAnyValue(x => x.SelectedNode)
.Select(x => x != null ? new ControlDetailsViewModel(x.Visual) : null)
.ToProperty(this, x => x.Details);
} }
public TreeNode[] Nodes { get; protected set; } public TreeNode[] Nodes { get; protected set; }
@ -27,10 +21,20 @@ namespace Avalonia.Diagnostics.ViewModels
public TreeNode SelectedNode public TreeNode SelectedNode
{ {
get { return _selected; } get { return _selected; }
set { this.RaiseAndSetIfChanged(ref _selected, value); } set
{
if (RaiseAndSetIfChanged(ref _selected, value))
{
Details = value != null ? new ControlDetailsViewModel(value.Visual) : null;
}
}
} }
public ControlDetailsViewModel Details => _details.Value; public ControlDetailsViewModel Details
{
get { return _details; }
private set { RaiseAndSetIfChanged(ref _details, value); }
}
public TreeNode FindNode(IControl control) public TreeNode FindNode(IControl control)
{ {

32
src/Avalonia.Diagnostics/ViewModels/ViewModelBase.cs

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace Avalonia.Diagnostics.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
[NotifyPropertyChangedInvocator]
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

7
src/Avalonia.Diagnostics/ViewModels/VisualTreeNode.cs

@ -1,10 +1,9 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls; using Avalonia.Collections;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
@ -17,11 +16,11 @@ namespace Avalonia.Diagnostics.ViewModels
if (host?.Root == null) if (host?.Root == null)
{ {
Children = visual.VisualChildren.CreateDerivedCollection(x => new VisualTreeNode(x, this)); Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this));
} }
else else
{ {
Children = new ReactiveList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) }); Children = new AvaloniaList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) });
} }
if ((Visual is IStyleable styleable)) if ((Visual is IStyleable styleable))

20
src/Avalonia.Diagnostics/Views/ControlDetailsView.cs

@ -8,7 +8,6 @@ using Avalonia.Controls;
using Avalonia.Diagnostics.ViewModels; using Avalonia.Diagnostics.ViewModels;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using ReactiveUI;
namespace Avalonia.Diagnostics.Views namespace Avalonia.Diagnostics.Views
{ {
@ -16,6 +15,7 @@ namespace Avalonia.Diagnostics.Views
{ {
private static readonly StyledProperty<ControlDetailsViewModel> ViewModelProperty = private static readonly StyledProperty<ControlDetailsViewModel> ViewModelProperty =
AvaloniaProperty.Register<ControlDetailsView, ControlDetailsViewModel>("ViewModel"); AvaloniaProperty.Register<ControlDetailsView, ControlDetailsViewModel>("ViewModel");
private SimpleGrid _grid;
public ControlDetailsView() public ControlDetailsView()
{ {
@ -27,7 +27,11 @@ namespace Avalonia.Diagnostics.Views
public ControlDetailsViewModel ViewModel public ControlDetailsViewModel ViewModel
{ {
get { return GetValue(ViewModelProperty); } get { return GetValue(ViewModelProperty); }
private set { SetValue(ViewModelProperty, value); } private set
{
SetValue(ViewModelProperty, value);
_grid[GridRepeater.ItemsProperty] = value?.Properties;
}
} }
private void InitializeComponent() private void InitializeComponent()
@ -36,7 +40,7 @@ namespace Avalonia.Diagnostics.Views
Content = new ScrollViewer Content = new ScrollViewer
{ {
Content = new SimpleGrid Content = _grid = new SimpleGrid
{ {
Styles = new Styles Styles = new Styles
{ {
@ -49,7 +53,6 @@ namespace Avalonia.Diagnostics.Views
}, },
}, },
[GridRepeater.TemplateProperty] = pt, [GridRepeater.TemplateProperty] = pt,
[!GridRepeater.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties).ToBinding(),
} }
}; };
} }
@ -62,16 +65,13 @@ namespace Avalonia.Diagnostics.Views
{ {
Text = property.Name, Text = property.Name,
TextWrapping = TextWrapping.NoWrap, TextWrapping = TextWrapping.NoWrap,
[!ToolTip.TipProperty] = property [!ToolTip.TipProperty] = property.GetObservable<string>(nameof(property.Diagnostic)).ToBinding(),
.WhenAnyValue(x => x.Diagnostic)
.ToBinding(),
}; };
yield return new TextBlock yield return new TextBlock
{ {
TextWrapping = TextWrapping.NoWrap, TextWrapping = TextWrapping.NoWrap,
[!TextBlock.TextProperty] = property [!TextBlock.TextProperty] = property.GetObservable<object>(nameof(property.Value))
.WhenAnyValue(v => v.Value)
.Select(v => v?.ToString()) .Select(v => v?.ToString())
.ToBinding(), .ToBinding(),
}; };
@ -79,7 +79,7 @@ namespace Avalonia.Diagnostics.Views
yield return new TextBlock yield return new TextBlock
{ {
TextWrapping = TextWrapping.NoWrap, TextWrapping = TextWrapping.NoWrap,
[!TextBlock.TextProperty] = property.WhenAnyValue(x => x.Priority).ToBinding(), [!TextBlock.TextProperty] = property.GetObservable<string>((nameof(property.Priority))).ToBinding(),
}; };
} }
} }

30
src/Avalonia.Diagnostics/Views/PropertyChangedExtenions.cs

@ -0,0 +1,30 @@
using System;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Reflection;
namespace Avalonia.Diagnostics.Views
{
internal static class PropertyChangedExtenions
{
public static IObservable<T> GetObservable<T>(this INotifyPropertyChanged source, string propertyName)
{
Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(propertyName != null);
var property = source.GetType().GetTypeInfo().GetDeclaredProperty(propertyName);
if (property == null)
{
throw new ArgumentException($"Property '{propertyName}' not found on '{source}.");
}
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
e => source.PropertyChanged += e,
e => source.PropertyChanged -= e)
.Where(e => e.EventArgs.PropertyName == propertyName)
.Select(_ => (T)property.GetValue(source))
.StartWith((T)property.GetValue(source));
}
}
}

5
src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs

@ -10,9 +10,11 @@
// - Sun Tsu, // - Sun Tsu,
// "The Art of War" // "The Art of War"
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Html; using Avalonia.Controls.Html;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.VisualTree;
using TheArtOfDev.HtmlRenderer.Adapters; using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities; using TheArtOfDev.HtmlRenderer.Adapters.Entities;
using TheArtOfDev.HtmlRenderer.Core.Utils; using TheArtOfDev.HtmlRenderer.Core.Utils;
@ -54,7 +56,8 @@ namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{ {
get get
{ {
return Util.Convert(MouseDevice.Instance.GetPosition(_control)); var pos = (_control.GetVisualRoot() as IInputRoot)?.MouseDevice?.Position ?? default(Point);
return Util.Convert(pos);
} }
} }

3
src/Avalonia.HtmlRenderer/HtmlControl.cs

@ -17,6 +17,7 @@ using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
using TheArtOfDev.HtmlRenderer.Core; using TheArtOfDev.HtmlRenderer.Core;
using TheArtOfDev.HtmlRenderer.Core.Entities; using TheArtOfDev.HtmlRenderer.Core.Entities;
using TheArtOfDev.HtmlRenderer.Avalonia; using TheArtOfDev.HtmlRenderer.Avalonia;
@ -512,7 +513,7 @@ namespace Avalonia.Controls.Html
protected virtual void InvokeMouseMove() protected virtual void InvokeMouseMove()
{ {
_htmlContainer.HandleMouseMove(this, MouseDevice.Instance?.GetPosition(this) ?? default(Point)); _htmlContainer.HandleMouseMove(this, (this.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(this) ?? default(Point));
} }
/// <summary> /// <summary>

7
src/Avalonia.Input/IInputDevice.cs

@ -1,9 +1,16 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Input.Raw;
namespace Avalonia.Input namespace Avalonia.Input
{ {
public interface IInputDevice public interface IInputDevice
{ {
/// <summary>
/// Processes raw event. Is called after preprocessing by InputManager
/// </summary>
/// <param name="ev"></param>
void ProcessRawEvent(RawInputEventArgs ev);
} }
} }

8
src/Avalonia.Input/IInputRoot.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using JetBrains.Annotations;
namespace Avalonia.Input namespace Avalonia.Input
{ {
/// <summary> /// <summary>
@ -27,5 +29,11 @@ namespace Avalonia.Input
/// Gets or sets a value indicating whether access keys are shown in the window. /// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary> /// </summary>
bool ShowAccessKeys { get; set; } bool ShowAccessKeys { get; set; }
/// <summary>
/// Gets associated mouse device
/// </summary>
[CanBeNull]
IMouseDevice MouseDevice { get; }
} }
} }

3
src/Avalonia.Input/IKeyboardDevice.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.ComponentModel;
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -26,7 +27,7 @@ namespace Avalonia.Input
Toggled = 2, Toggled = 2,
} }
public interface IKeyboardDevice : IInputDevice public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged
{ {
IInputElement FocusedElement { get; } IInputElement FocusedElement { get; }

1
src/Avalonia.Input/InputManager.cs

@ -35,6 +35,7 @@ namespace Avalonia.Input
public void ProcessInput(RawInputEventArgs e) public void ProcessInput(RawInputEventArgs e)
{ {
_preProcess.OnNext(e); _preProcess.OnNext(e);
e.Device?.ProcessRawEvent(e);
_process.OnNext(e); _process.OnNext(e);
_postProcess.OnNext(e); _postProcess.OnNext(e);
} }

12
src/Avalonia.Input/KeyboardDevice.cs

@ -16,14 +16,6 @@ namespace Avalonia.Input
{ {
private IInputElement _focusedElement; private IInputElement _focusedElement;
public KeyboardDevice()
{
InputManager.Process
.OfType<RawInputEventArgs>()
.Where(e => e.Device == this && !e.Handled)
.Subscribe(ProcessRawEvent);
}
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>(); public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
@ -77,8 +69,10 @@ namespace Avalonia.Input
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} }
private void ProcessRawEvent(RawInputEventArgs e) public void ProcessRawEvent(RawInputEventArgs e)
{ {
if(e.Handled)
return;
IInputElement element = FocusedElement; IInputElement element = FocusedElement;
if (element != null) if (element != null)

31
src/Avalonia.Input/MouseDevice.cs

@ -20,23 +20,7 @@ namespace Avalonia.Input
private int _clickCount; private int _clickCount;
private Rect _lastClickRect; private Rect _lastClickRect;
private uint _lastClickTime; private uint _lastClickTime;
/// <summary>
/// Intializes a new instance of <see cref="MouseDevice"/>.
/// </summary>
public MouseDevice()
{
InputManager.Process
.OfType<RawMouseEventArgs>()
.Where(e => e.Device == this && !e.Handled)
.Subscribe(ProcessRawEvent);
}
/// <summary>
/// Gets the current mouse device instance.
/// </summary>
public static IMouseDevice Instance => AvaloniaLocator.Current.GetService<IMouseDevice>();
/// <summary> /// <summary>
/// Gets the control that is currently capturing by the mouse, if any. /// Gets the control that is currently capturing by the mouse, if any.
/// </summary> /// </summary>
@ -50,12 +34,7 @@ namespace Avalonia.Input
get; get;
protected set; protected set;
} }
/// <summary>
/// Gets the application's input manager.
/// </summary>
public IInputManager InputManager => AvaloniaLocator.Current.GetService<IInputManager>();
/// <summary> /// <summary>
/// Gets the mouse position, in screen coordinates. /// Gets the mouse position, in screen coordinates.
/// </summary> /// </summary>
@ -102,6 +81,12 @@ namespace Avalonia.Input
return root.PointToClient(Position) - p; return root.PointToClient(Position) - p;
} }
public void ProcessRawEvent(RawInputEventArgs e)
{
if (!e.Handled && e is RawMouseEventArgs margs)
ProcessRawEvent(margs);
}
private void ProcessRawEvent(RawMouseEventArgs e) private void ProcessRawEvent(RawMouseEventArgs e)
{ {
Contract.Requires<ArgumentNullException>(e != null); Contract.Requires<ArgumentNullException>(e != null);

46
src/Avalonia.Input/Navigation/DirectionalNavigation.cs

@ -44,7 +44,7 @@ namespace Avalonia.Input.Navigation
GetFirstInNextContainer(element, direction); GetFirstInNextContainer(element, direction);
case KeyboardNavigationMode.Cycle: case KeyboardNavigationMode.Cycle:
return GetNextInContainer(element, container, direction) ?? return GetNextInContainer(element, container, direction) ??
GetFocusableDescendent(container, direction); GetFocusableDescendant(container, direction);
case KeyboardNavigationMode.Contained: case KeyboardNavigationMode.Contained:
return GetNextInContainer(element, container, direction); return GetNextInContainer(element, container, direction);
default: default:
@ -53,7 +53,7 @@ namespace Avalonia.Input.Navigation
} }
else else
{ {
return GetFocusableDescendents(element).FirstOrDefault(); return GetFocusableDescendants(element).FirstOrDefault();
} }
} }
@ -71,24 +71,24 @@ namespace Avalonia.Input.Navigation
} }
/// <summary> /// <summary>
/// Gets the first or last focusable descendent of the specified element. /// Gets the first or last focusable descendant of the specified element.
/// </summary> /// </summary>
/// <param name="container">The element.</param> /// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param> /// <param name="direction">The direction to search.</param>
/// <returns>The element or null if not found.##</returns> /// <returns>The element or null if not found.##</returns>
private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction) private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
{ {
return IsForward(direction) ? return IsForward(direction) ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendants(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault(); GetFocusableDescendants(container).LastOrDefault();
} }
/// <summary> /// <summary>
/// Gets the focusable descendents of the specified element. /// Gets the focusable descendants of the specified element.
/// </summary> /// </summary>
/// <param name="element">The element.</param> /// <param name="element">The element.</param>
/// <returns>The element's focusable descendents.</returns> /// <returns>The element's focusable descendants.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendents(IInputElement element) private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element)
{ {
var children = element.GetVisualChildren().OfType<IInputElement>(); var children = element.GetVisualChildren().OfType<IInputElement>();
@ -99,11 +99,11 @@ namespace Avalonia.Input.Navigation
yield return child; yield return child;
} }
if (child.CanFocusDescendents()) if (child.CanFocusDescendants())
{ {
foreach (var descendent in GetFocusableDescendents(child)) foreach (var descendant in GetFocusableDescendants(child))
{ {
yield return descendent; yield return descendant;
} }
} }
} }
@ -123,11 +123,11 @@ namespace Avalonia.Input.Navigation
{ {
if (direction == NavigationDirection.Down) if (direction == NavigationDirection.Down)
{ {
var descendent = GetFocusableDescendents(element).FirstOrDefault(); var descendant = GetFocusableDescendants(element).FirstOrDefault();
if (descendent != null) if (descendant != null)
{ {
return descendent; return descendant;
} }
} }
@ -156,11 +156,11 @@ namespace Avalonia.Input.Navigation
if (element != null && direction == NavigationDirection.Up) if (element != null && direction == NavigationDirection.Up)
{ {
var descendent = GetFocusableDescendents(element).LastOrDefault(); var descendant = GetFocusableDescendants(element).LastOrDefault();
if (descendent != null) if (descendant != null)
{ {
return descendent; return descendant;
} }
} }
@ -193,7 +193,7 @@ namespace Avalonia.Input.Navigation
var siblings = parent.GetVisualChildren() var siblings = parent.GetVisualChildren()
.OfType<IInputElement>() .OfType<IInputElement>()
.Where(FocusExtensions.CanFocusDescendents); .Where(FocusExtensions.CanFocusDescendants);
var sibling = isForward ? var sibling = isForward ?
siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() :
siblings.TakeWhile(x => x != container).LastOrDefault(); siblings.TakeWhile(x => x != container).LastOrDefault();
@ -207,8 +207,8 @@ namespace Avalonia.Input.Navigation
else else
{ {
next = isForward ? next = isForward ?
GetFocusableDescendents(sibling).FirstOrDefault() : GetFocusableDescendants(sibling).FirstOrDefault() :
GetFocusableDescendents(sibling).LastOrDefault(); GetFocusableDescendants(sibling).LastOrDefault();
} }
} }
@ -220,8 +220,8 @@ namespace Avalonia.Input.Navigation
else else
{ {
next = isForward ? next = isForward ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendants(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault(); GetFocusableDescendants(container).LastOrDefault();
} }
return next; return next;

6
src/Avalonia.Input/Navigation/FocusExtensions.cs

@ -16,10 +16,10 @@ namespace Avalonia.Input.Navigation
public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible; public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible;
/// <summary> /// <summary>
/// Checks if descendents of the specified element can be focused. /// Checks if descendants of the specified element can be focused.
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if descendents of the element can be focused.</returns> /// <returns>True if descendants of the element can be focused.</returns>
public static bool CanFocusDescendents(this IInputElement e) => e.IsEnabledCore && e.IsVisible; public static bool CanFocusDescendants(this IInputElement e) => e.IsEnabledCore && e.IsVisible;
} }
} }

46
src/Avalonia.Input/Navigation/TabNavigation.cs

@ -44,7 +44,7 @@ namespace Avalonia.Input.Navigation
GetFirstInNextContainer(element, direction); GetFirstInNextContainer(element, direction);
case KeyboardNavigationMode.Cycle: case KeyboardNavigationMode.Cycle:
return GetNextInContainer(element, container, direction) ?? return GetNextInContainer(element, container, direction) ??
GetFocusableDescendent(container, direction); GetFocusableDescendant(container, direction);
case KeyboardNavigationMode.Contained: case KeyboardNavigationMode.Contained:
return GetNextInContainer(element, container, direction); return GetNextInContainer(element, container, direction);
default: default:
@ -53,29 +53,29 @@ namespace Avalonia.Input.Navigation
} }
else else
{ {
return GetFocusableDescendents(element).FirstOrDefault(); return GetFocusableDescendants(element).FirstOrDefault();
} }
} }
/// <summary> /// <summary>
/// Gets the first or last focusable descendent of the specified element. /// Gets the first or last focusable descendant of the specified element.
/// </summary> /// </summary>
/// <param name="container">The element.</param> /// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param> /// <param name="direction">The direction to search.</param>
/// <returns>The element or null if not found.##</returns> /// <returns>The element or null if not found.##</returns>
private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction) private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
{ {
return direction == NavigationDirection.Next ? return direction == NavigationDirection.Next ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendants(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault(); GetFocusableDescendants(container).LastOrDefault();
} }
/// <summary> /// <summary>
/// Gets the focusable descendents of the specified element. /// Gets the focusable descendants of the specified element.
/// </summary> /// </summary>
/// <param name="element">The element.</param> /// <param name="element">The element.</param>
/// <returns>The element's focusable descendents.</returns> /// <returns>The element's focusable descendants.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendents(IInputElement element) private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element)
{ {
var mode = KeyboardNavigation.GetTabNavigation((InputElement)element); var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
@ -108,11 +108,11 @@ namespace Avalonia.Input.Navigation
yield return child; yield return child;
} }
if (child.CanFocusDescendents()) if (child.CanFocusDescendants())
{ {
foreach (var descendent in GetFocusableDescendents(child)) foreach (var descendant in GetFocusableDescendants(child))
{ {
yield return descendent; yield return descendant;
} }
} }
} }
@ -132,11 +132,11 @@ namespace Avalonia.Input.Navigation
{ {
if (direction == NavigationDirection.Next) if (direction == NavigationDirection.Next)
{ {
var descendent = GetFocusableDescendents(element).FirstOrDefault(); var descendant = GetFocusableDescendants(element).FirstOrDefault();
if (descendent != null) if (descendant != null)
{ {
return descendent; return descendant;
} }
} }
@ -167,11 +167,11 @@ namespace Avalonia.Input.Navigation
if (element != null && direction == NavigationDirection.Previous) if (element != null && direction == NavigationDirection.Previous)
{ {
var descendent = GetFocusableDescendents(element).LastOrDefault(); var descendant = GetFocusableDescendants(element).LastOrDefault();
if (descendent != null) if (descendant != null)
{ {
return descendent; return descendant;
} }
} }
@ -203,7 +203,7 @@ namespace Avalonia.Input.Navigation
var siblings = parent.GetVisualChildren() var siblings = parent.GetVisualChildren()
.OfType<IInputElement>() .OfType<IInputElement>()
.Where(FocusExtensions.CanFocusDescendents); .Where(FocusExtensions.CanFocusDescendants);
var sibling = direction == NavigationDirection.Next ? var sibling = direction == NavigationDirection.Next ?
siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() :
siblings.TakeWhile(x => x != container).LastOrDefault(); siblings.TakeWhile(x => x != container).LastOrDefault();
@ -217,8 +217,8 @@ namespace Avalonia.Input.Navigation
else else
{ {
next = direction == NavigationDirection.Next ? next = direction == NavigationDirection.Next ?
GetFocusableDescendents(sibling).FirstOrDefault() : GetFocusableDescendants(sibling).FirstOrDefault() :
GetFocusableDescendents(sibling).LastOrDefault(); GetFocusableDescendants(sibling).LastOrDefault();
} }
} }
@ -230,8 +230,8 @@ namespace Avalonia.Input.Navigation
else else
{ {
next = direction == NavigationDirection.Next ? next = direction == NavigationDirection.Next ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendants(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault(); GetFocusableDescendants(container).LastOrDefault();
} }
return next; return next;

97
src/Avalonia.Layout/LayoutManager.cs

@ -14,8 +14,8 @@ namespace Avalonia.Layout
/// </summary> /// </summary>
public class LayoutManager : ILayoutManager public class LayoutManager : ILayoutManager
{ {
private readonly HashSet<ILayoutable> _toMeasure = new HashSet<ILayoutable>(); private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>();
private readonly HashSet<ILayoutable> _toArrange = new HashSet<ILayoutable>(); private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
private bool _queued; private bool _queued;
private bool _running; private bool _running;
@ -30,8 +30,18 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null); Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
_toMeasure.Add(control); if (!control.IsAttachedToVisualTree)
_toArrange.Add(control); {
#if DEBUG
throw new AvaloniaInternalException(
"LayoutManager.InvalidateMeasure called on a control that is detached from the visual tree.");
#else
return;
#endif
}
_toMeasure.Enqueue(control);
_toArrange.Enqueue(control);
QueueLayoutPass(); QueueLayoutPass();
} }
@ -41,7 +51,17 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null); Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
_toArrange.Add(control); if (!control.IsAttachedToVisualTree)
{
#if DEBUG
throw new AvaloniaInternalException(
"LayoutManager.InvalidateArrange called on a control that is detached from the visual tree.");
#else
return;
#endif
}
_toArrange.Enqueue(control);
QueueLayoutPass(); QueueLayoutPass();
} }
@ -108,8 +128,12 @@ namespace Avalonia.Layout
{ {
while (_toMeasure.Count > 0) while (_toMeasure.Count > 0)
{ {
var next = _toMeasure.First(); var control = _toMeasure.Dequeue();
Measure(next);
if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
{
Measure(control);
}
} }
} }
@ -117,53 +141,60 @@ namespace Avalonia.Layout
{ {
while (_toArrange.Count > 0 && _toMeasure.Count == 0) while (_toArrange.Count > 0 && _toMeasure.Count == 0)
{ {
var next = _toArrange.First(); var control = _toArrange.Dequeue();
Arrange(next);
if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
{
Arrange(control);
}
} }
} }
private void Measure(ILayoutable control) private void Measure(ILayoutable control)
{ {
var root = control as ILayoutRoot; // Controls closest to the visual root need to be arranged first. We don't try to store
var parent = control.VisualParent as ILayoutable; // ordered invalidation lists, instead we traverse the tree upwards, measuring the
// controls closest to the root first. This has been shown by benchmarks to be the
if (root != null) // fastest and most memory-efficent algorithm.
{ if (control.VisualParent is ILayoutable parent)
root.Measure(root.MaxClientSize);
}
else if (parent != null)
{ {
Measure(parent); Measure(parent);
} }
if (!control.IsMeasureValid) // If the control being measured has IsMeasureValid == true here then its measure was
// handed by an ancestor and can be ignored. The measure may have also caused the
// control to be removed.
if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
{ {
control.Measure(control.PreviousMeasure.Value); if (control is ILayoutRoot root)
{
root.Measure(Size.Infinity);
}
else
{
control.Measure(control.PreviousMeasure.Value);
}
} }
_toMeasure.Remove(control);
} }
private void Arrange(ILayoutable control) private void Arrange(ILayoutable control)
{ {
var root = control as ILayoutRoot; if (control.VisualParent is ILayoutable parent)
var parent = control.VisualParent as ILayoutable;
if (root != null)
{
root.Arrange(new Rect(root.DesiredSize));
}
else if (parent != null)
{ {
Arrange(parent); Arrange(parent);
} }
if (control.PreviousArrange.HasValue) if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
{ {
control.Arrange(control.PreviousArrange.Value); if (control is ILayoutRoot root)
{
root.Arrange(new Rect(control.DesiredSize));
}
else
{
control.Arrange(control.PreviousArrange.Value);
}
} }
_toArrange.Remove(control);
} }
private void QueueLayoutPass() private void QueueLayoutPass()

18
src/Avalonia.Layout/Layoutable.cs

@ -378,8 +378,12 @@ namespace Avalonia.Layout
IsMeasureValid = false; IsMeasureValid = false;
IsArrangeValid = false; IsArrangeValid = false;
LayoutManager.Instance?.InvalidateMeasure(this);
InvalidateVisual(); if (((ILayoutable)this).IsAttachedToVisualTree)
{
LayoutManager.Instance?.InvalidateMeasure(this);
InvalidateVisual();
}
} }
} }
@ -393,8 +397,12 @@ namespace Avalonia.Layout
Logger.Verbose(LogArea.Layout, this, "Invalidated arrange"); Logger.Verbose(LogArea.Layout, this, "Invalidated arrange");
IsArrangeValid = false; IsArrangeValid = false;
LayoutManager.Instance?.InvalidateArrange(this);
InvalidateVisual(); if (((ILayoutable)this).IsAttachedToVisualTree)
{
LayoutManager.Instance?.InvalidateArrange(this);
InvalidateVisual();
}
} }
} }
@ -612,7 +620,7 @@ namespace Avalonia.Layout
/// <inheritdoc/> /// <inheritdoc/>
protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent) protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{ {
foreach (ILayoutable i in this.GetSelfAndVisualDescendents()) foreach (ILayoutable i in this.GetSelfAndVisualDescendants())
{ {
i.InvalidateMeasure(); i.InvalidateMeasure();
} }

10
src/Avalonia.Styling/LogicalTree/ILogical.cs

@ -36,6 +36,16 @@ namespace Avalonia.LogicalTree
/// </summary> /// </summary>
IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; } IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; }
/// <summary>
/// Notifies the control that it is being attached to a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
/// </remarks>
void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e);
/// <summary> /// <summary>
/// Notifies the control that it is being detached from a rooted logical tree. /// Notifies the control that it is being detached from a rooted logical tree.
/// </summary> /// </summary>

6
src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs

@ -37,15 +37,15 @@ namespace Avalonia.LogicalTree
return logical.LogicalChildren; return logical.LogicalChildren;
} }
public static IEnumerable<ILogical> GetLogicalDescendents(this ILogical logical) public static IEnumerable<ILogical> GetLogicalDescendants(this ILogical logical)
{ {
foreach (ILogical child in logical.LogicalChildren) foreach (ILogical child in logical.LogicalChildren)
{ {
yield return child; yield return child;
foreach (ILogical descendent in child.GetLogicalDescendents()) foreach (ILogical descendant in child.GetLogicalDescendants())
{ {
yield return descendent; yield return descendant;
} }
} }
} }

14
src/Avalonia.Styling/Styling/DescendentSelector.cs

@ -7,16 +7,16 @@ using Avalonia.LogicalTree;
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
internal class DescendentSelector : Selector internal class DescendantSelector : Selector
{ {
private readonly Selector _parent; private readonly Selector _parent;
private string _selectorString; private string _selectorString;
public DescendentSelector(Selector parent) public DescendantSelector(Selector parent)
{ {
if (parent == null) if (parent == null)
{ {
throw new InvalidOperationException("Descendent selector must be preceeded by a selector."); throw new InvalidOperationException("Descendant selector must be preceeded by a selector.");
} }
_parent = parent; _parent = parent;
@ -41,7 +41,7 @@ namespace Avalonia.Styling
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
{ {
ILogical c = (ILogical)control; ILogical c = (ILogical)control;
List<IObservable<bool>> descendentMatches = new List<IObservable<bool>>(); List<IObservable<bool>> descendantMatches = new List<IObservable<bool>>();
while (c != null) while (c != null)
{ {
@ -60,14 +60,14 @@ namespace Avalonia.Styling
} }
else else
{ {
descendentMatches.Add(match.ObservableResult); descendantMatches.Add(match.ObservableResult);
} }
} }
} }
if (descendentMatches.Count > 0) if (descendantMatches.Count > 0)
{ {
return new SelectorMatch(StyleActivator.Or(descendentMatches)); return new SelectorMatch(StyleActivator.Or(descendantMatches));
} }
else else
{ {

6
src/Avalonia.Styling/Styling/Selectors.cs

@ -42,13 +42,13 @@ namespace Avalonia.Styling
} }
/// <summary> /// <summary>
/// Returns a selector which matches a descendent of a previous selector. /// Returns a selector which matches a descendant of a previous selector.
/// </summary> /// </summary>
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector Descendent(this Selector previous) public static Selector Descendant(this Selector previous)
{ {
return new DescendentSelector(previous); return new DescendantSelector(previous);
} }
/// <summary> /// <summary>

2
src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs

@ -27,7 +27,7 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// DPI of underling screen /// DPI of underling screen
/// </summary> /// </summary>
Size Dpi { get; } Vector Dpi { get; }
/// <summary> /// <summary>
/// Pixel format /// Pixel format

2
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -164,7 +164,7 @@ namespace Avalonia.Rendering
private static void ClearTransformedBounds(IVisual visual) private static void ClearTransformedBounds(IVisual visual)
{ {
foreach (var e in visual.GetSelfAndVisualDescendents()) foreach (var e in visual.GetSelfAndVisualDescendants())
{ {
BoundsTracker.SetTransformedBounds((Visual)visual, null); BoundsTracker.SetTransformedBounds((Visual)visual, null);
} }

13
src/Avalonia.Visuals/Vector.cs

@ -52,8 +52,6 @@ namespace Avalonia
return new Point(a._x, a._y); return new Point(a._x, a._y);
} }
/// <summary> /// <summary>
/// Calculates the dot product of two vectors /// Calculates the dot product of two vectors
/// </summary> /// </summary>
@ -65,6 +63,17 @@ namespace Avalonia
return a.X*b.X + a.Y*b.Y; return a.X*b.X + a.Y*b.Y;
} }
/// <summary>
/// Scales a vector.
/// </summary>
/// <param name="vector">The vector</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The scaled vector.</returns>
public static Vector operator *(Vector vector, double scale)
{
return new Vector(vector._x * scale, vector._y * scale);
}
/// <summary> /// <summary>
/// Length of the vector /// Length of the vector
/// </summary> /// </summary>

6
src/Avalonia.Visuals/Visual.cs

@ -314,7 +314,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method /// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendents. /// for this control and all of its visual descendants.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
@ -342,7 +342,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method /// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendents. /// for this control and all of its visual descendants.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
@ -422,7 +422,7 @@ namespace Avalonia
if (visual == null) if (visual == null)
{ {
throw new ArgumentException("'visual' is not a descendent of 'ancestor'."); throw new ArgumentException("'visual' is not a descendant of 'ancestor'.");
} }
} }

16
src/Avalonia.Visuals/VisualTree/VisualExtensions.cs

@ -123,33 +123,33 @@ namespace Avalonia.VisualTree
} }
/// <summary> /// <summary>
/// Enumerates the descendents of an <see cref="IVisual"/> in the visual tree. /// Enumerates the descendants of an <see cref="IVisual"/> in the visual tree.
/// </summary> /// </summary>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <returns>The visual's ancestors.</returns> /// <returns>The visual's ancestors.</returns>
public static IEnumerable<IVisual> GetVisualDescendents(this IVisual visual) public static IEnumerable<IVisual> GetVisualDescendants(this IVisual visual)
{ {
foreach (IVisual child in visual.VisualChildren) foreach (IVisual child in visual.VisualChildren)
{ {
yield return child; yield return child;
foreach (IVisual descendent in child.GetVisualDescendents()) foreach (IVisual descendant in child.GetVisualDescendants())
{ {
yield return descendent; yield return descendant;
} }
} }
} }
/// <summary> /// <summary>
/// Enumerates an <see cref="IVisual"/> and its descendents in the visual tree. /// Enumerates an <see cref="IVisual"/> and its descendants in the visual tree.
/// </summary> /// </summary>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <returns>The visual and its ancestors.</returns> /// <returns>The visual and its ancestors.</returns>
public static IEnumerable<IVisual> GetSelfAndVisualDescendents(this IVisual visual) public static IEnumerable<IVisual> GetSelfAndVisualDescendants(this IVisual visual)
{ {
yield return visual; yield return visual;
foreach (var ancestor in visual.GetVisualDescendents()) foreach (var ancestor in visual.GetVisualDescendants())
{ {
yield return ancestor; yield return ancestor;
} }
@ -196,7 +196,7 @@ namespace Avalonia.VisualTree
/// Tests whether an <see cref="IVisual"/> is an ancestor of another visual. /// Tests whether an <see cref="IVisual"/> is an ancestor of another visual.
/// </summary> /// </summary>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <param name="target">The potential descendent.</param> /// <param name="target">The potential descendant.</param>
/// <returns> /// <returns>
/// True if <paramref name="visual"/> is an ancestor of <paramref name="target"/>; /// True if <paramref name="visual"/> is an ancestor of <paramref name="target"/>;
/// otherwise false. /// otherwise false.

1
src/Gtk/Avalonia.Gtk/GtkPlatform.cs

@ -51,7 +51,6 @@ namespace Avalonia.Gtk
.Bind<IClipboard>().ToSingleton<ClipboardImpl>() .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance) .Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance)
.Bind<IKeyboardDevice>().ToConstant(GtkKeyboardDevice.Instance) .Bind<IKeyboardDevice>().ToConstant(GtkKeyboardDevice.Instance)
.Bind<IMouseDevice>().ToConstant(GtkMouseDevice.Instance)
.Bind<IPlatformSettings>().ToConstant(s_instance) .Bind<IPlatformSettings>().ToConstant(s_instance)
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance) .Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
.Bind<IRendererFactory>().ToConstant(s_instance) .Bind<IRendererFactory>().ToConstant(s_instance)

2
src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs

@ -48,7 +48,7 @@ namespace Avalonia.Gtk
public int Height => _surface.Height; public int Height => _surface.Height;
public int RowBytes => _surface.Stride; public int RowBytes => _surface.Stride;
//TODO: Proper DPI detect //TODO: Proper DPI detect
public Size Dpi => new Size(96, 96); public Vector Dpi => new Vector(96, 96);
public PixelFormat Format => PixelFormat.Bgra8888; public PixelFormat Format => PixelFormat.Bgra8888;
} }
} }

3
src/Gtk/Avalonia.Gtk/TopLevelImpl.cs

@ -75,6 +75,8 @@ namespace Avalonia.Gtk
} }
} }
public IMouseDevice MouseDevice => GtkMouseDevice.Instance;
public Avalonia.Controls.WindowState WindowState public Avalonia.Controls.WindowState WindowState
{ {
get get
@ -114,6 +116,7 @@ namespace Avalonia.Gtk
public Action Closed { get; set; } public Action Closed { get; set; }
public Action Deactivated { get; set; } public Action Deactivated { get; set; }
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }

1
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -34,7 +34,6 @@ namespace Avalonia.Gtk3
.Bind<IClipboard>().ToSingleton<ClipboardImpl>() .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactory()) .Bind<IStandardCursorFactory>().ToConstant(new CursorFactory())
.Bind<IKeyboardDevice>().ToConstant(Keyboard) .Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IMouseDevice>().ToConstant(Mouse)
.Bind<IPlatformSettings>().ToConstant(Instance) .Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IPlatformThreadingInterface>().ToConstant(Instance) .Bind<IPlatformThreadingInterface>().ToConstant(Instance)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialog>() .Bind<ISystemDialogImpl>().ToSingleton<SystemDialog>()

5
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@ -52,12 +52,11 @@ namespace Avalonia.Gtk3
public int RowBytes { get; } public int RowBytes { get; }
public Size Dpi public Vector Dpi
{ {
get get
{ {
return new Vector(96, 96) * _factor;
return new Size(96, 96) * _factor;
} }
} }

2
src/Gtk/Avalonia.Gtk3/Interop/GObject.cs

@ -41,7 +41,7 @@ namespace Avalonia.Gtk3.Interop
class GtkWindow : GtkWidget class GtkWindow : GtkWidget
{ {
public static GtkWindow Null { get; } = new GtkWindow();
} }
class GtkImContext : GObject class GtkImContext : GObject

2
src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs

@ -11,6 +11,8 @@ namespace Avalonia.Gtk3.Interop
public Utf8Buffer(string s) : base(IntPtr.Zero, true) public Utf8Buffer(string s) : base(IntPtr.Zero, true)
{ {
if (s == null)
return;
_data = Encoding.UTF8.GetBytes(s); _data = Encoding.UTF8.GetBytes(s);
_gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned); _gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
handle = _gchandle.AddrOfPinnedObject(); handle = _gchandle.AddrOfPinnedObject();

3
src/Gtk/Avalonia.Gtk3/SystemDialogs.cs

@ -18,7 +18,8 @@ namespace Avalonia.Gtk3
bool multiselect, string initialFileName) bool multiselect, string initialFileName)
{ {
GtkFileChooser dlg; GtkFileChooser dlg;
using (var name = title != null ? new Utf8Buffer(title) : null) parent = parent ?? GtkWindow.Null;
using (var name = new Utf8Buffer(title))
dlg = Native.GtkFileChooserDialogNew(name, parent, action, IntPtr.Zero); dlg = Native.GtkFileChooserDialogNew(name, parent, action, IntPtr.Zero);
if (multiselect) if (multiselect)
Native.GtkFileChooserSetSelectMultiple(dlg, true); Native.GtkFileChooserSetSelectMultiple(dlg, true);

1
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -233,6 +233,7 @@ namespace Avalonia.Gtk3
} }
} }
public IMouseDevice MouseDevice => Gtk3Platform.Mouse;
public double Scaling => (double) 1 / (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1); public double Scaling => (double) 1 / (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1);

1
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -56,6 +56,7 @@ namespace Avalonia.LinuxFramebuffer
} }
public Size ClientSize => _fb.PixelSize; public Size ClientSize => _fb.PixelSize;
public IMouseDevice MouseDevice => LinuxFramebufferPlatform.MouseDevice;
public double Scaling => 1; public double Scaling => 1;
public IEnumerable<object> Surfaces => new object[] {_fb}; public IEnumerable<object> Surfaces => new object[] {_fb};
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }

6
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs

@ -13,16 +13,16 @@ namespace Avalonia.LinuxFramebuffer
{ {
public sealed unsafe class LinuxFramebuffer : IFramebufferPlatformSurface, IDisposable public sealed unsafe class LinuxFramebuffer : IFramebufferPlatformSurface, IDisposable
{ {
private readonly Size _dpi; private readonly Vector _dpi;
private int _fd; private int _fd;
private fb_fix_screeninfo _fixedInfo; private fb_fix_screeninfo _fixedInfo;
private fb_var_screeninfo _varInfo; private fb_var_screeninfo _varInfo;
private IntPtr _mappedLength; private IntPtr _mappedLength;
private IntPtr _mappedAddress; private IntPtr _mappedAddress;
public LinuxFramebuffer(string fileName = null, Size? dpi = null) public LinuxFramebuffer(string fileName = null, Vector? dpi = null)
{ {
_dpi = dpi ?? new Size(96, 96); _dpi = dpi ?? new Vector(96, 96);
fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0"; fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
_fd = NativeUnsafeMethods.open(fileName, 2, 0); _fd = NativeUnsafeMethods.open(fileName, 2, 0);
if (_fd <= 0) if (_fd <= 0)

1
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -33,7 +33,6 @@ namespace Avalonia.LinuxFramebuffer
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>() .Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice) .Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IMouseDevice>().ToConstant(MouseDevice)
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>() .Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory) .Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance) .Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)

4
src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs

@ -11,7 +11,7 @@ namespace Avalonia.LinuxFramebuffer
private fb_var_screeninfo _varInfo; private fb_var_screeninfo _varInfo;
private readonly IntPtr _address; private readonly IntPtr _address;
public LockedFramebuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr address, Size dpi) public LockedFramebuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr address, Vector dpi)
{ {
_fb = fb; _fb = fb;
_fixedInfo = fixedInfo; _fixedInfo = fixedInfo;
@ -41,7 +41,7 @@ namespace Avalonia.LinuxFramebuffer
public int Width => (int)_varInfo.xres; public int Width => (int)_varInfo.xres;
public int Height => (int) _varInfo.yres; public int Height => (int) _varInfo.yres;
public int RowBytes => (int) _fixedInfo.line_length; public int RowBytes => (int) _fixedInfo.line_length;
public Size Dpi { get; } public Vector Dpi { get; }
public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888; public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
} }
} }

2
src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs

@ -102,7 +102,7 @@ namespace Avalonia.Markup.Xaml.Data
private object ConvertValue(IList<object> values, Type targetType) private object ConvertValue(IList<object> values, Type targetType)
{ {
var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentUICulture); var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentCulture);
if (converted == AvaloniaProperty.UnsetValue && FallbackValue != null) if (converted == AvaloniaProperty.UnsetValue && FallbackValue != null)
{ {

10
src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorGrammar.cs

@ -93,9 +93,9 @@ namespace Avalonia.Markup.Xaml.Parsers
public static readonly Parser<ChildSyntax> Child = Parse.Char('>').Token().Return(new ChildSyntax()); public static readonly Parser<ChildSyntax> Child = Parse.Char('>').Token().Return(new ChildSyntax());
public static readonly Parser<DescendentSyntax> Descendent = public static readonly Parser<DescendantSyntax> Descendant =
from child in Parse.WhiteSpace.Many() from child in Parse.WhiteSpace.Many()
select new DescendentSyntax(); select new DescendantSyntax();
public static readonly Parser<TemplateSyntax> Template = public static readonly Parser<TemplateSyntax> Template =
from template in Parse.String("/template/").Token() from template in Parse.String("/template/").Token()
@ -115,7 +115,7 @@ namespace Avalonia.Markup.Xaml.Parsers
.Or<ISyntax>(Property) .Or<ISyntax>(Property)
.Or<ISyntax>(Child) .Or<ISyntax>(Child)
.Or<ISyntax>(Template) .Or<ISyntax>(Template)
.Or<ISyntax>(Descendent); .Or<ISyntax>(Descendant);
public static readonly Parser<IEnumerable<ISyntax>> Selector = SingleSelector.Many().End(); public static readonly Parser<IEnumerable<ISyntax>> Selector = SingleSelector.Many().End();
@ -191,11 +191,11 @@ namespace Avalonia.Markup.Xaml.Parsers
} }
} }
public class DescendentSyntax : ISyntax public class DescendantSyntax : ISyntax
{ {
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return obj is DescendentSyntax; return obj is DescendantSyntax;
} }
} }

6
src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorParser.cs

@ -47,7 +47,7 @@ namespace Avalonia.Markup.Xaml.Parsers
var name = i as SelectorGrammar.NameSyntax; var name = i as SelectorGrammar.NameSyntax;
var property = i as SelectorGrammar.PropertySyntax; var property = i as SelectorGrammar.PropertySyntax;
var child = i as SelectorGrammar.ChildSyntax; var child = i as SelectorGrammar.ChildSyntax;
var descendent = i as SelectorGrammar.DescendentSyntax; var descendant = i as SelectorGrammar.DescendantSyntax;
var template = i as SelectorGrammar.TemplateSyntax; var template = i as SelectorGrammar.TemplateSyntax;
if (ofType != null) if (ofType != null)
@ -102,9 +102,9 @@ namespace Avalonia.Markup.Xaml.Parsers
{ {
result = result.Child(); result = result.Child();
} }
else if (descendent != null) else if (descendant != null)
{ {
result = result.Descendent(); result = result.Descendant();
} }
else if (template != null) else if (template != null)
{ {

4
src/Markup/Avalonia.Markup/Data/BindingExpression.cs

@ -122,7 +122,7 @@ namespace Avalonia.Markup.Data
value, value,
type, type,
ConverterParameter, ConverterParameter,
CultureInfo.CurrentUICulture); CultureInfo.CurrentCulture);
if (converted == AvaloniaProperty.UnsetValue) if (converted == AvaloniaProperty.UnsetValue)
{ {
@ -186,7 +186,7 @@ namespace Avalonia.Markup.Data
value, value,
_targetType, _targetType,
ConverterParameter, ConverterParameter,
CultureInfo.CurrentUICulture); CultureInfo.CurrentCulture);
notification = converted as BindingNotification; notification = converted as BindingNotification;

2
src/Skia/Avalonia.Skia/BitmapImpl.cs

@ -131,7 +131,7 @@ namespace Avalonia.Skia
public int Width => _bmp.Width; public int Width => _bmp.Width;
public int Height => _bmp.Height; public int Height => _bmp.Height;
public int RowBytes => _bmp.RowBytes; public int RowBytes => _bmp.RowBytes;
public Size Dpi { get; } = new Size(96, 96); public Vector Dpi { get; } = new Vector(96, 96);
public PixelFormat Format => _bmp.ColorType.ToPixelFormat(); public PixelFormat Format => _bmp.ColorType.ToPixelFormat();
} }

2
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -76,7 +76,7 @@ namespace Avalonia.Skia
canvas.RestoreToCount(0); canvas.RestoreToCount(0);
canvas.Save(); canvas.Save();
canvas.ResetMatrix(); canvas.ResetMatrix();
var scale = Matrix.CreateScale(fb.Dpi.Width / 96, fb.Dpi.Height / 96); var scale = Matrix.CreateScale(fb.Dpi.X / 96, fb.Dpi.Y / 96);
return new DrawingContextImpl(canvas, visualBrushRenderer, scale, canvas, surface, shim, fb); return new DrawingContextImpl(canvas, visualBrushRenderer, scale, canvas, surface, shim, fb);
} }
} }

2
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -50,7 +50,9 @@
</Compile> </Compile>
<Compile Include="Direct2D1Platform.cs" /> <Compile Include="Direct2D1Platform.cs" />
<Compile Include="Disposable.cs" /> <Compile Include="Disposable.cs" />
<Compile Include="ExternalRenderTarget.cs" />
<Compile Include="HwndRenderTarget.cs" /> <Compile Include="HwndRenderTarget.cs" />
<Compile Include="IExternalDirect2DRenderTargetSurface.cs" />
<Compile Include="Media\BrushImpl.cs" /> <Compile Include="Media\BrushImpl.cs" />
<Compile Include="Media\BrushWrapper.cs" /> <Compile Include="Media\BrushWrapper.cs" />
<Compile Include="Media\DrawingContextImpl.cs" /> <Compile Include="Media\DrawingContextImpl.cs" />

15
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -135,12 +135,17 @@ namespace Avalonia.Direct2D1
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{ {
var nativeWindow = surfaces?.OfType<IPlatformHandle>().FirstOrDefault(); foreach (var s in surfaces)
if (nativeWindow != null)
{ {
if(nativeWindow.HandleDescriptor != "HWND") if (s is IPlatformHandle nativeWindow)
throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " + nativeWindow.HandleDescriptor); {
return new HwndRenderTarget(nativeWindow); if (nativeWindow.HandleDescriptor != "HWND")
throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " +
nativeWindow.HandleDescriptor);
return new HwndRenderTarget(nativeWindow);
}
if (s is IExternalDirect2DRenderTargetSurface external)
return new ExternalRenderTarget(external, s_dwfactory);
} }
throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces");
} }

50
src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Direct2D1.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1
{
class ExternalRenderTarget : IRenderTarget
{
private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider;
private readonly DirectWriteFactory _dwFactory;
private SharpDX.Direct2D1.RenderTarget _target;
public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
DirectWriteFactory dwFactory)
{
_externalRenderTargetProvider = externalRenderTargetProvider;
_dwFactory = dwFactory;
}
public void Dispose()
{
_target?.Dispose();
_target = null;
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
_target = _target ?? _externalRenderTargetProvider.CreateRenderTarget();
_externalRenderTargetProvider.BeforeDrawing();
return new DrawingContextImpl(visualBrushRenderer, _target, _dwFactory, null, () =>
{
try
{
_externalRenderTargetProvider.AfterDrawing();
}
catch (SharpDXException ex) when ((uint) ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET
{
_target?.Dispose();
_target = null;
}
});
}
}
}

15
src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Direct2D1
{
public interface IExternalDirect2DRenderTargetSurface
{
SharpDX.Direct2D1.RenderTarget CreateRenderTarget();
void BeforeDrawing();
void AfterDrawing();
}
}

7
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -23,6 +23,7 @@ namespace Avalonia.Direct2D1.Media
private readonly IVisualBrushRenderer _visualBrushRenderer; private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly SharpDX.DXGI.SwapChain1 _swapChain;
private readonly Action _finishedCallback;
private SharpDX.DirectWrite.Factory _directWriteFactory; private SharpDX.DirectWrite.Factory _directWriteFactory;
/// <summary> /// <summary>
@ -32,15 +33,18 @@ namespace Avalonia.Direct2D1.Media
/// <param name="renderTarget">The render target to draw to.</param> /// <param name="renderTarget">The render target to draw to.</param>
/// <param name="directWriteFactory">The DirectWrite factory.</param> /// <param name="directWriteFactory">The DirectWrite factory.</param>
/// <param name="swapChain">An optional swap chain associated with this drawing context.</param> /// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
/// <param name="finishedCallback">An optional delegate to be called when context is disposed.</param>
public DrawingContextImpl( public DrawingContextImpl(
IVisualBrushRenderer visualBrushRenderer, IVisualBrushRenderer visualBrushRenderer,
SharpDX.Direct2D1.RenderTarget renderTarget, SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory, SharpDX.DirectWrite.Factory directWriteFactory,
SharpDX.DXGI.SwapChain1 swapChain = null) SharpDX.DXGI.SwapChain1 swapChain = null,
Action finishedCallback = null)
{ {
_visualBrushRenderer = visualBrushRenderer; _visualBrushRenderer = visualBrushRenderer;
_renderTarget = renderTarget; _renderTarget = renderTarget;
_swapChain = swapChain; _swapChain = swapChain;
_finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory; _directWriteFactory = directWriteFactory;
_swapChain = swapChain; _swapChain = swapChain;
_renderTarget.BeginDraw(); _renderTarget.BeginDraw();
@ -73,6 +77,7 @@ namespace Avalonia.Direct2D1.Media
_renderTarget.EndDraw(); _renderTarget.EndDraw();
_swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None); _swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None);
_finishedCallback?.Invoke();
} }
catch (SharpDXException ex) when ((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET catch (SharpDXException ex) when ((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET
{ {

2
src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs

@ -37,7 +37,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
public int Width => _lock.Size.Width; public int Width => _lock.Size.Width;
public int Height => _lock.Size.Height; public int Height => _lock.Size.Height;
public int RowBytes => _lock.Stride; public int RowBytes => _lock.Stride;
public Size Dpi { get; } = new Size(96, 96); public Vector Dpi { get; } = new Vector(96, 96);
public PixelFormat Format => _format; public PixelFormat Format => _format;
} }

2
src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs

@ -10,7 +10,7 @@ namespace Avalonia.Win32.Input
{ {
class WindowsMouseDevice : MouseDevice class WindowsMouseDevice : MouseDevice
{ {
public new static WindowsMouseDevice Instance { get; } = new WindowsMouseDevice(); public static WindowsMouseDevice Instance { get; } = new WindowsMouseDevice();
public WindowImpl CurrentWindow public WindowImpl CurrentWindow
{ {

1
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -66,7 +66,6 @@ namespace Avalonia.Win32
.Bind<IClipboard>().ToSingleton<ClipboardImpl>() .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance) .Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance)
.Bind<IKeyboardDevice>().ToConstant(WindowsKeyboardDevice.Instance) .Bind<IKeyboardDevice>().ToConstant(WindowsKeyboardDevice.Instance)
.Bind<IMouseDevice>().ToConstant(WindowsMouseDevice.Instance)
.Bind<IPlatformSettings>().ToConstant(s_instance) .Bind<IPlatformSettings>().ToConstant(s_instance)
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance) .Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
.Bind<IRenderLoop>().ToConstant(new RenderLoop(60)) .Bind<IRenderLoop>().ToConstant(new RenderLoop(60))

6
src/Windows/Avalonia.Win32/WindowFramebuffer.cs

@ -39,7 +39,7 @@ namespace Avalonia.Win32
public int RowBytes => Width * 4; public int RowBytes => Width * 4;
public PixelFormat Format => PixelFormat.Bgra8888; public PixelFormat Format => PixelFormat.Bgra8888;
public Size Dpi public Vector Dpi
{ {
get get
{ {
@ -56,10 +56,10 @@ namespace Avalonia.Win32
out dpix, out dpix,
out dpiy) == 0) out dpiy) == 0)
{ {
return new Size(dpix, dpiy); return new Vector(dpix, dpiy);
} }
} }
return new Size(96, 96); return new Vector(96, 96);
} }
} }

2
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -133,6 +133,8 @@ namespace Avalonia.Win32
} }
} }
public IMouseDevice MouseDevice => WindowsMouseDevice.Instance;
public WindowState WindowState public WindowState WindowState
{ {
get get

4
src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs

@ -24,7 +24,7 @@ namespace Avalonia.iOS
Width = (int) frame.Width * factor; Width = (int) frame.Width * factor;
Height = (int) frame.Height * factor; Height = (int) frame.Height * factor;
RowBytes = Width * 4; RowBytes = Width * 4;
Dpi = new Size(96, 96) * factor; Dpi = new Vector(96, 96) * factor;
Format = PixelFormat.Rgba8888; Format = PixelFormat.Rgba8888;
Address = Marshal.AllocHGlobal(Height * RowBytes); Address = Marshal.AllocHGlobal(Height * RowBytes);
} }
@ -53,7 +53,7 @@ namespace Avalonia.iOS
public int Width { get; } public int Width { get; }
public int Height { get; } public int Height { get; }
public int RowBytes { get; } public int RowBytes { get; }
public Size Dpi { get; } public Vector Dpi { get; }
public PixelFormat Format { get; } public PixelFormat Format { get; }
} }
} }

2
src/iOS/Avalonia.iOS/TopLevelImpl.cs

@ -61,6 +61,8 @@ namespace Avalonia.iOS
public Size ClientSize => Bounds.Size.ToAvalonia(); public Size ClientSize => Bounds.Size.ToAvalonia();
public IMouseDevice MouseDevice => iOSPlatform.MouseDevice;
public override void Draw(CGRect rect) public override void Draw(CGRect rect)
{ {
Paint?.Invoke(new Rect(rect.X, rect.Y, rect.Width, rect.Height)); Paint?.Invoke(new Rect(rect.X, rect.Y, rect.Width, rect.Height));

1
src/iOS/Avalonia.iOS/iOSPlatform.cs

@ -57,7 +57,6 @@ namespace Avalonia.iOS
//.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>() //.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>() .Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice) .Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IMouseDevice>().ToConstant(MouseDevice)
.Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory) .Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>() .Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance) .Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)

1
tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0"> <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks> <TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup> </PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" /> <Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" /> <Import Project="..\..\build\Moq.props" />

134
tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs

@ -0,0 +1,134 @@
using System;
using System.Linq;
using Avalonia.Collections;
using Xunit;
namespace Avalonia.Base.UnitTests.Collections
{
public class AvaloniaListExtenionsTests
{
[Fact]
public void CreateDerivedList_Creates_Initial_Items()
{
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 });
var target = source.CreateDerivedList(x => new Wrapper(x));
var result = target.Select(x => x.Value).ToList();
Assert.Equal(source, result);
}
[Fact]
public void CreateDerivedList_Handles_Add()
{
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 });
var target = source.CreateDerivedList(x => new Wrapper(x));
source.Add(4);
var result = target.Select(x => x.Value).ToList();
Assert.Equal(source, result);
}
[Fact]
public void CreateDerivedList_Handles_Insert()
{
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 });
var target = source.CreateDerivedList(x => new Wrapper(x));
source.Insert(1, 4);
var result = target.Select(x => x.Value).ToList();
Assert.Equal(source, result);
}
[Fact]
public void CreateDerivedList_Handles_Remove()
{
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 });
var target = source.CreateDerivedList(x => new Wrapper(x));
source.Remove(2);
var result = target.Select(x => x.Value).ToList();
Assert.Equal(source, result);
}
[Fact]
public void CreateDerivedList_Handles_RemoveRange()
{
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 });
var target = source.CreateDerivedList(x => new Wrapper(x));
source.RemoveRange(1, 2);
var result = target.Select(x => x.Value).ToList();
Assert.Equal(source, result);
}
[Fact]
public void CreateDerivedList_Handles_Move()
{
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 });
var target = source.CreateDerivedList(x => new Wrapper(x));
source.Move(2, 0);
var result = target.Select(x => x.Value).ToList();
Assert.Equal(source, result);
}
[Fact]
public void CreateDerivedList_Handles_MoveRange()
{
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 });
var target = source.CreateDerivedList(x => new Wrapper(x));
source.MoveRange(1, 2, 0);
var result = target.Select(x => x.Value).ToList();
Assert.Equal(source, result);
}
[Fact]
public void CreateDerivedList_Handles_Replace()
{
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 });
var target = source.CreateDerivedList(x => new Wrapper(x));
source[1] = 4;
var result = target.Select(x => x.Value).ToList();
Assert.Equal(source, result);
}
[Fact]
public void CreateDerivedList_Handles_Clear()
{
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 });
var target = source.CreateDerivedList(x => new Wrapper(x));
source.Clear();
var result = target.Select(x => x.Value).ToList();
Assert.Equal(source, result);
}
private class Wrapper
{
public Wrapper(int value)
{
Value = value;
}
public int Value { get; }
}
}
}

2
tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs

@ -7,4 +7,4 @@ using Xunit;
[assembly: AssemblyTitle("Avalonia.UnitTests")] [assembly: AssemblyTitle("Avalonia.UnitTests")]
// Don't run tests in parallel. // Don't run tests in parallel.
[assembly: CollectionBehavior(DisableTestParallelization = true)] [assembly: CollectionBehavior(DisableTestParallelization = true)]

3
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@ -49,6 +49,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Layout\Measure.cs" />
<Compile Include="Styling\ApplyStyling.cs" /> <Compile Include="Styling\ApplyStyling.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
@ -100,7 +101,7 @@
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.9.2" /> <PackageReference Include="BenchmarkDotNet" Version="0.10.8" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildThisFileDirectory)..\..\src\Shared\nuget.workaround.targets" /> <Import Project="$(MSBuildThisFileDirectory)..\..\src\Shared\nuget.workaround.targets" />
</Project> </Project>

65
tests/Avalonia.Benchmarks/Layout/Measure.cs

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.UnitTests;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Layout
{
[MemoryDiagnoser]
public class Measure : IDisposable
{
private IDisposable _app;
private TestRoot root;
private List<Control> controls = new List<Control>();
public Measure()
{
_app = UnitTestApplication.Start(TestServices.RealLayoutManager);
var panel = new StackPanel();
root = new TestRoot { Child = panel };
controls.Add(panel);
CreateChildren(panel, 3, 5);
LayoutManager.Instance.ExecuteInitialLayoutPass(root);
}
public void Dispose()
{
_app.Dispose();
}
[Benchmark]
public void Remeasure_Half()
{
var random = new Random(1);
foreach (var control in controls)
{
if (random.Next(2) == 0)
{
control.InvalidateMeasure();
}
}
LayoutManager.Instance.ExecuteLayoutPass();
}
private void CreateChildren(IPanel parent, int childCount, int iterations)
{
for (var i = 0; i < childCount; ++i)
{
var control = new StackPanel();
parent.Children.Add(control);
if (iterations > 0)
{
CreateChildren(control, childCount, iterations - 1);
}
controls.Add(control);
}
}
}
}

1
tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs

@ -11,6 +11,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Benchmarks.Styling namespace Avalonia.Benchmarks.Styling
{ {
[MemoryDiagnoser]
public class ApplyStyling : IDisposable public class ApplyStyling : IDisposable
{ {
private IDisposable _app; private IDisposable _app;

4
tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj

@ -1,12 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0"> <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks> <TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup> </PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" /> <Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" /> <Import Project="..\..\build\Moq.props" />
<Import Project="..\..\build\XUnit.props" /> <Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" /> <Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
<ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.3.1" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" /> <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />

2
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -245,7 +245,7 @@ namespace Avalonia.Controls.UnitTests
// The items were created before the template was applied, so now we need to go back // The items were created before the template was applied, so now we need to go back
// and re-arrange everything. // and re-arrange everything.
foreach (IControl i in target.GetSelfAndVisualDescendents()) foreach (IControl i in target.GetSelfAndVisualDescendants())
{ {
i.InvalidateMeasure(); i.InvalidateMeasure();
} }

291
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@ -0,0 +1,291 @@
// 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.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.LogicalTree;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests.Presenters
{
/// <summary>
/// Tests for ContentControls that are hosted in a control template.
/// </summary>
public class ContentPresenterTests_InTemplate
{
[Fact]
public void Should_Register_With_Host_When_TemplatedParent_Set()
{
var host = new Mock<IContentPresenterHost>();
var target = new ContentPresenter();
target.SetValue(Control.TemplatedParentProperty, host.Object);
host.Verify(x => x.RegisterContentPresenter(target));
}
[Fact]
public void Setting_Content_To_Control_Should_Set_Child()
{
var (target, _) = CreateTarget();
var child = new Border();
target.Content = child;
Assert.Equal(child, target.Child);
}
[Fact]
public void Setting_Content_To_Control_Should_Update_Logical_Tree()
{
var (target, parent) = CreateTarget();
var child = new Border();
target.Content = child;
Assert.Equal(parent, child.GetLogicalParent());
Assert.Equal(new[] { child }, parent.GetLogicalChildren());
}
[Fact]
public void Setting_Content_To_Control_Should_Update_Visual_Tree()
{
var (target, _) = CreateTarget();
var child = new Border();
target.Content = child;
Assert.Equal(target, child.GetVisualParent());
Assert.Equal(new[] { child }, target.GetVisualChildren());
}
[Fact]
public void Setting_Content_To_String_Should_Create_TextBlock()
{
var (target, _) = CreateTarget();
target.Content = "Foo";
Assert.IsType<TextBlock>(target.Child);
Assert.Equal("Foo", ((TextBlock)target.Child).Text);
}
[Fact]
public void Setting_Content_To_String_Should_Update_Logical_Tree()
{
var (target, parent) = CreateTarget();
target.Content = "Foo";
var child = target.Child;
Assert.Equal(parent, child.GetLogicalParent());
Assert.Equal(new[] { child }, parent.GetLogicalChildren());
}
[Fact]
public void Setting_Content_To_String_Should_Update_Visual_Tree()
{
var (target, _) = CreateTarget();
target.Content = "Foo";
var child = target.Child;
Assert.Equal(target, child.GetVisualParent());
Assert.Equal(new[] { child }, target.GetVisualChildren());
}
[Fact]
public void Clearing_Control_Content_Should_Update_Logical_Tree()
{
var (target, _) = CreateTarget();
var child = new Border();
target.Content = child;
target.Content = null;
Assert.Equal(null, child.GetLogicalParent());
Assert.Empty(target.GetLogicalChildren());
}
[Fact]
public void Clearing_Control_Content_Should_Update_Visual_Tree()
{
var (target, _) = CreateTarget();
var child = new Border();
target.Content = child;
target.Content = null;
Assert.Equal(null, child.GetVisualParent());
Assert.Empty(target.GetVisualChildren());
}
[Fact]
public void Control_Content_Should_Not_Be_NameScope()
{
var (target, _) = CreateTarget();
target.Content = new TextBlock();
Assert.IsType<TextBlock>(target.Child);
Assert.Null(NameScope.GetNameScope((Control)target.Child));
}
[Fact]
public void DataTemplate_Created_Control_Should_Be_NameScope()
{
var (target, _) = CreateTarget();
target.Content = "Foo";
Assert.IsType<TextBlock>(target.Child);
Assert.NotNull(NameScope.GetNameScope((Control)target.Child));
}
[Fact]
public void Assigning_Control_To_Content_Should_Not_Set_DataContext()
{
var (target, _) = CreateTarget();
target.Content = new Border();
Assert.False(target.IsSet(Control.DataContextProperty));
}
[Fact]
public void Assigning_NonControl_To_Content_Should_Set_DataContext_On_UpdateChild()
{
var (target, _) = CreateTarget();
target.Content = "foo";
Assert.Equal("foo", target.DataContext);
}
[Fact]
public void Should_Use_ContentTemplate_If_Specified()
{
var (target, _) = CreateTarget();
target.ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas());
target.Content = "Foo";
Assert.IsType<Canvas>(target.Child);
}
[Fact]
public void Should_Update_If_ContentTemplate_Changed()
{
var (target, _) = CreateTarget();
target.Content = "Foo";
Assert.IsType<TextBlock>(target.Child);
target.ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas());
Assert.IsType<Canvas>(target.Child);
target.ContentTemplate = null;
Assert.IsType<TextBlock>(target.Child);
}
[Fact]
public void Assigning_Control_To_Content_After_NonControl_Should_Clear_DataContext()
{
var (target, _) = CreateTarget();
target.Content = "foo";
Assert.True(target.IsSet(Control.DataContextProperty));
target.Content = new Border();
Assert.False(target.IsSet(Control.DataContextProperty));
}
[Fact]
public void Recycles_DataTemplate()
{
var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), true));
target.Content = "foo";
var control = target.Child;
Assert.IsType<Border>(control);
target.Content = "bar";
Assert.Same(control, target.Child);
}
[Fact]
public void Detects_DataTemplate_Doesnt_Match_And_Doesnt_Recycle()
{
var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(x => x == "foo", _ => new Border(), true));
target.Content = "foo";
var control = target.Child;
Assert.IsType<Border>(control);
target.Content = "bar";
Assert.IsType<TextBlock>(target.Child);
}
[Fact]
public void Detects_DataTemplate_Doesnt_Support_Recycling()
{
var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), false));
target.Content = "foo";
var control = target.Child;
Assert.IsType<Border>(control);
target.Content = "bar";
Assert.NotSame(control, target.Child);
}
[Fact]
public void Reevaluates_DataTemplates_When_Recycling()
{
var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(x => x == "bar", _ => new Canvas(), true));
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), true));
target.Content = "foo";
var control = target.Child;
Assert.IsType<Border>(control);
target.Content = "bar";
Assert.IsType<Canvas>(target.Child);
}
(ContentPresenter presenter, ContentControl templatedParent) CreateTarget()
{
var templatedParent = new ContentControl
{
Template = new FuncControlTemplate<ContentControl>(x =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
}),
};
var root = new TestRoot { Child = templatedParent };
templatedParent.ApplyTemplate();
return ((ContentPresenter)templatedParent.Presenter, templatedParent);
}
private class TestContentControl : ContentControl
{
public IControl Child { get; set; }
}
}
}

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

Loading…
Cancel
Save