Browse Source

Merge branch 'master' into pr/558

pull/582/head
Steven Kirk 10 years ago
parent
commit
34a6bb7f26
  1. 27
      .travis.yml
  2. 1365
      Avalonia.mono.sln
  3. 16
      Avalonia.sln
  4. 2
      nuget/template/Avalonia.Skia.Desktop.nuspec
  5. 4
      readme.md
  6. 3
      samples/BindingTest/App.xaml.cs
  7. 3
      samples/ControlCatalog.Desktop/Program.cs
  8. 10
      samples/ControlCatalog/Pages/CanvasPage.xaml
  9. 1
      samples/ControlCatalog/Pages/MenuPage.xaml
  10. 3
      samples/TestApplication/Program.cs
  11. 14
      samples/VirtualizationTest/MainWindow.xaml
  12. 3
      samples/VirtualizationTest/Program.cs
  13. 38
      samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs
  14. 3
      samples/XamlTestApplication/Program.cs
  15. 8
      samples/XamlTestApplicationPcl/TestScrollable.cs
  16. 3
      samples/XamlTestApplicationPcl/Views/MainWindow.cs
  17. 11
      samples/XamlTestApplicationPcl/Views/MainWindow.xaml
  18. 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  19. 8
      src/Android/Avalonia.Android/Avalonia.Android.v2.ncrunchproject
  20. 46
      src/Avalonia.Controls/AppBuilder.cs
  21. 51
      src/Avalonia.Controls/Application.cs
  22. 6
      src/Avalonia.Controls/Avalonia.Controls.csproj
  23. 2
      src/Avalonia.Controls/Canvas.cs
  24. 13
      src/Avalonia.Controls/ControlExtensions.cs
  25. 22
      src/Avalonia.Controls/Design.cs
  26. 14
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  27. 5
      src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
  28. 27
      src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs
  29. 2
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  30. 22
      src/Avalonia.Controls/IVirtualizingController.cs
  31. 23
      src/Avalonia.Controls/IVirtualizingPanel.cs
  32. 2
      src/Avalonia.Controls/LayoutTransformControl.cs
  33. 7
      src/Avalonia.Controls/Menu.cs
  34. 7
      src/Avalonia.Controls/MenuItem.cs
  35. 17
      src/Avalonia.Controls/Mixins/SelectableMixin.cs
  36. 16
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  37. 2
      src/Avalonia.Controls/Presenters/IItemsPresenter.cs
  38. 65
      src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
  39. 28
      src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs
  40. 183
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  41. 48
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  42. 5
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  43. 61
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  44. 3
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  45. 12
      src/Avalonia.Controls/Primitives/ILogicalScrollable.cs
  46. 47
      src/Avalonia.Controls/Primitives/Popup.cs
  47. 12
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  48. 4
      src/Avalonia.Controls/Primitives/TabStrip.cs
  49. 2
      src/Avalonia.Controls/ScrollViewer.cs
  50. 33
      src/Avalonia.Controls/StackPanel.cs
  51. 3
      src/Avalonia.Controls/Templates/FuncTemplate`1.cs
  52. 6
      src/Avalonia.Controls/Templates/ITemplate`1.cs
  53. 59
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  54. 11
      src/Avalonia.Controls/Window.cs
  55. 18
      src/Avalonia.Controls/WrapPanel.cs
  56. 52
      src/Avalonia.DesignerSupport/DesignerApi.cs
  57. 64
      src/Avalonia.DesignerSupport/DesignerAssist.cs
  58. 4
      src/Avalonia.Input/Avalonia.Input.csproj
  59. 2
      src/Avalonia.Input/FocusManager.cs
  60. 2
      src/Avalonia.Input/IKeyboardNavigationHandler.cs
  61. 2
      src/Avalonia.Input/INavigableContainer.cs
  62. 16
      src/Avalonia.Input/InputExtensions.cs
  63. 34
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  64. 27
      src/Avalonia.Input/Navigation/DirectionalNavigation.cs
  65. 26
      src/Avalonia.Input/Navigation/TabNavigation.cs
  66. 14
      src/Avalonia.Input/NavigationDirection.cs
  67. 35
      src/Avalonia.Layout/LayoutManager.cs
  68. 11
      src/Avalonia.Layout/Layoutable.cs
  69. 39
      src/Avalonia.SceneGraph/Media/DrawingContext.cs
  70. 10
      src/Avalonia.SceneGraph/Media/Geometry.cs
  71. 14
      src/Avalonia.SceneGraph/Media/IDrawingContext.cs
  72. 3
      src/Avalonia.SceneGraph/Media/Imaging/Bitmap.cs
  73. 39
      src/Avalonia.SceneGraph/Media/PathMarkupParser.cs
  74. 7
      src/Avalonia.SceneGraph/Platform/IGeometryImpl.cs
  75. 16
      src/Avalonia.SceneGraph/Rect.cs
  76. 12
      src/Avalonia.SceneGraph/Rendering/RendererMixin.cs
  77. 55
      src/Avalonia.SceneGraph/Visual.cs
  78. 41
      src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs
  79. 14
      src/Avalonia.SceneGraph/VisualTree/IVisual.cs
  80. 24
      src/Avalonia.SceneGraph/VisualTree/TransformedBounds.cs
  81. 11
      src/Avalonia.Styling/Avalonia.Styling.csproj
  82. 0
      src/Avalonia.Styling/Controls/INameScope.cs
  83. 6
      src/Avalonia.Styling/Controls/NameScope.cs
  84. 0
      src/Avalonia.Styling/Controls/NameScopeEventArgs.cs
  85. 0
      src/Avalonia.Styling/Controls/NameScopeExtensions.cs
  86. 10
      src/Avalonia.Styling/Styling/ITemplate.cs
  87. 37
      src/Avalonia.Styling/Styling/Setter.cs
  88. 196
      src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs
  89. 9
      src/Gtk/Avalonia.Cairo/Media/StreamGeometryContextImpl.cs
  90. 5
      src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs
  91. 1
      src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaObjectAssembler.cs
  92. 9
      src/Markup/Avalonia.Markup.Xaml/Context/PropertyAccessor.cs
  93. 15
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  94. 8
      src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs
  95. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  96. 10
      src/Markup/Avalonia.Markup.Xaml/Templates/FocusAdornerTemplate.cs
  97. 10
      src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
  98. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs
  99. 11
      src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
  100. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

27
.travis.yml

@ -0,0 +1,27 @@
language: csharp
mono:
- nightly
solution: Avalonia.mono.sln
before_install:
- mkdir -p .nuget
- wget -O .nuget/nuget.exe https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
install:
- sudo apt-get install -y gtk-sharp2
- mono .nuget/nuget.exe restore Avalonia.mono.sln
- mono .nuget/nuget.exe install xunit.runner.console -Version 2.1.0 -OutputDirectory testrunner
script:
- xbuild /p:Configuration=Release Avalonia.mono.sln
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Base.UnitTests/bin/Release/Avalonia.Base.UnitTests.dll
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Input.UnitTests/bin/Release/Avalonia.Input.UnitTests.dll
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Interactivity.UnitTests/bin/Release/Avalonia.Interactivity.UnitTests.dll
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Layout.UnitTests/bin/Release/Avalonia.Layout.UnitTests.dll
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Markup.UnitTests/bin/Release/Avalonia.Markup.UnitTests.dll
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Styling.UnitTests/bin/Release/Avalonia.Styling.UnitTests.dll
notifications:
email: false
webhooks:
urls:
- https://webhooks.gitter.im/e/98f653320ef2b7506c05
on_success: change
on_failure: always
on_start: never

1365
Avalonia.mono.sln

File diff suppressed because it is too large

16
Avalonia.sln

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject
@ -15,6 +15,9 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Direct2D1", "src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj", "{3E908F67-5543-4879-A1DC-08EACE79B3CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer", "src\Windows\Avalonia.Designer\Avalonia.Designer.csproj", "{EC42600F-049B-43FF-AED1-8314D61B2749}"
ProjectSection(ProjectDependencies) = postProject
{2B888490-D14A-4BCA-AB4B-48676FA93C9B} = {2B888490-D14A-4BCA-AB4B-48676FA93C9B}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Input", "src\Avalonia.Input\Avalonia.Input.csproj", "{62024B2D-53EB-4638-B26B-85EEAA54866E}"
EndProject
@ -156,23 +159,33 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.Te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Avalonia.RenderTests", "tests\Avalonia.RenderTests\Avalonia.RenderTests.shproj", "{48840EDD-24BF-495D-911E-2EB12AE75D3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{e106cf37-4066-4615-b684-172a6d30b058}*SharedItemsImports = 4
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{dabfd304-d6a4-4752-8123-c2ccf7ac7831}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{78345174-5b52-4a14-b9fd-d5f2428137f0}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{ff69b927-c545-49ae-8e16-3d14d621aa12}*SharedItemsImports = 4
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{d35a9f3d-8bb0-496e-bf72-444038a7debb}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{8c923867-8a8f-4f6b-8b80-47d9e8436166}*SharedItemsImports = 4
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{48840edd-24bf-495d-911e-2eb12ae75d3b}*SharedItemsImports = 13
samples\TestApplicationShared\TestApplicationShared.projitems*{e3a1060b-50d0-44e8-88b6-f44ef2e5bd72}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4
@ -1983,6 +1996,7 @@ Global
{D35A9F3D-8BB0-496E-BF72-444038A7DEBB} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{52F55355-D120-42AC-8116-8410A7D602FA} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{48840EDD-24BF-495D-911E-2EB12AE75D3B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{FBCAF3D0-2808-4934-8E96-3F607594517B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
EndGlobal

2
nuget/template/Avalonia.Skia.Desktop.nuspec

@ -13,7 +13,7 @@
<copyright>Copyright 2015</copyright>
<tags>Avalonia</tags>
<dependencies>
<dependency id="SkiaSharp" version="1.49.2.1-beta"/>
<dependency id="SkiaSharp" version="1.49.3.0-beta"/>
<dependency id="Avalonia" version="#VERSION#" />
</dependencies>
</metadata>

4
readme.md

@ -3,6 +3,8 @@
[![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master)
[![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia)
A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android.
![](docs/images/screen.png)
@ -45,7 +47,7 @@ framework.
As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can
take a look at the [getting started page](docs/tutorial/gettingstarted.md) for an
overview of how to get started but probably the best thing to do for now is to already know a little bit
about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/Avalonia/Avalonia).
about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
There's also a high-level [architecture document](docs/spec/architecture.md) that is currently a little bit
out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/.

3
samples/BindingTest/App.xaml.cs

@ -19,8 +19,7 @@ namespace BindingTest
InitializeLogging();
AppBuilder.Configure<App>()
.UseWin32()
.UseDirect2D1()
.UsePlatformDetect()
.Start<MainWindow>();
}

3
samples/ControlCatalog.Desktop/Program.cs

@ -17,8 +17,7 @@ namespace ControlCatalog
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args
// again.
AppBuilder.Configure<App>()
.UseWin32()
.UseDirect2D1()
.UsePlatformDetect()
.Start<MainWindow>();
}

10
samples/ControlCatalog/Pages/CanvasPage.xaml

@ -3,7 +3,15 @@
<TextBlock Classes="h1">Canvas</TextBlock>
<TextBlock Classes="h2">A panel which lays out its children by explicit coordinates</TextBlock>
<Canvas Background="Yellow" Width="300" Height="400">
<Rectangle Fill="Blue" Width="63" Height="41" Canvas.Left="40" Canvas.Top="31"/>
<Rectangle Fill="Blue" Width="63" Height="41" Canvas.Left="40" Canvas.Top="31">
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Black"/>
<GradientStop Offset="1" Color="Transparent"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.OpacityMask> </Rectangle>
<Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="88" Canvas.Top="100"/>
<Path Fill="Orange" Data="M 0,0 c 50,0 50,-50 c 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/>
<Path Fill="OrangeRed" Canvas.Left="180" Canvas.Top="250">

1
samples/ControlCatalog/Pages/MenuPage.xaml

@ -10,6 +10,7 @@
<Menu>
<MenuItem Header="_First">
<MenuItem Header="Standard _Menu Item"/>
<Separator/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>

3
samples/TestApplication/Program.cs

@ -35,8 +35,7 @@ namespace TestApplication
var app = new App();
AppBuilder.Configure(app)
.UseWin32()
.UseDirect2D1()
.UsePlatformDetect()
.SetupWithoutStarting();
app.Run();

14
samples/VirtualizationTest/MainWindow.xaml

@ -5,6 +5,10 @@
Margin="16 0 0 0"
MinWidth="150"
Gap="4">
<DropDown Items="{Binding VirtualizationModes}"
SelectedItem="{Binding VirtualizationMode}"/>
<DropDown Items="{Binding Orientations}"
SelectedItem="{Binding Orientation}"/>
<TextBox Watermark="Item Count"
UseFloatingWatermark="True"
Text="{Binding ItemCount}"/>
@ -23,12 +27,20 @@
<Button Command="{Binding AddItemCommand}">Add Item</Button>
<Button Command="{Binding RemoveItemCommand}">Remove Item</Button>
<Button Command="{Binding RecreateCommand}">Recreate</Button>
<Button Command="{Binding SelectFirstCommand}">Select First</Button>
<Button Command="{Binding SelectLastCommand}">Select Last</Button>
</StackPanel>
<ListBox Name="listBox"
Items="{Binding Items}"
SelectedItems="{Binding SelectedItems}"
SelectionMode="Multiple">
SelectionMode="Multiple"
VirtualizationMode="{Binding VirtualizationMode}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="{Binding Orientation}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>

3
samples/VirtualizationTest/Program.cs

@ -16,8 +16,7 @@ namespace VirtualizationTest
InitializeLogging();
AppBuilder.Configure<App>()
.UseWin32()
.UseDirect2D1()
.UsePlatformDetect()
.Start<MainWindow>();
}

38
samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs

@ -2,8 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
using ReactiveUI;
namespace VirtualizationTest.ViewModels
@ -15,6 +17,8 @@ namespace VirtualizationTest.ViewModels
private int _newItemIndex;
private IReactiveList<ItemViewModel> _items;
private string _prefix = "Item";
private Orientation _orientation;
private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple;
public MainWindowViewModel()
{
@ -27,6 +31,12 @@ namespace VirtualizationTest.ViewModels
RemoveItemCommand = ReactiveCommand.Create();
RemoveItemCommand.Subscribe(_ => Remove());
SelectFirstCommand = ReactiveCommand.Create();
SelectFirstCommand.Subscribe(_ => SelectItem(0));
SelectLastCommand = ReactiveCommand.Create();
SelectLastCommand.Subscribe(_ => SelectItem(Items.Count - 1));
}
public string NewItemString
@ -50,11 +60,29 @@ namespace VirtualizationTest.ViewModels
private set { this.RaiseAndSetIfChanged(ref _items, value); }
}
public ReactiveCommand<object> AddItemCommand { get; private set; }
public Orientation Orientation
{
get { return _orientation; }
set { this.RaiseAndSetIfChanged(ref _orientation, value); }
}
public ReactiveCommand<object> RecreateCommand { get; private set; }
public IEnumerable<Orientation> Orientations =>
Enum.GetValues(typeof(Orientation)).Cast<Orientation>();
public ItemVirtualizationMode VirtualizationMode
{
get { return _virtualizationMode; }
set { this.RaiseAndSetIfChanged(ref _virtualizationMode, value); }
}
public IEnumerable<ItemVirtualizationMode> VirtualizationModes =>
Enum.GetValues(typeof(ItemVirtualizationMode)).Cast<ItemVirtualizationMode>();
public ReactiveCommand<object> AddItemCommand { get; private set; }
public ReactiveCommand<object> RecreateCommand { get; private set; }
public ReactiveCommand<object> RemoveItemCommand { get; private set; }
public ReactiveCommand<object> SelectFirstCommand { get; private set; }
public ReactiveCommand<object> SelectLastCommand { get; private set; }
private void ResizeItems(int count)
{
@ -103,5 +131,11 @@ namespace VirtualizationTest.ViewModels
.Select(x => new ItemViewModel(x, _prefix));
Items = new ReactiveList<ItemViewModel>(items);
}
private void SelectItem(int index)
{
SelectedItems.Clear();
SelectedItems.Add(Items[index]);
}
}
}

3
samples/XamlTestApplication/Program.cs

@ -21,8 +21,7 @@ namespace XamlTestApplication
InitializeLogging();
AppBuilder.Configure<XamlTestApp>()
.UseWin32()
.UseDirect2D1()
.UsePlatformDetect()
.Start<Views.MainWindow>();
}

8
samples/XamlTestApplicationPcl/TestScrollable.cs

@ -2,6 +2,7 @@ using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.VisualTree;
@ -75,7 +76,12 @@ namespace XamlTestApplication
}
}
public bool BringIntoView(IVisual target, Rect targetRect)
public bool BringIntoView(IControl target, Rect targetRect)
{
throw new NotImplementedException();
}
public IControl GetControlInDirection(NavigationDirection direction, IControl from)
{
throw new NotImplementedException();
}

3
samples/XamlTestApplicationPcl/Views/MainWindow.cs

@ -27,9 +27,6 @@ namespace XamlTestApplication.Views
AvaloniaXamlLoader.Load(this);
_exitMenu = this.FindControl<MenuItem>("exitMenu");
_exitMenu.Click += (s, e) => Application.Current.Exit();
var virtualList = this.FindControl<ListBox>("virtualList");
virtualList.Items = Enumerable.Range(0, 200).Select(x => $"Item {x}").ToList();
}
}
}

11
samples/XamlTestApplicationPcl/Views/MainWindow.xaml

@ -24,16 +24,7 @@
<TabControl.Transition>
<PageSlide Duration="0.25" />
</TabControl.Transition>
<TabItem Header="Virtualization">
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Right" Orientation="Vertical">
</StackPanel>
<ListBox Name="virtualList"/>
</DockPanel>
</TabItem>
<TabItem Header="Buttons">
<ScrollViewer CanScrollHorizontally="False">
<StackPanel Margin="10" Gap="4">

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

@ -38,7 +38,6 @@ namespace Avalonia.Android
.Bind<IWindowingPlatform>().ToConstant(this);
SkiaPlatform.Initialize();
Application.RegisterPlatformCallback(() => { });
_scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;

8
src/Android/Avalonia.Android/Avalonia.Android.v2.ncrunchproject

@ -7,7 +7,7 @@
<AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
<AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
<AllowCodeAnalysis>false</AllowCodeAnalysis>
<IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
<IgnoreThisComponentCompletely>true</IgnoreThisComponentCompletely>
<RunPreBuildEvents>false</RunPreBuildEvents>
<RunPostBuildEvents>false</RunPostBuildEvents>
<PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
@ -17,9 +17,9 @@
<DetectStackOverflow>true</DetectStackOverflow>
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
<DefaultTestTimeout>60000</DefaultTestTimeout>
<UseBuildConfiguration />
<UseBuildPlatform />
<ProxyProcessPath />
<UseBuildConfiguration></UseBuildConfiguration>
<UseBuildPlatform></UseBuildPlatform>
<ProxyProcessPath></ProxyProcessPath>
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
<MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
<BuildProcessArchitecture>x86</BuildProcessArchitecture>

46
src/Avalonia.Controls/AppBuilder.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reflection;
namespace Avalonia.Controls
{
@ -98,23 +99,64 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="initializer">The method to call to initialize the windowing subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder WithWindowingSubsystem(Action initializer)
public AppBuilder UseWindowingSubsystem(Action initializer)
{
WindowingSubsystem = initializer;
return this;
}
/// <summary>
/// Specifies a windowing subsystem to use.
/// </summary>
/// <param name="dll">The dll in which to look for subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder UseWindowingSubsystem(string dll) => UseWindowingSubsystem(GetInitializer(dll));
/// <summary>
/// Specifies a rendering subsystem to use.
/// </summary>
/// <param name="initializer">The method to call to initialize the rendering subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder WithRenderingSubsystem(Action initializer)
public AppBuilder UseRenderingSubsystem(Action initializer)
{
RenderingSubsystem = initializer;
return this;
}
/// <summary>
/// Specifies a rendering subsystem to use.
/// </summary>
/// <param name="dll">The dll in which to look for subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder UseRenderingSubsystem(string dll) => UseRenderingSubsystem(GetInitializer(dll));
static Action GetInitializer(string assemblyName) => () =>
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform";
var platformClassFullName = assemblyName + "." + platformClassName;
var platformClass = assembly.GetType(platformClassFullName);
var init = platformClass.GetRuntimeMethod("Initialize", new Type[0]);
init.Invoke(null, null);
};
public AppBuilder UsePlatformDetect()
{
var platformId = (int)
((dynamic) Type.GetType("System.Environment").GetRuntimeProperty("OSVersion").GetValue(null)).Platform;
if (platformId == 4 || platformId == 6)
{
UseRenderingSubsystem("Avalonia.Cairo");
UseWindowingSubsystem("Avalonia.Gtk");
}
else
{
UseRenderingSubsystem("Avalonia.Direct2D1");
UseWindowingSubsystem("Avalonia.Win32");
}
return this;
}
/// <summary>
/// Sets up the platform-speciic services for the <see cref="Application"/>.
/// </summary>

51
src/Avalonia.Controls/Application.cs

@ -26,16 +26,12 @@ namespace Avalonia
/// - A global set of <see cref="Styles"/>.
/// - A <see cref="FocusManager"/>.
/// - An <see cref="InputManager"/>.
/// - Loads and initializes rendering and windowing subsystems with
/// <see cref="InitializeSubsystems(int)"/> and <see cref="InitializeSubsystem(string)"/>.
/// - Registers services needed by the rest of Avalonia in the <see cref="RegisterServices"/>
/// method.
/// - Tracks the lifetime of the application.
/// </remarks>
public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IApplicationLifecycle
{
static Action _platformInitializationCallback;
/// <summary>
/// The application-global data templates.
/// </summary>
@ -121,11 +117,6 @@ namespace Avalonia
/// </summary>
IStyleHost IStyleHost.StylingParent => null;
public static void RegisterPlatformCallback(Action cb)
{
_platformInitializationCallback = cb;
}
/// <summary>
/// Initializes the application by loading XAML etc.
/// </summary>
@ -189,47 +180,5 @@ namespace Avalonia
.Bind<IRenderQueueManager>().ToTransient<RenderQueueManager>()
.Bind<IApplicationLifecycle>().ToConstant(this);
}
/// <summary>
/// Initializes the rendering and windowing subsystems according to platform.
/// </summary>
/// <param name="platformID">The value of Environment.OSVersion.Platform.</param>
protected void InitializeSubsystems(int platformID)
{
if (_platformInitializationCallback != null)
{
_platformInitializationCallback();
}
else if (platformID == 4 || platformID == 6)
{
InitializeSubsystem("Avalonia.Cairo");
InitializeSubsystem("Avalonia.Gtk");
}
else
{
InitializeSubsystem("Avalonia.Direct2D1");
InitializeSubsystem("Avalonia.Win32");
}
}
/// <summary>
/// Initializes the rendering or windowing subsystem defined by the specified assemblt.
/// </summary>
/// <param name="assemblyName">The name of the assembly.</param>
protected static void InitializeSubsystem(string assemblyName)
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform";
var platformClassFullName = assemblyName + "." + platformClassName;
var platformClass = assembly.GetType(platformClassFullName);
var init = platformClass.GetRuntimeMethod("Initialize", new Type[0]);
init.Invoke(null, null);
}
internal static void InitializeWin32Subsystem()
{
InitializeSubsystem("Avalonia.Direct2D1");
InitializeSubsystem("Avalonia.Win32");
}
}
}

6
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -51,21 +51,19 @@
<Compile Include="DockPanel.cs" />
<Compile Include="Expander.cs" />
<Compile Include="Generators\ItemContainerInfo.cs" />
<Compile Include="Generators\MenuItemContainerGenerator.cs" />
<Compile Include="Generators\TreeContainerIndex.cs" />
<Compile Include="HotkeyManager.cs" />
<Compile Include="IApplicationLifecycle.cs" />
<Compile Include="IScrollable.cs" />
<Compile Include="INameScope.cs" />
<Compile Include="IPseudoClasses.cs" />
<Compile Include="DropDownItem.cs" />
<Compile Include="ISetInheritanceParent.cs" />
<Compile Include="ItemVirtualizationMode.cs" />
<Compile Include="IVirtualizingController.cs" />
<Compile Include="IVirtualizingPanel.cs" />
<Compile Include="LayoutTransformControl.cs" />
<Compile Include="Mixins\ContentControlMixin.cs" />
<Compile Include="NameScope.cs" />
<Compile Include="NameScopeEventArgs.cs" />
<Compile Include="NameScopeExtensions.cs" />
<Compile Include="Platform\ITopLevelRenderer.cs" />
<Compile Include="Platform\IWindowingPlatform.cs" />
<Compile Include="Platform\PlatformManager.cs" />

2
src/Avalonia.Controls/Canvas.cs

@ -136,7 +136,7 @@ namespace Avalonia.Controls
/// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns>
IInputElement INavigableContainer.GetControl(FocusNavigationDirection direction, IInputElement from)
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from)
{
// TODO: Implement this
return null;

13
src/Avalonia.Controls/ControlExtensions.cs

@ -20,6 +20,8 @@ namespace Avalonia.Controls
/// <param name="control">The control.</param>
public static void BringIntoView(this IControl control)
{
Contract.Requires<ArgumentNullException>(control != null);
control.BringIntoView(new Rect(control.Bounds.Size));
}
@ -30,6 +32,8 @@ namespace Avalonia.Controls
/// <param name="rect">The area of the control to being into view.</param>
public static void BringIntoView(this IControl control, Rect rect)
{
Contract.Requires<ArgumentNullException>(control != null);
var ev = new RequestBringIntoViewEventArgs
{
RoutedEvent = Control.RequestBringIntoViewEvent,
@ -49,6 +53,9 @@ namespace Avalonia.Controls
/// <returns>The control or null if not found.</returns>
public static T FindControl<T>(this IControl control, string name) where T : class, IControl
{
Contract.Requires<ArgumentNullException>(control != null);
Contract.Requires<ArgumentNullException>(name != null);
var nameScope = control.FindNameScope();
if (nameScope == null)
@ -66,6 +73,8 @@ namespace Avalonia.Controls
/// <returns>The control's name scope, or null if not found.</returns>
public static INameScope FindNameScope(this IControl control)
{
Contract.Requires<ArgumentNullException>(control != null);
return control.GetSelfAndLogicalAncestors()
.OfType<Control>()
.Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
@ -80,6 +89,8 @@ namespace Avalonia.Controls
/// <param name="value">True to add the pseudoclass or false to remove.</param>
public static void Set(this IPseudoClasses classes, string name, bool value)
{
Contract.Requires<ArgumentNullException>(classes != null);
if (value)
{
classes.Add(name);
@ -99,6 +110,8 @@ namespace Avalonia.Controls
/// <returns>A disposable used to cancel the subscription.</returns>
public static IDisposable Set(this IPseudoClasses classes, string name, IObservable<bool> trigger)
{
Contract.Requires<ArgumentNullException>(classes != null);
return trigger.Subscribe(x => classes.Set(name, x));
}
}

22
src/Avalonia.Controls/Design.cs

@ -1,4 +1,6 @@
using System.Runtime.CompilerServices;
namespace Avalonia.Controls
{
public static class Design
@ -43,7 +45,25 @@ namespace Avalonia.Controls
{
return control.GetValue(DataContextProperty);
}
static readonly ConditionalWeakTable<object, Control> Substitutes = new ConditionalWeakTable<object, Control>();
public static readonly AttachedProperty<Control> PreviewWithProperty = AvaloniaProperty
.RegisterAttached<AvaloniaObject, Control>("PreviewWith", typeof (Design));
public static void SetPreviewWith(object target, Control control)
{
Substitutes.Remove(target);
Substitutes.Add(target, control);
}
public static Control GetPreviewWith(object target)
{
Control rv;
Substitutes.TryGetValue(target, out rv);
return rv;
}
internal static void ApplyDesignerProperties(Control target, Control source)
{
if (source.IsSet(WidthProperty))

14
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
namespace Avalonia.Controls.Generators
{
@ -187,11 +188,16 @@ namespace Avalonia.Controls.Generators
if (result == null)
{
result = new ContentPresenter
result = new ContentPresenter();
result.SetValue(ContentPresenter.ContentProperty, item, BindingPriority.Style);
if (ItemTemplate != null)
{
ContentTemplate = ItemTemplate,
Content = item,
};
result.SetValue(
ContentPresenter.ContentTemplateProperty,
ItemTemplate,
BindingPriority.TemplatedParent);
}
}
return result;

5
src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs

@ -5,6 +5,7 @@ using System;
using System.Linq.Expressions;
using System.Reflection;
using Avalonia.Controls.Templates;
using Avalonia.Data;
namespace Avalonia.Controls.Generators
{
@ -62,10 +63,10 @@ namespace Avalonia.Controls.Generators
if (ContentTemplateProperty != null)
{
result.SetValue(ContentTemplateProperty, ItemTemplate);
result.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style);
}
result.SetValue(ContentProperty, item);
result.SetValue(ContentProperty, item, BindingPriority.Style);
if (!(item is IControl))
{

27
src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Controls.Generators
{
public class MenuItemContainerGenerator : ItemContainerGenerator<MenuItem>
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemContainerGenerator{T}"/> class.
/// </summary>
/// <param name="owner">The owner control.</param>
public MenuItemContainerGenerator(IControl owner)
: base(owner, MenuItem.HeaderProperty, null)
{
}
/// <inheritdoc/>
protected override IControl CreateContainer(object item)
{
var separator = item as Separator;
return separator != null ? separator : base.CreateContainer(item);
}
}
}

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

@ -78,7 +78,7 @@ namespace Avalonia.Controls.Generators
var template = GetTreeDataTemplate(item, ItemTemplate);
var result = new T();
result.SetValue(ContentProperty, template.Build(item));
result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style);
var itemsSelector = template.ItemsSelector(item);

22
src/Avalonia.Controls/IVirtualizingController.cs

@ -0,0 +1,22 @@
// 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.
namespace Avalonia.Controls
{
/// <summary>
/// Interface implemented by controls that act as controllers for an
/// <see cref="IVirtualizingPanel"/>.
/// </summary>
public interface IVirtualizingController
{
/// <summary>
/// Called when the <see cref="IVirtualizingPanel"/>'s controls should be updated.
/// </summary>
/// <remarks>
/// The controller should respond to this method being called by either adding
/// children up until <see cref="IVirtualizingPanel.IsFull"/> becomes true or
/// removing <see cref="IVirtualizingPanel.OverflowCount"/> controls.
/// </remarks>
void UpdateControls();
}
}

23
src/Avalonia.Controls/IVirtualizingPanel.cs

@ -1,8 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Controls
{
/// <summary>
@ -10,14 +8,35 @@ namespace Avalonia.Controls
/// </summary>
public interface IVirtualizingPanel : IPanel
{
/// <summary>
/// Gets or sets the controller for the virtualizing panel.
/// </summary>
/// <remarks>
/// A virtualizing controller is responsible for maintaing the controls in the virtualizing
/// panel. This property will be set by the controller when virtualization is initialized.
/// Note that this property may remain null if the panel is added to a control that does
/// not act as a virtualizing controller.
/// </remarks>
IVirtualizingController Controller { get; set; }
/// <summary>
/// Gets a value indicating whether the panel is full.
/// </summary>
/// <remarks>
/// This property should return false until enough children are added to fill the space
/// passed into the last measure in the direction of scroll. It should be updated
/// immediately after a child is added or removed.
/// </remarks>
bool IsFull { get; }
/// <summary>
/// Gets the number of items that can be removed while keeping the panel full.
/// </summary>
/// <remarks>
/// This property should return the number of children that are completely out of the
/// panel's current bounds in the direction of scroll. It should be updated after an
/// arrange.
/// </remarks>
int OverflowCount { get; }
/// <summary>

2
src/Avalonia.Controls/LayoutTransformControl.cs

@ -139,7 +139,7 @@ namespace Avalonia.Controls
if (null != TransformRoot)
{
TransformRoot.RenderTransform = _matrixTransform;
TransformRoot.TransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute);
TransformRoot.RenderTransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute);
}
ApplyLayoutTransform();

7
src/Avalonia.Controls/Menu.cs

@ -4,6 +4,7 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
@ -131,6 +132,12 @@ namespace Avalonia.Controls
_subscription.Dispose();
}
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<MenuItem>(this, MenuItem.HeaderProperty, null);
}
/// <summary>
/// Called when a key is pressed within the menu.
/// </summary>

7
src/Avalonia.Controls/MenuItem.cs

@ -4,6 +4,7 @@
using System;
using System.Linq;
using System.Windows.Input;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -204,6 +205,12 @@ namespace Avalonia.Controls
IsSelected = true;
}
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
}
/// <summary>
/// Called when a key is pressed in the <see cref="MenuItem"/>.
/// </summary>

17
src/Avalonia.Controls/Mixins/SelectableMixin.cs

@ -51,22 +51,7 @@ namespace Avalonia.Controls.Mixins
if (sender != null)
{
var itemsControl = sender.Parent as SelectingItemsControl;
if ((bool)x.NewValue)
{
((IPseudoClasses)sender.Classes).Add(":selected");
if (((IVisual)sender).IsAttachedToVisualTree &&
itemsControl?.AutoScrollToSelectedItem == true)
{
sender.BringIntoView();
}
}
else
{
((IPseudoClasses)sender.Classes).Remove(":selected");
}
((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue);
sender.RaiseEvent(new RoutedEventArgs
{

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

@ -277,9 +277,23 @@ namespace Avalonia.Controls.Presenters
Child = newChild;
if (oldChild?.Parent == this)
{
LogicalChildren.Remove(oldChild);
}
if (newChild.Parent == null)
{
((ISetLogicalParent)newChild).SetParent((ILogical)this.TemplatedParent ?? this);
var templatedLogicalParent = TemplatedParent as ILogical;
if (templatedLogicalParent != null)
{
((ISetLogicalParent)newChild).SetParent(templatedLogicalParent);
}
else
{
LogicalChildren.Add(newChild);
}
}
VisualChildren.Add(newChild);

2
src/Avalonia.Controls/Presenters/IItemsPresenter.cs

@ -6,5 +6,7 @@ namespace Avalonia.Controls.Presenters
public interface IItemsPresenter : IPresenter
{
IPanel Panel { get; }
void ScrollIntoView(object item);
}
}

65
src/Avalonia.Controls/Presenters/ItemVirtualizer.cs

@ -6,6 +6,7 @@ using System.Collections;
using System.Collections.Specialized;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters
@ -13,8 +14,10 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Base class for classes which handle virtualization for an <see cref="ItemsPresenter"/>.
/// </summary>
internal abstract class ItemVirtualizer
internal abstract class ItemVirtualizer : IVirtualizingController, IDisposable
{
private bool disposedValue;
/// <summary>
/// Initializes a new instance of the <see cref="ItemVirtualizer"/> class.
/// </summary>
@ -22,6 +25,8 @@ namespace Avalonia.Controls.Presenters
public ItemVirtualizer(ItemsPresenter owner)
{
Owner = owner;
Items = owner.Items;
ItemCount = owner.Items.Count();
}
/// <summary>
@ -115,34 +120,45 @@ namespace Avalonia.Controls.Presenters
{
var virtualizingPanel = owner.Panel as IVirtualizingPanel;
var scrollable = (ILogicalScrollable)owner;
ItemVirtualizer result = null;
if (virtualizingPanel != null && scrollable.InvalidateScroll != null)
{
switch (owner.VirtualizationMode)
{
case ItemVirtualizationMode.Simple:
return new ItemVirtualizerSimple(owner);
result = new ItemVirtualizerSimple(owner);
break;
}
}
return new ItemVirtualizerNone(owner);
if (result == null)
{
result = new ItemVirtualizerNone(owner);
}
if (virtualizingPanel != null)
{
virtualizingPanel.Controller = result;
}
return result;
}
/// <summary>
/// Called by the <see cref="Owner"/> when it carries out an arrange.
/// </summary>
/// <param name="finalSize">The final size passed to the arrange.</param>
public abstract void Arranging(Size finalSize);
/// <inheritdoc/>
public virtual void UpdateControls()
{
}
/// <summary>
/// Called when a request is made to bring an item into view.
/// Gets the next control in the specified direction.
/// </summary>
/// <param name="target">The item to bring into view.</param>
/// <param name="targetRect">The rect on the item to bring into view.</param>
/// <returns>True if the request was handled; otherwise false.</returns>
public virtual bool BringIntoView(IVisual target, Rect targetRect)
/// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns>
public virtual IControl GetControlInDirection(NavigationDirection direction, IControl from)
{
return false;
return null;
}
/// <summary>
@ -157,5 +173,26 @@ namespace Avalonia.Controls.Presenters
Items = items;
ItemCount = items.Count();
}
/// <summary>
/// Scrolls the specified item into view.
/// </summary>
/// <param name="item">The item.</param>
public virtual void ScrollIntoView(object item)
{
}
/// <inheritdoc/>
public virtual void Dispose()
{
VirtualizingPanel.Controller = null;
VirtualizingPanel.Children.Clear();
Owner.ItemContainerGenerator.Clear();
}
/// <summary>
/// Invalidates the current scroll.
/// </summary>
protected void InvalidateScroll() => ((ILogicalScrollable)Owner).InvalidateScroll();
}
}

28
src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs

@ -19,6 +19,10 @@ namespace Avalonia.Controls.Presenters
public ItemVirtualizerNone(ItemsPresenter owner)
: base(owner)
{
if (Items != null && owner.Panel != null)
{
AddContainers(0, Items);
}
}
/// <inheritdoc/>
@ -52,12 +56,6 @@ namespace Avalonia.Controls.Presenters
get { throw new NotSupportedException(); }
}
/// <inheritdoc/>
public override void Arranging(Size finalSize)
{
// We don't need to do anything here.
}
/// <inheritdoc/>
public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e)
{
@ -112,6 +110,24 @@ namespace Avalonia.Controls.Presenters
Owner.InvalidateMeasure();
}
/// <summary>
/// Scrolls the specified item into view.
/// </summary>
/// <param name="item">The item.</param>
public override void ScrollIntoView(object item)
{
if (Items != null)
{
var index = Items.IndexOf(item);
if (index != -1)
{
var container = Owner.ItemContainerGenerator.ContainerFromIndex(index);
container?.BringIntoView();
}
}
}
private IList<ItemContainerInfo> AddContainers(int index, IEnumerable items)
{
var generator = Owner.ItemContainerGenerator;

183
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -5,8 +5,11 @@ using System;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters
{
@ -23,6 +26,8 @@ namespace Avalonia.Controls.Presenters
public ItemVirtualizerSimple(ItemsPresenter owner)
: base(owner)
{
// Don't need to add children here as UpdateControls should be called by the panel
// measure/arrange.
}
/// <inheritdoc/>
@ -48,7 +53,9 @@ namespace Avalonia.Controls.Presenters
if (delta != 0)
{
if ((NextIndex - 1) + delta < ItemCount)
var newLastIndex = (NextIndex - 1) + delta;
if (newLastIndex < ItemCount)
{
if (panel.PixelOffset > 0)
{
@ -63,10 +70,14 @@ namespace Avalonia.Controls.Presenters
}
else
{
// We're moving to a partially obscured item at the end of the list.
// We're moving to a partially obscured item at the end of the list so
// offset the panel by the height of the first item.
var firstIndex = ItemCount - panel.Children.Count;
RecycleContainersForMove(firstIndex - FirstIndex);
panel.PixelOffset = VirtualizingPanel.PixelOverflow;
panel.PixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
panel.Children[0].Bounds.Height :
panel.Children[0].Bounds.Width;
}
}
}
@ -84,10 +95,10 @@ namespace Avalonia.Controls.Presenters
}
/// <inheritdoc/>
public override void Arranging(Size finalSize)
public override void UpdateControls()
{
CreateAndRemoveContainers();
((ILogicalScrollable)Owner).InvalidateScroll();
InvalidateScroll();
}
/// <inheritdoc/>
@ -95,15 +106,18 @@ namespace Avalonia.Controls.Presenters
{
base.ItemsChanged(items, e);
var panel = VirtualizingPanel;
if (items != null)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
CreateAndRemoveContainers();
if (e.NewStartingIndex >= FirstIndex &&
e.NewStartingIndex + e.NewItems.Count <= NextIndex)
{
CreateAndRemoveContainers();
RecycleContainers();
}
@ -125,6 +139,7 @@ namespace Avalonia.Controls.Presenters
case NotifyCollectionChangedAction.Reset:
RecycleContainersOnRemove();
CreateAndRemoveContainers();
break;
}
}
@ -132,9 +147,94 @@ namespace Avalonia.Controls.Presenters
{
Owner.ItemContainerGenerator.Clear();
VirtualizingPanel.Children.Clear();
FirstIndex = NextIndex = 0;
}
((ILogicalScrollable)Owner).InvalidateScroll();
// If we are scrolled to view a partially visible last item but controls were added
// then we need to return to a non-offset scroll position.
if (panel.PixelOffset != 0 && FirstIndex + panel.Children.Count < ItemCount)
{
panel.PixelOffset = 0;
RecycleContainersForMove(1);
}
InvalidateScroll();
}
public override IControl GetControlInDirection(NavigationDirection direction, IControl from)
{
var generator = Owner.ItemContainerGenerator;
var panel = VirtualizingPanel;
var itemIndex = generator.IndexFromContainer(from);
var vertical = VirtualizingPanel.ScrollDirection == Orientation.Vertical;
if (itemIndex == -1)
{
return null;
}
var newItemIndex = -1;
switch (direction)
{
case NavigationDirection.First:
newItemIndex = 0;
break;
case NavigationDirection.Last:
newItemIndex = ItemCount - 1;
break;
case NavigationDirection.Up:
if (vertical)
{
newItemIndex = itemIndex - 1;
}
break;
case NavigationDirection.Down:
if (vertical)
{
newItemIndex = itemIndex + 1;
}
break;
case NavigationDirection.Left:
if (!vertical)
{
newItemIndex = itemIndex - 1;
}
break;
case NavigationDirection.Right:
if (!vertical)
{
newItemIndex = itemIndex + 1;
}
break;
case NavigationDirection.PageUp:
newItemIndex = Math.Max(0, itemIndex - (int)ViewportValue);
break;
case NavigationDirection.PageDown:
newItemIndex = Math.Min(ItemCount - 1, itemIndex + (int)ViewportValue);
break;
}
return ScrollIntoView(newItemIndex);
}
/// <inheritdoc/>
public override void ScrollIntoView(object item)
{
var index = Items.IndexOf(item);
if (index != -1)
{
ScrollIntoView(index);
}
}
/// <summary>
@ -146,7 +246,7 @@ namespace Avalonia.Controls.Presenters
var generator = Owner.ItemContainerGenerator;
var panel = VirtualizingPanel;
if (!panel.IsFull && Items != null)
if (!panel.IsFull && Items != null && panel.IsAttachedToVisualTree)
{
var memberSelector = Owner.MemberSelector;
var index = NextIndex;
@ -336,5 +436,70 @@ namespace Avalonia.Controls.Presenters
Owner.ItemContainerGenerator.Dematerialize(FirstIndex + index, count);
NextIndex -= count;
}
/// <summary>
/// Scrolls the item with the specified index into view.
/// </summary>
/// <param name="index">The item index.</param>
/// <returns>The container that was brought into view.</returns>
private IControl ScrollIntoView(int index)
{
var panel = VirtualizingPanel;
var generator = Owner.ItemContainerGenerator;
var newOffset = -1.0;
if (index >= 0 && index < ItemCount)
{
if (index < FirstIndex)
{
newOffset = index;
}
else if (index >= NextIndex)
{
newOffset = index - Math.Ceiling(ViewportValue - 1);
}
else if (OffsetValue + ViewportValue >= ItemCount)
{
newOffset = OffsetValue - 1;
}
if (newOffset != -1)
{
OffsetValue = newOffset;
}
var container = generator.ContainerFromIndex(index);
var layoutManager = LayoutManager.Instance;
// We need to do a layout here because it's possible that the container we moved to
// is only partially visible due to differing item sizes. If the container is only
// partially visible, scroll again. Don't do this if there's no layout manager:
// it means we're running a unit test.
if (layoutManager != null)
{
layoutManager.ExecuteLayoutPass();
if (!new Rect(panel.Bounds.Size).Contains(container.Bounds))
{
OffsetValue += 1;
}
}
return container;
}
return null;
}
/// <summary>
/// Ensures an offset value is within the value range.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The coerced value.</returns>
private double CoerceOffset(double value)
{
var max = Math.Max(ExtentValue - ViewportValue, 0);
return MathUtilities.Clamp(value, 0, max);
}
}
}

48
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -5,7 +5,6 @@ using System;
using System.Collections.Specialized;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.VisualTree;
using static Avalonia.Utilities.MathUtilities;
namespace Avalonia.Controls.Presenters
@ -33,6 +32,9 @@ namespace Avalonia.Controls.Presenters
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(
typeof(ItemsPresenter),
KeyboardNavigationMode.Once);
VirtualizationModeProperty.Changed
.AddClassHandler<ItemsPresenter>(x => x.VirtualizationModeChanged);
}
/// <summary>
@ -67,23 +69,46 @@ namespace Avalonia.Controls.Presenters
Action ILogicalScrollable.InvalidateScroll { get; set; }
/// <inheritdoc/>
Size ILogicalScrollable.ScrollSize => new Size(0, 1);
Size ILogicalScrollable.ScrollSize => new Size(1, 1);
/// <inheritdoc/>
Size ILogicalScrollable.PageScrollSize => new Size(0, 1);
/// <inheritdoc/>
bool ILogicalScrollable.BringIntoView(IVisual target, Rect targetRect)
bool ILogicalScrollable.BringIntoView(IControl target, Rect targetRect)
{
return _virtualizer?.BringIntoView(target, targetRect) ?? false;
return false;
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
IControl ILogicalScrollable.GetControlInDirection(NavigationDirection direction, IControl from)
{
return _virtualizer?.GetControlInDirection(direction, from);
}
public override void ScrollIntoView(object item)
{
var result = base.ArrangeOverride(finalSize);
_virtualizer.Arranging(finalSize);
return result;
_virtualizer?.ScrollIntoView(item);
}
protected override Size MeasureOverride(Size availableSize)
{
// If infinity is passed as the available size and we're virtualized then we need to
// fill the available space, but to do that we *don't* want to materialize all our
// items! Take a look at the root of the tree for a MaxClientSize and use that as
// the available size.
if (availableSize == Size.Infinity && VirtualizationMode != ItemVirtualizationMode.None)
{
var window = VisualRoot as Window;
if (window != null)
{
availableSize = window.PlatformImpl.MaxClientSize;
}
}
Panel.Measure(availableSize);
return Panel.DesiredSize;
}
/// <inheritdoc/>
@ -116,5 +141,12 @@ namespace Avalonia.Controls.Presenters
var maxY = Math.Max(scrollable.Extent.Height - scrollable.Viewport.Height, 0);
return new Vector(Clamp(value.X, 0, maxX), Clamp(value.Y, 0, maxY));
}
private void VirtualizationModeChanged(AvaloniaPropertyChangedEventArgs e)
{
_virtualizer?.Dispose();
_virtualizer = ItemVirtualizer.Create(this);
((ILogicalScrollable)this).InvalidateScroll?.Invoke();
}
}
}

5
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -163,6 +163,11 @@ namespace Avalonia.Controls.Presenters
}
}
/// <inheritdoc/>
public virtual void ScrollIntoView(object item)
{
}
/// <summary>
/// Creates the <see cref="ItemContainerGenerator"/> for the control.
/// </summary>

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

@ -59,6 +59,7 @@ namespace Avalonia.Controls.Presenters
static ScrollContentPresenter()
{
ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>(x => x.ChildChanged);
AffectsArrange(OffsetProperty);
}
@ -118,10 +119,11 @@ namespace Avalonia.Controls.Presenters
}
var scrollable = Child as ILogicalScrollable;
var control = target as IControl;
if (scrollable?.IsLogicalScrollEnabled == true)
if (scrollable?.IsLogicalScrollEnabled == true && control != null)
{
return scrollable.BringIntoView(target, targetRect);
return scrollable.BringIntoView(control, targetRect);
}
var transform = target.TransformToVisual(Child);
@ -229,26 +231,32 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
if (Extent.Height > Viewport.Height)
if (Extent.Height > Viewport.Height || Extent.Width > Viewport.Width)
{
var scrollable = Child as ILogicalScrollable;
bool isLogical = scrollable?.IsLogicalScrollEnabled == true;
if (scrollable?.IsLogicalScrollEnabled == true)
{
var y = Offset.Y + (-e.Delta.Y * scrollable.ScrollSize.Height);
double x = Offset.X;
double y = Offset.Y;
if (Extent.Height > Viewport.Height)
{
double height = isLogical ? scrollable.ScrollSize.Height : 50;
y += -e.Delta.Y * height;
y = Math.Max(y, 0);
y = Math.Min(y, Extent.Height - Viewport.Height);
Offset = new Vector(Offset.X, y);
e.Handled = true;
}
else
if (Extent.Width > Viewport.Width)
{
var y = Offset.Y + (-e.Delta.Y * 50);
y = Math.Max(y, 0);
y = Math.Min(y, Extent.Height - Viewport.Height);
Offset = new Vector(Offset.X, y);
e.Handled = true;
double width = isLogical ? scrollable.ScrollSize.Width : 50;
x += -e.Delta.X * width;
x = Math.Max(x, 0);
x = Math.Min(x, Extent.Width - Viewport.Width);
}
Offset = new Vector(x, y);
e.Handled = true;
}
}
@ -257,6 +265,16 @@ namespace Avalonia.Controls.Presenters
e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect);
}
private void ChildChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateScrollableSubscription((IControl)e.NewValue);
if (e.OldValue != null)
{
Offset = default(Vector);
}
}
private void UpdateScrollableSubscription(IControl child)
{
var scrollable = child as ILogicalScrollable;
@ -268,7 +286,7 @@ namespace Avalonia.Controls.Presenters
{
scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
if (scrollable?.IsLogicalScrollEnabled == true)
if (scrollable.IsLogicalScrollEnabled == true)
{
_logicalScrollSubscription = new CompositeDisposable(
this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
@ -285,15 +303,10 @@ namespace Avalonia.Controls.Presenters
if (logicalScroll != scrollable.IsLogicalScrollEnabled)
{
UpdateScrollableSubscription(Child);
if (!scrollable.IsLogicalScrollEnabled)
{
Offset = default(Vector);
InvalidateMeasure();
}
Offset = default(Vector);
InvalidateMeasure();
}
if (scrollable.IsLogicalScrollEnabled)
else if (scrollable.IsLogicalScrollEnabled)
{
Viewport = scrollable.Viewport;
Extent = scrollable.Extent;
@ -301,4 +314,4 @@ namespace Avalonia.Controls.Presenters
}
}
}
}
}

3
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.VisualTree;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives
{
@ -58,6 +59,8 @@ namespace Avalonia.Controls.Primitives
if (info != null)
{
child.RenderTransform = new MatrixTransform(info.Bounds.Transform);
child.RenderTransformOrigin = new RelativePoint(new Point(0,0), RelativeUnit.Absolute);
child.Arrange(info.Bounds.Bounds);
}
else

12
src/Avalonia.Controls/Primitives/ILogicalScrollable.cs

@ -2,7 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.VisualTree;
using Avalonia.Input;
namespace Avalonia.Controls.Primitives
{
@ -56,6 +56,14 @@ namespace Avalonia.Controls.Primitives
/// <param name="target">The target visual.</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>
bool BringIntoView(IVisual target, Rect targetRect);
bool BringIntoView(IControl target, Rect targetRect);
/// <summary>
/// Gets the next control in the specified direction.
/// </summary>
/// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns>
IControl GetControlInDirection(NavigationDirection direction, IControl from);
}
}

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

@ -10,6 +10,7 @@ using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.VisualTree;
using Avalonia.Layout;
namespace Avalonia.Controls.Primitives
{
@ -39,6 +40,18 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
/// <summary>
/// Defines the <see cref="HorizontalOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> HorizontalOffsetProperty =
AvaloniaProperty.Register<Popup, double>(nameof(HorizontalOffset));
/// <summary>
/// Defines the <see cref="VerticalOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> VerticalOffsetProperty =
AvaloniaProperty.Register<Popup, double>(nameof(VerticalOffset));
/// <summary>
/// Defines the <see cref="PlacementTarget"/> property.
/// </summary>
@ -122,6 +135,24 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementModeProperty, value); }
}
/// <summary>
/// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
/// </summary>
public double HorizontalOffset
{
get { return GetValue(HorizontalOffsetProperty); }
set { SetValue(HorizontalOffsetProperty, value); }
}
/// <summary>
/// Gets or sets the Vertical offset of the popup in relation to the <see cref="PlacementTarget"/>
/// </summary>
public double VerticalOffset
{
get { return GetValue(VerticalOffsetProperty); }
set { SetValue(VerticalOffsetProperty, value); }
}
/// <summary>
/// Gets or sets the control that is used to determine the popup's position.
/// </summary>
@ -287,18 +318,26 @@ namespace Avalonia.Controls.Primitives
if (target?.GetVisualRoot() == null)
{
mode = PlacementMode.Pointer;
}
}
switch (mode)
{
case PlacementMode.Pointer:
return MouseDevice.Instance?.Position ?? default(Point);
if (MouseDevice.Instance != null)
{
// Scales the Horizontal and Vertical offset to screen co-ordinates.
var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling);
return MouseDevice.Instance.Position + screenOffset;
}
return default(Point);
case PlacementMode.Bottom:
return target?.PointToScreen(new Point(0, target.Bounds.Height)) ?? zero;
return target?.PointToScreen(new Point(0 + HorizontalOffset, target.Bounds.Height + VerticalOffset)) ?? zero;
case PlacementMode.Right:
return target?.PointToScreen(new Point(target.Bounds.Width, 0)) ?? zero;
return target?.PointToScreen(new Point(target.Bounds.Width + HorizontalOffset, 0 + VerticalOffset)) ?? zero;
default:
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");

12
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -280,6 +280,12 @@ namespace Avalonia.Controls.Primitives
}
}
/// <summary>
/// Scrolls the specified item into view.
/// </summary>
/// <param name="item">The item.</param>
public void ScrollIntoView(object item) => Presenter?.ScrollIntoView(item);
/// <summary>
/// Tries to get the container that was the source of an event.
/// </summary>
@ -723,6 +729,12 @@ namespace Avalonia.Controls.Primitives
{
case NotifyCollectionChangedAction.Add:
SelectedItemsAdded(e.NewItems.Cast<object>().ToList());
if (AutoScrollToSelectedItem)
{
ScrollIntoView(e.NewItems[0]);
}
added = e.NewItems;
break;

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

@ -9,6 +9,9 @@ namespace Avalonia.Controls.Primitives
{
public class TabStrip : SelectingItemsControl
{
private static readonly FuncTemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new WrapPanel { Orientation = Orientation.Horizontal });
private static IMemberSelector s_MemberSelector = new FuncMemberSelector<object, object>(SelectHeader);
static TabStrip()
@ -16,6 +19,7 @@ namespace Avalonia.Controls.Primitives
MemberSelectorProperty.OverrideDefaultValue<TabStrip>(s_MemberSelector);
SelectionModeProperty.OverrideDefaultValue<TabStrip>(SelectionMode.AlwaysSelected);
FocusableProperty.OverrideDefaultValue(typeof(TabStrip), false);
ItemsPanelProperty.OverrideDefaultValue<TabStrip>(DefaultPanel);
}
protected override IItemContainerGenerator CreateItemContainerGenerator()

2
src/Avalonia.Controls/ScrollViewer.cs

@ -370,7 +370,7 @@ namespace Avalonia.Controls
private static double Max(double x, double y)
{
var result = Math.Max(x, y);
return double.IsNaN(result) ? 0 : Math.Round(result);
return double.IsNaN(result) ? 0 : result;
}
private static Vector ValidateOffset(AvaloniaObject o, Vector value)

33
src/Avalonia.Controls/StackPanel.cs

@ -72,37 +72,52 @@ namespace Avalonia.Controls
/// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns>
IInputElement INavigableContainer.GetControl(FocusNavigationDirection direction, IInputElement from)
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from)
{
var fromControl = from as IControl;
return (fromControl != null) ? GetControlInDirection(direction, fromControl) : null;
}
/// <summary>
/// Gets the next control in the specified direction.
/// </summary>
/// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns>
protected virtual IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{
var horiz = Orientation == Orientation.Horizontal;
int index = Children.IndexOf((IControl)from);
switch (direction)
{
case FocusNavigationDirection.First:
case NavigationDirection.First:
index = 0;
break;
case FocusNavigationDirection.Last:
case NavigationDirection.Last:
index = Children.Count - 1;
break;
case FocusNavigationDirection.Next:
case NavigationDirection.Next:
++index;
break;
case FocusNavigationDirection.Previous:
case NavigationDirection.Previous:
--index;
break;
case FocusNavigationDirection.Left:
case NavigationDirection.Left:
index = horiz ? index - 1 : -1;
break;
case FocusNavigationDirection.Right:
case NavigationDirection.Right:
index = horiz ? index + 1 : -1;
break;
case FocusNavigationDirection.Up:
case NavigationDirection.Up:
index = horiz ? -1 : index - 1;
break;
case FocusNavigationDirection.Down:
case NavigationDirection.Down:
index = horiz ? -1 : index + 1;
break;
default:
index = -1;
break;
}
if (index >= 0 && index < Children.Count)

3
src/Avalonia.Controls/Templates/FuncTemplate`1.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Styling;
namespace Avalonia.Controls.Templates
{
@ -34,5 +35,7 @@ namespace Avalonia.Controls.Templates
{
return _func();
}
object ITemplate.Build() => Build();
}
}

6
src/Avalonia.Controls/Templates/ITemplate`1.cs

@ -1,13 +1,15 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Styling;
namespace Avalonia.Controls
{
/// <summary>
/// Creates a control.
/// </summary>
/// <typeparam name="TControl">The type of control.</typeparam>
public interface ITemplate<TControl> where TControl : IControl
public interface ITemplate<TControl> : ITemplate where TControl : IControl
{
/// <summary>
/// Creates the control.
@ -15,6 +17,6 @@ namespace Avalonia.Controls
/// <returns>
/// The created control.
/// </returns>
TControl Build();
new TControl Build();
}
}

59
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -3,12 +3,17 @@
using System;
using System.Collections.Specialized;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
public class VirtualizingStackPanel : StackPanel, IVirtualizingPanel
{
private Size _availableSpace;
private double _takenSpace;
private int _canBeRemoved;
private double _averageItemSize;
@ -20,15 +25,14 @@ namespace Avalonia.Controls
get
{
return Orientation == Orientation.Horizontal ?
_takenSpace >= AvailableSpace.Width :
_takenSpace >= AvailableSpace.Height;
_takenSpace >= _availableSpace.Width :
_takenSpace >= _availableSpace.Height;
}
}
IVirtualizingController IVirtualizingPanel.Controller { get; set; }
int IVirtualizingPanel.OverflowCount => _canBeRemoved;
Orientation IVirtualizingPanel.ScrollDirection => Orientation;
double IVirtualizingPanel.AverageItemSize => _averageItemSize;
double IVirtualizingPanel.PixelOverflow
@ -36,8 +40,8 @@ namespace Avalonia.Controls
get
{
var bounds = Orientation == Orientation.Horizontal ?
Bounds.Width : Bounds.Height;
return Math.Max(0, (_takenSpace - _pixelOffset) - bounds);
_availableSpace.Width : _availableSpace.Height;
return Math.Max(0, _takenSpace - bounds);
}
}
@ -55,16 +59,29 @@ namespace Avalonia.Controls
}
}
// TODO: We need to put a reasonable limit on this, probably based on the max window size.
private Size AvailableSpace => ((ILayoutable)this).PreviousMeasure ?? Bounds.Size;
private IVirtualizingController Controller => ((IVirtualizingPanel)this).Controller;
protected override Size MeasureOverride(Size availableSize)
{
if (availableSize != ((ILayoutable)this).PreviousMeasure)
{
_availableSpace = availableSize;
Controller?.UpdateControls();
}
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
_availableSpace = finalSize;
_canBeRemoved = 0;
_takenSpace = 0;
_averageItemSize = 0;
_averageCount = 0;
var result = base.ArrangeOverride(finalSize);
_takenSpace += _pixelOffset;
Controller?.UpdateControls();
return result;
}
@ -92,6 +109,21 @@ namespace Avalonia.Controls
}
}
protected override IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{
var logicalScrollable = Parent as ILogicalScrollable;
var fromControl = from as IControl;
if (logicalScrollable?.IsLogicalScrollEnabled == true && fromControl != null)
{
return logicalScrollable.GetControlInDirection(direction, fromControl);
}
else
{
return base.GetControlInDirection(direction, from);
}
}
internal override void ArrangeChild(
IControl child,
Rect rect,
@ -103,7 +135,7 @@ namespace Avalonia.Controls
rect = new Rect(rect.X, rect.Y - _pixelOffset, rect.Width, rect.Height);
child.Arrange(rect);
if (rect.Y >= AvailableSpace.Height)
if (rect.Y >= _availableSpace.Height)
{
++_canBeRemoved;
}
@ -120,7 +152,7 @@ namespace Avalonia.Controls
rect = new Rect(rect.X - _pixelOffset, rect.Y, rect.Width, rect.Height);
child.Arrange(rect);
if (rect.X >= AvailableSpace.Width)
if (rect.X >= _availableSpace.Width)
{
++_canBeRemoved;
}
@ -139,7 +171,7 @@ namespace Avalonia.Controls
var bounds = Bounds;
var gap = Gap;
child.Measure(AvailableSpace);
child.Measure(_availableSpace);
++_averageCount;
if (Orientation == Orientation.Vertical)
@ -173,6 +205,11 @@ namespace Avalonia.Controls
_takenSpace -= width + gap;
RemoveFromAverageItemSize(width);
}
if (_canBeRemoved > 0)
{
--_canBeRemoved;
}
}
private void AddToAverageItemSize(double value)

11
src/Avalonia.Controls/Window.cs

@ -89,7 +89,16 @@ namespace Avalonia.Controls
/// Initializes a new instance of the <see cref="Window"/> class.
/// </summary>
public Window()
: base(PlatformManager.CreateWindow())
: this(PlatformManager.CreateWindow())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Window"/> class.
/// </summary>
/// <param name="impl">The window implementation.</param>
public Window(IWindowImpl impl)
: base(impl)
{
_maxPlatformClientSize = this.PlatformImpl.MaxClientSize;
}

18
src/Avalonia.Controls/WrapPanel.cs

@ -48,35 +48,35 @@ namespace Avalonia.Controls
/// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns>
IInputElement INavigableContainer.GetControl(FocusNavigationDirection direction, IInputElement from)
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from)
{
var horiz = Orientation == Orientation.Horizontal;
int index = Children.IndexOf((IControl)from);
switch (direction)
{
case FocusNavigationDirection.First:
case NavigationDirection.First:
index = 0;
break;
case FocusNavigationDirection.Last:
case NavigationDirection.Last:
index = Children.Count - 1;
break;
case FocusNavigationDirection.Next:
case NavigationDirection.Next:
++index;
break;
case FocusNavigationDirection.Previous:
case NavigationDirection.Previous:
--index;
break;
case FocusNavigationDirection.Left:
case NavigationDirection.Left:
index = horiz ? index - 1 : -1;
break;
case FocusNavigationDirection.Right:
case NavigationDirection.Right:
index = horiz ? index + 1 : -1;
break;
case FocusNavigationDirection.Up:
case NavigationDirection.Up:
index = horiz ? -1 : index - 1;
break;
case FocusNavigationDirection.Down:
case NavigationDirection.Down:
index = horiz ? -1 : index + 1;
break;
}

52
src/Avalonia.DesignerSupport/DesignerApi.cs

@ -7,33 +7,42 @@ using System.Threading.Tasks;
namespace Avalonia.DesignerSupport
{
class DesignerApi
class DesignerApiDictionary
{
private readonly Dictionary<string, object> _inner;
public Dictionary<string, object> Dictionary { get; set; }
public DesignerApi(Dictionary<string, object> inner)
public DesignerApiDictionary(Dictionary<string, object> dictionary)
{
_inner = inner;
Dictionary = dictionary;
}
object Get([CallerMemberName] string name = null)
protected object Get([CallerMemberName] string name = null)
{
object rv;
_inner.TryGetValue(name, out rv);
Dictionary.TryGetValue(name, out rv);
return rv;
}
void Set(object value, [CallerMemberName] string name = null)
protected void Set(object value, [CallerMemberName] string name = null)
{
_inner[name] = value;
Dictionary[name] = value;
}
}
class DesignerApi : DesignerApiDictionary
{
public Action<string> UpdateXaml
{
get { return (Action<string>) Get(); }
set {Set(value); }
}
public Action<Dictionary<string, object>> UpdateXaml2
{
get { return (Action<Dictionary<string, object>>)Get(); }
set { Set(value); }
}
public Action OnResize
{
get { return (Action) Get(); }
@ -52,5 +61,32 @@ namespace Avalonia.DesignerSupport
get { return (Action<double>) Get(); }
}
public DesignerApi(Dictionary<string, object> dictionary) : base(dictionary)
{
}
}
class DesignerApiXamlFileInfo : DesignerApiDictionary
{
public string Xaml
{
get { return (string)Get(); }
set { Set(value); }
}
public string AssemblyPath
{
get { return (string) Get(); }
set { Set(value); }
}
public DesignerApiXamlFileInfo(Dictionary<string, object> dictionary) : base(dictionary)
{
}
public DesignerApiXamlFileInfo(): base(new Dictionary<string, object>())
{
}
}
}

64
src/Avalonia.DesignerSupport/DesignerAssist.cs

@ -36,7 +36,7 @@ namespace Avalonia.DesignerSupport
public static void Init(Dictionary<string, object> shared)
{
Design.IsDesignMode = true;
Api = new DesignerApi(shared) {UpdateXaml = UpdateXaml, SetScalingFactor = SetScalingFactor};
Api = new DesignerApi(shared) {UpdateXaml = UpdateXaml, UpdateXaml2 = UpdateXaml2, SetScalingFactor = SetScalingFactor};
var plat = (IPclPlatformWrapper) Activator.CreateInstance(Assembly.Load(new AssemblyName("Avalonia.Win32"))
.DefinedTypes.First(typeof (IPclPlatformWrapper).GetTypeInfo().IsAssignableFrom).AsType());
@ -58,10 +58,9 @@ namespace Avalonia.DesignerSupport
//Ignore, Assembly.DefinedTypes threw an exception, we can't do anything about that
}
}
AppBuilder.Configure(app == null ? new DesignerApp() : (Application) Activator.CreateInstance(app.AsType()))
.WithWindowingSubsystem(Application.InitializeWin32Subsystem)
.WithRenderingSubsystem(() => { })
.UseWindowingSubsystem("Avalonia.Win32")
.UseRenderingSubsystem("Avalonia.Direct2D1")
.SetupWithoutStarting();
}
@ -74,22 +73,65 @@ namespace Avalonia.DesignerSupport
static Window s_currentWindow;
private static void UpdateXaml(string xaml)
private static void UpdateXaml(string xaml) => UpdateXaml2(new DesignerApiXamlFileInfo
{
Xaml = xaml
}.Dictionary);
private static void UpdateXaml2(Dictionary<string, object> dic)
{
var xamlInfo = new DesignerApiXamlFileInfo(dic);
Window window;
Control original;
Control control;
using (PlatformManager.DesignerMode())
{
var loader = new AvaloniaXamlLoader();
var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml));
var stream = new MemoryStream(Encoding.UTF8.GetBytes(xamlInfo.Xaml));
original = (Control)loader.Load(stream);
window = original as Window;
Uri baseUri = null;
if (xamlInfo.AssemblyPath != null)
{
//Fabricate fake Uri
baseUri =
new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(xamlInfo.AssemblyPath));
}
var loaded = loader.Load(stream, null, baseUri);
var styles = loaded as Styles;
if (styles != null)
{
var substitute = Design.GetPreviewWith(styles) ??
styles.Select(Design.GetPreviewWith).FirstOrDefault(s => s != null);
if (substitute != null)
{
substitute.Styles.AddRange(styles);
control = substitute;
}
else
control = new StackPanel
{
Children =
{
new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"},
new TextBlock {Text = "<Design.PreviewWith>"},
new TextBlock {Text = " <Border Padding=20><!-- YOUR CONTROL FOR PREVIEW HERE--></Border>"},
new TextBlock {Text = "<Design.PreviewWith>"},
new TextBlock {Text = "before setters in your first Style"}
}
};
}
if (loaded is Application)
control = new TextBlock {Text = "Application can't be previewed in design view"};
else
control = (Control) loaded;
window = control as Window;
if (window == null)
{
window = new Window() {Content = original};
window = new Window() {Content = (Control)control};
}
if (!window.IsSet(Window.SizeToContentProperty))
@ -99,7 +141,7 @@ namespace Avalonia.DesignerSupport
s_currentWindow?.Close();
s_currentWindow = window;
window.Show();
Design.ApplyDesignerProperties(window, original);
Design.ApplyDesignerProperties(window, control);
Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle);
Api.OnResize?.Invoke();
}

4
src/Avalonia.Input/Avalonia.Input.csproj

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@ -79,7 +79,7 @@
<Compile Include="IMainMenu.cs" />
<Compile Include="IAccessKeyHandler.cs" />
<Compile Include="FocusManager.cs" />
<Compile Include="FocusNavigationDirection.cs" />
<Compile Include="NavigationDirection.cs" />
<Compile Include="ICloseable.cs" />
<Compile Include="IFocusManager.cs" />
<Compile Include="IFocusScope.cs" />

2
src/Avalonia.Input/FocusManager.cs

@ -79,7 +79,7 @@ namespace Avalonia.Input
{
IInputElement element;
if (_focusScopes.TryGetValue(scope, out element))
if (_focusScopes.TryGetValue(scope, out element) && element != null)
{
Focus(element, method);
return;

2
src/Avalonia.Input/IKeyboardNavigationHandler.cs

@ -25,7 +25,7 @@ namespace Avalonia.Input
/// <param name="modifiers">Any input modifiers active at the time of focus.</param>
void Move(
IInputElement element,
FocusNavigationDirection direction,
NavigationDirection direction,
InputModifiers modifiers = InputModifiers.None);
}
}

2
src/Avalonia.Input/INavigableContainer.cs

@ -14,6 +14,6 @@ namespace Avalonia.Input
/// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns>
IInputElement GetControl(FocusNavigationDirection direction, IInputElement from);
IInputElement GetControl(NavigationDirection direction, IInputElement from);
}
}

16
src/Avalonia.Input/InputExtensions.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.VisualTree;
using System;
using System.Collections.Generic;
using System.Linq;
@ -24,14 +25,13 @@ namespace Avalonia.Input
{
Contract.Requires<ArgumentNullException>(element != null);
if (element.Bounds.Contains(p) &&
element.IsVisible &&
if (element.IsVisible &&
element.IsHitTestVisible &&
element.IsEnabledCore)
{
p -= element.Bounds.Position;
bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)element).Contains(p);
if (element.VisualChildren.Any())
if ((containsPoint || !element.ClipToBounds) && element.VisualChildren.Any())
{
foreach (var child in ZSort(element.VisualChildren.OfType<IInputElement>()))
{
@ -42,7 +42,10 @@ namespace Avalonia.Input
}
}
yield return element;
if (containsPoint)
{
yield return element;
}
}
}
@ -68,7 +71,6 @@ namespace Avalonia.Input
})
.OrderBy(x => x, null)
.Select(x => x.Element);
}
private class ZOrderElement : IComparable<ZOrderElement>
@ -92,4 +94,4 @@ namespace Avalonia.Input
}
}
}
}
}

34
src/Avalonia.Input/KeyboardNavigationHandler.cs

@ -48,11 +48,11 @@ namespace Avalonia.Input
/// </returns>
public static IInputElement GetNext(
IInputElement element,
FocusNavigationDirection direction)
NavigationDirection direction)
{
Contract.Requires<ArgumentNullException>(element != null);
if (direction == FocusNavigationDirection.Next || direction == FocusNavigationDirection.Previous)
if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
{
return TabNavigation.GetNextInTabOrder(element, direction);
}
@ -70,7 +70,7 @@ namespace Avalonia.Input
/// <param name="modifiers">Any input modifiers active at the time of focus.</param>
public void Move(
IInputElement element,
FocusNavigationDirection direction,
NavigationDirection direction,
InputModifiers modifiers = InputModifiers.None)
{
Contract.Requires<ArgumentNullException>(element != null);
@ -79,8 +79,8 @@ namespace Avalonia.Input
if (next != null)
{
var method = direction == FocusNavigationDirection.Next ||
direction == FocusNavigationDirection.Previous ?
var method = direction == NavigationDirection.Next ||
direction == NavigationDirection.Previous ?
NavigationMethod.Tab : NavigationMethod.Directional;
FocusManager.Instance.Focus(next, method, modifiers);
}
@ -97,25 +97,37 @@ namespace Avalonia.Input
if (current != null)
{
FocusNavigationDirection? direction = null;
NavigationDirection? direction = null;
switch (e.Key)
{
case Key.Tab:
direction = (e.Modifiers & InputModifiers.Shift) == 0 ?
FocusNavigationDirection.Next : FocusNavigationDirection.Previous;
NavigationDirection.Next : NavigationDirection.Previous;
break;
case Key.Up:
direction = FocusNavigationDirection.Up;
direction = NavigationDirection.Up;
break;
case Key.Down:
direction = FocusNavigationDirection.Down;
direction = NavigationDirection.Down;
break;
case Key.Left:
direction = FocusNavigationDirection.Left;
direction = NavigationDirection.Left;
break;
case Key.Right:
direction = FocusNavigationDirection.Right;
direction = NavigationDirection.Right;
break;
case Key.PageUp:
direction = NavigationDirection.PageUp;
break;
case Key.PageDown:
direction = NavigationDirection.PageDown;
break;
case Key.Home:
direction = NavigationDirection.First;
break;
case Key.End:
direction = NavigationDirection.Last;
break;
}

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

@ -24,18 +24,17 @@ namespace Avalonia.Input.Navigation
/// </returns>
public static IInputElement GetNext(
IInputElement element,
FocusNavigationDirection direction)
NavigationDirection direction)
{
Contract.Requires<ArgumentNullException>(element != null);
Contract.Requires<ArgumentException>(
direction != FocusNavigationDirection.Next &&
direction != FocusNavigationDirection.Previous);
direction != NavigationDirection.Next &&
direction != NavigationDirection.Previous);
var container = element.GetVisualParent<IInputElement>();
if (container != null)
{
var isForward = IsForward(direction);
var mode = KeyboardNavigation.GetDirectionalNavigation((InputElement)container);
switch (mode)
@ -63,12 +62,12 @@ namespace Avalonia.Input.Navigation
/// </summary>
/// <param name="direction">The direction.</param>
/// <returns>True if the direction is forward.</returns>
private static bool IsForward(FocusNavigationDirection direction)
private static bool IsForward(NavigationDirection direction)
{
return direction == FocusNavigationDirection.Next ||
direction == FocusNavigationDirection.Last ||
direction == FocusNavigationDirection.Right ||
direction == FocusNavigationDirection.Down;
return direction == NavigationDirection.Next ||
direction == NavigationDirection.Last ||
direction == NavigationDirection.Right ||
direction == NavigationDirection.Down;
}
/// <summary>
@ -77,7 +76,7 @@ namespace Avalonia.Input.Navigation
/// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param>
/// <returns>The element or null if not found.##</returns>
private static IInputElement GetFocusableDescendent(IInputElement container, FocusNavigationDirection direction)
private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction)
{
return IsForward(direction) ?
GetFocusableDescendents(container).FirstOrDefault() :
@ -121,9 +120,9 @@ namespace Avalonia.Input.Navigation
private static IInputElement GetNextInContainer(
IInputElement element,
IInputElement container,
FocusNavigationDirection direction)
NavigationDirection direction)
{
if (direction == FocusNavigationDirection.Down)
if (direction == NavigationDirection.Down)
{
var descendent = GetFocusableDescendents(element).FirstOrDefault();
@ -156,7 +155,7 @@ namespace Avalonia.Input.Navigation
element = null;
}
if (element != null && direction == FocusNavigationDirection.Up)
if (element != null && direction == NavigationDirection.Up)
{
var descendent = GetFocusableDescendents(element).LastOrDefault();
@ -180,7 +179,7 @@ namespace Avalonia.Input.Navigation
/// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer(
IInputElement container,
FocusNavigationDirection direction)
NavigationDirection direction)
{
var parent = container.GetVisualParent<IInputElement>();
var isForward = IsForward(direction);

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

@ -24,12 +24,12 @@ namespace Avalonia.Input.Navigation
/// </returns>
public static IInputElement GetNextInTabOrder(
IInputElement element,
FocusNavigationDirection direction)
NavigationDirection direction)
{
Contract.Requires<ArgumentNullException>(element != null);
Contract.Requires<ArgumentException>(
direction == FocusNavigationDirection.Next ||
direction == FocusNavigationDirection.Previous);
direction == NavigationDirection.Next ||
direction == NavigationDirection.Previous);
var container = element.GetVisualParent<IInputElement>();
@ -63,9 +63,9 @@ namespace Avalonia.Input.Navigation
/// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param>
/// <returns>The element or null if not found.##</returns>
private static IInputElement GetFocusableDescendent(IInputElement container, FocusNavigationDirection direction)
private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction)
{
return direction == FocusNavigationDirection.Next ?
return direction == NavigationDirection.Next ?
GetFocusableDescendents(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault();
}
@ -128,9 +128,9 @@ namespace Avalonia.Input.Navigation
private static IInputElement GetNextInContainer(
IInputElement element,
IInputElement container,
FocusNavigationDirection direction)
NavigationDirection direction)
{
if (direction == FocusNavigationDirection.Next)
if (direction == NavigationDirection.Next)
{
var descendent = GetFocusableDescendents(element).FirstOrDefault();
@ -165,7 +165,7 @@ namespace Avalonia.Input.Navigation
element = null;
}
if (element != null && direction == FocusNavigationDirection.Previous)
if (element != null && direction == NavigationDirection.Previous)
{
var descendent = GetFocusableDescendents(element).LastOrDefault();
@ -189,14 +189,14 @@ namespace Avalonia.Input.Navigation
/// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer(
IInputElement container,
FocusNavigationDirection direction)
NavigationDirection direction)
{
var parent = container.GetVisualParent<IInputElement>();
IInputElement next = null;
if (parent != null)
{
if (direction == FocusNavigationDirection.Previous && parent.CanFocus())
if (direction == NavigationDirection.Previous && parent.CanFocus())
{
return parent;
}
@ -204,7 +204,7 @@ namespace Avalonia.Input.Navigation
var siblings = parent.GetVisualChildren()
.OfType<IInputElement>()
.Where(FocusExtensions.CanFocusDescendents);
var sibling = direction == FocusNavigationDirection.Next ?
var sibling = direction == NavigationDirection.Next ?
siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() :
siblings.TakeWhile(x => x != container).LastOrDefault();
@ -216,7 +216,7 @@ namespace Avalonia.Input.Navigation
}
else
{
next = direction == FocusNavigationDirection.Next ?
next = direction == NavigationDirection.Next ?
GetFocusableDescendents(sibling).FirstOrDefault() :
GetFocusableDescendents(sibling).LastOrDefault();
}
@ -229,7 +229,7 @@ namespace Avalonia.Input.Navigation
}
else
{
next = direction == FocusNavigationDirection.Next ?
next = direction == NavigationDirection.Next ?
GetFocusableDescendents(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault();
}

14
src/Avalonia.Input/FocusNavigationDirection.cs → src/Avalonia.Input/NavigationDirection.cs

@ -4,9 +4,9 @@
namespace Avalonia.Input
{
/// <summary>
/// Describes how focus should be moved.
/// Describes how focus should be moved by directional or tab keys.
/// </summary>
public enum FocusNavigationDirection
public enum NavigationDirection
{
/// <summary>
/// Move the focus to the next control in the tab order.
@ -47,5 +47,15 @@ namespace Avalonia.Input
/// Move the focus down.
/// </summary>
Down,
/// <summary>
/// Move the focus up a page.
/// </summary>
PageUp,
/// <summary>
/// Move the focus down a page.
/// </summary>
PageDown,
}
}

35
src/Avalonia.Layout/LayoutManager.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Logging;
using Avalonia.Threading;
@ -13,8 +14,8 @@ namespace Avalonia.Layout
/// </summary>
public class LayoutManager : ILayoutManager
{
private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>();
private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
private readonly HashSet<ILayoutable> _toMeasure = new HashSet<ILayoutable>();
private readonly HashSet<ILayoutable> _toArrange = new HashSet<ILayoutable>();
private bool _queued;
private bool _running;
@ -29,8 +30,8 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess();
_toMeasure.Enqueue(control);
_toArrange.Enqueue(control);
_toMeasure.Add(control);
_toArrange.Add(control);
QueueLayoutPass();
}
@ -40,7 +41,7 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess();
_toArrange.Enqueue(control);
_toArrange.Add(control);
QueueLayoutPass();
}
@ -107,7 +108,7 @@ namespace Avalonia.Layout
{
while (_toMeasure.Count > 0)
{
var next = _toMeasure.Dequeue();
var next = _toMeasure.First();
Measure(next);
}
}
@ -116,7 +117,7 @@ namespace Avalonia.Layout
{
while (_toArrange.Count > 0 && _toMeasure.Count == 0)
{
var next = _toArrange.Dequeue();
var next = _toArrange.First();
Arrange(next);
}
}
@ -124,29 +125,45 @@ namespace Avalonia.Layout
private void Measure(ILayoutable control)
{
var root = control as ILayoutRoot;
var parent = control.VisualParent as ILayoutable;
if (root != null)
{
root.Measure(root.MaxClientSize);
}
else if (control.PreviousMeasure.HasValue)
else if (parent != null)
{
Measure(parent);
}
if (!control.IsMeasureValid)
{
control.Measure(control.PreviousMeasure.Value);
}
_toMeasure.Remove(control);
}
private void Arrange(ILayoutable control)
{
var root = control as ILayoutRoot;
var parent = control.VisualParent as ILayoutable;
if (root != null)
{
root.Arrange(new Rect(root.DesiredSize));
}
else if (control.PreviousArrange.HasValue)
else if (parent != null)
{
Arrange(parent);
}
if (control.PreviousArrange.HasValue)
{
control.Arrange(control.PreviousArrange.Value);
}
_toArrange.Remove(control);
}
private void QueueLayoutPass()

11
src/Avalonia.Layout/Layoutable.cs

@ -603,6 +603,17 @@ namespace Avalonia.Layout
return finalSize;
}
/// <inheritdoc/>
protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{
foreach (ILayoutable i in this.GetSelfAndVisualDescendents())
{
i.InvalidateMeasure();
}
base.OnVisualParentChanged(oldParent, newParent);
}
/// <summary>
/// Calls <see cref="InvalidateMeasure"/> on the control on which a property changed.
/// </summary>

39
src/Avalonia.SceneGraph/Media/DrawingContext.cs

@ -44,6 +44,8 @@ namespace Avalonia.Media
private Matrix _currentTransform = Matrix.Identity;
private Matrix _currentContainerTransform = Matrix.Identity;
/// <summary>
/// Gets the current transform of the drawing context.
/// </summary>
@ -57,7 +59,7 @@ namespace Avalonia.Media
}
}
private Matrix _currentContainerTransform = Matrix.Identity;
internal Matrix CurrentContainerTransform => _currentContainerTransform;
/// <summary>
/// Draws a bitmap image.
@ -125,7 +127,9 @@ namespace Avalonia.Media
Matrix,
Opacity,
Clip,
MatrixContainer
MatrixContainer,
GeometryClip,
OpacityMask
}
public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix))
@ -147,10 +151,14 @@ namespace Avalonia.Media
_context._states.Pop();
if (_type == PushedStateType.Matrix)
_context.CurrentTransform = _matrix;
else if(_type == PushedStateType.Clip)
else if (_type == PushedStateType.Clip)
_context._impl.PopClip();
else if(_type == PushedStateType.Opacity)
else if (_type == PushedStateType.Opacity)
_context._impl.PopOpacity();
else if (_type == PushedStateType.GeometryClip)
_context._impl.PopGeometryClip();
else if (_type == PushedStateType.OpacityMask)
_context._impl.PopOpacityMask();
else if (_type == PushedStateType.MatrixContainer)
{
var cont = _context._transformContainers.Pop();
@ -172,6 +180,18 @@ namespace Avalonia.Media
return new PushedState(this, PushedState.PushedStateType.Clip);
}
/// <summary>
/// Pushes a clip geometry.
/// </summary>
/// <param name="clip">The clip geometry.</param>
/// <returns>A disposable used to undo the clip geometry.</returns>
public PushedState PushGeometryClip(Geometry clip)
{
Contract.Requires<ArgumentNullException>(clip != null);
_impl.PushGeometryClip(clip);
return new PushedState(this, PushedState.PushedStateType.GeometryClip);
}
/// <summary>
/// Pushes an opacity value.
/// </summary>
@ -184,6 +204,17 @@ namespace Avalonia.Media
return new PushedState(this, PushedState.PushedStateType.Opacity);
}
/// <summary>
/// Pushes an opacity mask.
/// </summary>
/// <param name="mask">The opacity mask.</param>
/// <returns>A disposable to undo the opacity mask.</returns>
public PushedState PushOpacityMask(IBrush mask, Rect bounds)
{
_impl.PushOpacityMask(mask, bounds);
return new PushedState(this, PushedState.PushedStateType.OpacityMask);
}
/// <summary>
/// Pushes a matrix transformation.
/// </summary>

10
src/Avalonia.SceneGraph/Media/Geometry.cs

@ -66,5 +66,15 @@ namespace Avalonia.Media
{
return PlatformImpl.GetRenderBounds(strokeThickness);
}
/// <summary>
/// Indicates whether the geometry contains the specified point.
/// </summary>
/// <param name="point">The point.</param>
/// <returns><c>true</c> if the geometry contains the point; otherwise, <c>false</c>.</returns>
public bool FillContains(Point point)
{
return PlatformImpl.FillContains(point);
}
}
}

14
src/Avalonia.SceneGraph/Media/IDrawingContext.cs

@ -69,7 +69,6 @@ namespace Avalonia.Media
/// Pushes a clip rectange.
/// </summary>
/// <param name="clip">The clip rectangle.</param>
/// <returns>A disposable used to undo the clip rectangle.</returns>
void PushClip(Rect clip);
void PopClip();
@ -78,9 +77,20 @@ namespace Avalonia.Media
/// Pushes an opacity value.
/// </summary>
/// <param name="opacity">The opacity.</param>
/// <returns>A disposable used to undo the opacity.</returns>
void PushOpacity(double opacity);
void PopOpacity();
void PushOpacityMask(IBrush mask, Rect bounds);
void PopOpacityMask();
/// <summary>
/// Pushes a clip geometry.
/// </summary>
/// <param name="clip">The clip geometry.</param>
void PushGeometryClip(Geometry clip);
void PopGeometryClip();
}
}

3
src/Avalonia.SceneGraph/Media/Imaging/Bitmap.cs

@ -55,7 +55,8 @@ namespace Avalonia.Media.Imaging
/// </summary>
public IBitmapImpl PlatformImpl
{
get; }
get;
}
/// <summary>
/// Saves the bitmap to a file.

39
src/Avalonia.SceneGraph/Media/PathMarkupParser.cs

@ -34,6 +34,12 @@ namespace Avalonia.Media
{ 'z', Command.Close },
};
private static readonly Dictionary<char, FillRule> FillRules = new Dictionary<char, FillRule>
{
{'0', FillRule.EvenOdd },
{'1', FillRule.NonZero }
};
private StreamGeometry _geometry;
private readonly StreamGeometryContext _context;
@ -90,8 +96,7 @@ namespace Avalonia.Media
switch (command)
{
case Command.FillRule:
// TODO: Implement.
reader.Read();
_context.SetFillRule(ReadFillRule(reader));
break;
case Command.Move:
@ -226,6 +231,24 @@ namespace Avalonia.Media
}
}
private static FillRule ReadFillRule(StringReader reader)
{
int i = reader.Read();
if (i == -1)
{
throw new InvalidDataException("Invalid fill rule");
}
char c = (char)i;
FillRule rule;
if (!FillRules.TryGetValue(c, out rule))
{
throw new InvalidDataException("Invalid fill rule");
}
return rule;
}
private static double ReadDouble(StringReader reader)
{
ReadWhitespace(reader);
@ -248,8 +271,16 @@ namespace Avalonia.Media
{
b.Append(c);
reader.Read();
readSign = c == '+' || c == '-';
readPoint = c == '.';
if (!readSign)
{
readSign = c == '+' || c == '-';
}
if (!readPoint)
{
readPoint = c == '.';
}
if (c == 'E')
{

7
src/Avalonia.SceneGraph/Platform/IGeometryImpl.cs

@ -24,5 +24,12 @@ namespace Avalonia.Platform
/// <param name="strokeThickness">The stroke thickness.</param>
/// <returns>The bounding rectangle.</returns>
Rect GetRenderBounds(double strokeThickness);
/// <summary>
/// Indicates whether the geometry contains the specified point.
/// </summary>
/// <param name="point">The point.</param>
/// <returns><c>true</c> if the geometry contains the point; otherwise, <c>false</c>.</returns>
bool FillContains(Point point);
}
}

16
src/Avalonia.SceneGraph/Rect.cs

@ -224,14 +224,24 @@ namespace Avalonia
}
/// <summary>
/// Determines whether a points in in the bounds of the rectangle.
/// Determines whether a point in in the bounds of the rectangle.
/// </summary>
/// <param name="p">The point.</param>
/// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns>
public bool Contains(Point p)
{
return p.X >= _x && p.X < _x + _width &&
p.Y >= _y && p.Y < _y + _height;
return p.X >= _x && p.X <= _x + _width &&
p.Y >= _y && p.Y <= _y + _height;
}
/// <summary>
/// Determines whether the rectangle fully contains another rectangle.
/// </summary>
/// <param name="r">The rectangle.</param>
/// <returns>true if the rectangle is fully contained; otherwise false.</returns>
public bool Contains(Rect r)
{
return Contains(r.TopLeft) && Contains(r.BottomRight);
}
/// <summary>

12
src/Avalonia.SceneGraph/Rendering/RendererMixin.cs

@ -104,7 +104,7 @@ namespace Avalonia.Rendering
if (visual.RenderTransform != null)
{
var origin = visual.TransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
}
@ -119,9 +119,17 @@ namespace Avalonia.Rendering
using (context.PushPostTransform(m))
using (context.PushOpacity(opacity))
using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState))
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState))
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState))
using (context.PushTransformContainer())
{
visual.Render(context);
var transformed =
new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform);
if (visual is Visual)
{
BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
}
var lst = GetSortedVisualList(visual.VisualChildren);
@ -164,7 +172,7 @@ namespace Avalonia.Rendering
}
else
{
var origin = visual.TransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(visual.Bounds.Position + origin);
var m = (-offset) * visual.RenderTransform.Value * (offset);
return visual.Bounds.TransformToAABB(m);

55
src/Avalonia.SceneGraph/Visual.cs

@ -39,6 +39,12 @@ namespace Avalonia
public static readonly StyledProperty<bool> ClipToBoundsProperty =
AvaloniaProperty.Register<Visual, bool>(nameof(ClipToBounds));
/// <summary>
/// Defines the <see cref="Clip"/> property.
/// </summary>
public static readonly StyledProperty<Geometry> ClipProperty =
AvaloniaProperty.Register<Visual, Geometry>(nameof(Clip));
/// <summary>
/// Defines the <see cref="IsVisibleProperty"/> property.
/// </summary>
@ -51,6 +57,12 @@ namespace Avalonia
public static readonly StyledProperty<double> OpacityProperty =
AvaloniaProperty.Register<Visual, double>(nameof(Opacity), 1);
/// <summary>
/// Defines the <see cref="OpacityMask"/> property.
/// </summary>
public static readonly StyledProperty<IBrush> OpacityMaskProperty =
AvaloniaProperty.Register<Visual, IBrush>(nameof(OpacityMask));
/// <summary>
/// Defines the <see cref="RenderTransform"/> property.
/// </summary>
@ -58,10 +70,10 @@ namespace Avalonia
AvaloniaProperty.Register<Visual, Transform>(nameof(RenderTransform));
/// <summary>
/// Defines the <see cref="TransformOrigin"/> property.
/// Defines the <see cref="RenderTransformOrigin"/> property.
/// </summary>
public static readonly StyledProperty<RelativePoint> TransformOriginProperty =
AvaloniaProperty.Register<Visual, RelativePoint>(nameof(TransformOrigin), defaultValue: RelativePoint.Center);
public static readonly StyledProperty<RelativePoint> RenderTransformOriginProperty =
AvaloniaProperty.Register<Visual, RelativePoint>(nameof(RenderTransformOrigin), defaultValue: RelativePoint.Center);
/// <summary>
/// Defines the <see cref="IVisual.VisualParent"/> property.
@ -127,6 +139,15 @@ namespace Avalonia
set { SetValue(ClipToBoundsProperty, value); }
}
/// <summary>
/// Gets or sets the geometry clip for this visual.
/// </summary>
public Geometry Clip
{
get { return GetValue(ClipProperty); }
set { SetValue(ClipProperty, value); }
}
/// <summary>
/// Gets a value indicating whether this scene graph node and all its parents are visible.
/// </summary>
@ -153,6 +174,16 @@ namespace Avalonia
set { SetValue(OpacityProperty, value); }
}
/// <summary>
/// Gets the opacity mask of the scene graph node.
/// </summary>
public IBrush OpacityMask
{
get { return GetValue(OpacityMaskProperty); }
set { SetValue(OpacityMaskProperty, value); }
}
/// <summary>
/// Gets the render transform of the scene graph node.
/// </summary>
@ -165,10 +196,10 @@ namespace Avalonia
/// <summary>
/// Gets the transform origin of the scene graph node.
/// </summary>
public RelativePoint TransformOrigin
public RelativePoint RenderTransformOrigin
{
get { return GetValue(TransformOriginProperty); }
set { SetValue(TransformOriginProperty, value); }
get { return GetValue(RenderTransformOriginProperty); }
set { SetValue(RenderTransformOriginProperty, value); }
}
/// <summary>
@ -353,6 +384,16 @@ namespace Avalonia
DetachedFromVisualTree?.Invoke(this, e);
}
/// <summary>
/// Called when the control's visual parent changes.
/// </summary>
/// <param name="oldParent">The old visual parent.</param>
/// <param name="newParent">The new visual parent.</param>
protected virtual void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{
RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue);
}
/// <summary>
/// Called when a property changes that should invalidate the visual.
/// </summary>
@ -468,7 +509,7 @@ namespace Avalonia
OnAttachedToVisualTreeCore(e);
}
RaisePropertyChanged(VisualParentProperty, old, value, BindingPriority.LocalValue);
OnVisualParentChanged(old, value);
}
/// <summary>

41
src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Media;
namespace Avalonia.VisualTree
{
@ -16,6 +17,9 @@ namespace Avalonia.VisualTree
/// </remarks>
public class BoundsTracker
{
private static AttachedProperty<TransformedBounds> TransformedBoundsProperty =
AvaloniaProperty.RegisterAttached<BoundsTracker, Visual, TransformedBounds>("TransformedBounds");
/// <summary>
/// Starts tracking the specified visual.
/// </summary>
@ -23,42 +27,19 @@ namespace Avalonia.VisualTree
/// <returns>An observable that returns the tracked bounds.</returns>
public IObservable<TransformedBounds> Track(Visual visual)
{
return Track(visual, (Visual)visual.GetVisualRoot());
return visual.GetObservable(TransformedBoundsProperty);
}
/// <summary>
/// Starts tracking the specified visual relative to another control.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="relativeTo">The control that the tracking should be relative to.</param>
/// <returns>An observable that returns the tracked bounds.</returns>
public IObservable<TransformedBounds> Track(Visual visual, Visual relativeTo)
internal static void SetTransformedBounds(Visual visual, TransformedBounds bounds)
{
var visuals = visual.GetSelfAndVisualAncestors()
.TakeWhile(x => x != relativeTo)
.Reverse();
var boundsSubscriptions = new List<IObservable<Rect>>();
foreach (var v in visuals.Cast<Visual>())
{
boundsSubscriptions.Add(v.GetObservable(Visual.BoundsProperty));
}
var bounds = boundsSubscriptions.CombineLatest().Select(ExtractBounds);
// TODO: Track transform and clip rectangle.
return bounds.Select(x => new TransformedBounds(x, new Rect(), Matrix.Identity));
visual.SetValue(TransformedBoundsProperty, bounds);
}
/// <summary>
/// Sums a collection of rectangles.
/// Gets the transformed bounds of the visual.
/// </summary>
/// <param name="rects">The collection of rectangles.</param>
/// <returns>The summed rectangle.</returns>
private static Rect ExtractBounds(IList<Rect> rects)
{
var position = rects.Select(x => x.Position).Aggregate((a, b) => a + b);
return new Rect(position, rects.Last().Size);
}
/// <param name="visual">The visual.</param>
/// <returns>The transformed bounds.</returns>
public static TransformedBounds GetTransformedBounds(Visual visual) => visual.GetValue(TransformedBoundsProperty);
}
}

14
src/Avalonia.SceneGraph/VisualTree/IVisual.cs

@ -41,6 +41,11 @@ namespace Avalonia.VisualTree
/// </summary>
bool ClipToBounds { get; set; }
/// <summary>
/// Gets or sets the geometry clip for this visual.
/// </summary>
Geometry Clip { get; set; }
/// <summary>
/// Gets a value indicating whether this scene graph node is attached to a visual root.
/// </summary>
@ -61,15 +66,20 @@ namespace Avalonia.VisualTree
/// </summary>
double Opacity { get; set; }
/// <summary>
/// Gets or sets the opacity mask of the scene graph node.
/// </summary>
IBrush OpacityMask { get; set; }
/// <summary>
/// Gets or sets the render transform of the scene graph node.
/// </summary>
Transform RenderTransform { get; set; }
/// <summary>
/// Gets or sets the transform origin of the scene graph node.
/// Gets or sets the render transform origin of the scene graph node.
/// </summary>
RelativePoint TransformOrigin { get; set; }
RelativePoint RenderTransformOrigin { get; set; }
/// <summary>
/// Gets the scene graph node's child nodes.

24
src/Avalonia.SceneGraph/VisualTree/TransformedBounds.cs

@ -1,15 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Media;
namespace Avalonia.VisualTree
{
/// <summary>
/// Holds information about the bounds of a control, together with a transform and a clip/
/// Holds information about the bounds of a control, together with a transform and a clip.
/// </summary>
public class TransformedBounds
public struct TransformedBounds
{
/// <summary>
/// Initializes a new instance of the <see cref="TransformedBounds"/> class.
/// Initializes a new instance of the <see cref="TransformedBounds"/> struct.
/// </summary>
/// <param name="bounds">The control's bounds.</param>
/// <param name="clip">The control's clip rectangle.</param>
@ -35,5 +37,19 @@ namespace Avalonia.VisualTree
/// Gets the control's transform.
/// </summary>
public Matrix Transform { get; }
public bool Contains(Point point)
{
if (Transform.HasInverse)
{
Point trPoint = point * Transform.Invert();
return Bounds.Contains(trPoint);
}
else
{
return Bounds.Contains(point);
}
}
}
}
}

11
src/Avalonia.Styling/Avalonia.Styling.csproj

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@ -43,12 +43,17 @@
<Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Controls\NameScopeEventArgs.cs" />
<Compile Include="Controls\NameScopeExtensions.cs" />
<Compile Include="LogicalTree\ILogical.cs" />
<Compile Include="LogicalTree\LogicalExtensions.cs" />
<Compile Include="LogicalTree\LogicalTreeAttachmentEventArgs.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Styling\ActivatedSubject.cs" />
<Compile Include="Styling\ActivatedValue.cs" />
<Compile Include="Controls\INameScope.cs" />
<Compile Include="Styling\ITemplate.cs" />
<Compile Include="Controls\NameScope.cs" />
<Compile Include="Styling\TemplateSelector.cs" />
<Compile Include="Styling\DescendentSelector.cs" />
<Compile Include="Styling\ChildSelector.cs" />
@ -92,6 +97,10 @@
<None Include="Styling\packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Avalonia.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj">
<Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
<Name>Avalonia.Base</Name>

0
src/Avalonia.Controls/INameScope.cs → src/Avalonia.Styling/Controls/INameScope.cs

6
src/Avalonia.Controls/NameScope.cs → src/Avalonia.Styling/Controls/NameScope.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls
/// Defines the NameScope attached property.
/// </summary>
public static readonly AttachedProperty<INameScope> NameScopeProperty =
AvaloniaProperty.RegisterAttached<NameScope, Control, INameScope>("NameScope");
AvaloniaProperty.RegisterAttached<NameScope, Visual, INameScope>("NameScope");
private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
@ -34,7 +34,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>The value of the NameScope attached property.</returns>
public static INameScope GetNameScope(Control visual)
public static INameScope GetNameScope(Visual visual)
{
return visual.GetValue(NameScopeProperty);
}
@ -44,7 +44,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="value">The value to set.</param>
public static void SetNameScope(Control visual, INameScope value)
public static void SetNameScope(Visual visual, INameScope value)
{
visual.SetValue(NameScopeProperty, value);
}

0
src/Avalonia.Controls/NameScopeEventArgs.cs → src/Avalonia.Styling/Controls/NameScopeEventArgs.cs

0
src/Avalonia.Controls/NameScopeExtensions.cs → src/Avalonia.Styling/Controls/NameScopeExtensions.cs

10
src/Avalonia.Styling/Styling/ITemplate.cs

@ -0,0 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Styling
{
public interface ITemplate
{
object Build();
}
}

37
src/Avalonia.Styling/Styling/Setter.cs

@ -4,6 +4,7 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Metadata;
using Avalonia.Reactive;
@ -19,6 +20,8 @@ namespace Avalonia.Styling
/// </remarks>
public class Setter : ISetter
{
private object _value;
/// <summary>
/// Initializes a new instance of the <see cref="Setter"/> class.
/// </summary>
@ -54,8 +57,22 @@ namespace Avalonia.Styling
[DependsOn(nameof(Property))]
public object Value
{
get;
set;
get
{
return _value;
}
set
{
if (value is IStyleable)
{
throw new ArgumentException(
"Cannot assign a control to Style.Value. Wrap the control in a <Template>.",
"value");
}
_value = value;
}
}
/// <summary>
@ -75,17 +92,27 @@ namespace Avalonia.Styling
throw new InvalidOperationException("Setter.Property must be set.");
}
var binding = Value as IBinding;
var value = Value;
var binding = value as IBinding;
if (binding == null)
{
var template = value as ITemplate;
if (template != null)
{
var materialized = template.Build();
NameScope.SetNameScope((Visual)materialized, new NameScope());
value = materialized;
}
if (activator == null)
{
return control.Bind(Property, ObservableEx.SingleValue(Value), BindingPriority.Style);
return control.Bind(Property, ObservableEx.SingleValue(value), BindingPriority.Style);
}
else
{
var activated = new ActivatedValue(activator, Value, description);
var activated = new ActivatedValue(activator, value, description);
return control.Bind(Property, activated, BindingPriority.StyleTrigger);
}
}

196
src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs

@ -15,7 +15,7 @@ namespace Avalonia.Cairo.Media
using Cairo = global::Cairo;
/// <summary>
/// Draws using Direct2D1.
/// Draws using Cairo.
/// </summary>
public class DrawingContext : IDrawingContextImpl, IDisposable
{
@ -24,6 +24,8 @@ namespace Avalonia.Cairo.Media
/// </summary>
private readonly Cairo.Context _context;
private readonly Stack<BrushImpl> _maskStack = new Stack<BrushImpl>();
/// <summary>
/// Initializes a new instance of the <see cref="DrawingContext"/> class.
/// </summary>
@ -83,31 +85,31 @@ namespace Avalonia.Cairo.Media
_context.Scale(scale.X, scale.Y);
destRect /= scale;
if (opacityOverride < 1.0f) {
_context.PushGroup ();
Gdk.CairoHelper.SetSourcePixbuf (
_context,
impl.Surface,
-sourceRect.X + destRect.X,
-sourceRect.Y + destRect.Y);
_context.Rectangle (destRect.ToCairo ());
_context.Fill ();
_context.PopGroupToSource ();
_context.PaintWithAlpha (opacityOverride);
} else {
_context.PushGroup ();
Gdk.CairoHelper.SetSourcePixbuf (
_context,
impl.Surface,
-sourceRect.X + destRect.X,
-sourceRect.Y + destRect.Y);
_context.Rectangle (destRect.ToCairo ());
_context.Fill ();
_context.PopGroupToSource ();
_context.PaintWithAlpha (opacityOverride);
}
if (opacityOverride < 1.0f) {
_context.PushGroup ();
Gdk.CairoHelper.SetSourcePixbuf (
_context,
impl.Surface,
-sourceRect.X + destRect.X,
-sourceRect.Y + destRect.Y);
_context.Rectangle (destRect.ToCairo ());
_context.Fill ();
_context.PopGroupToSource ();
_context.PaintWithAlpha (opacityOverride);
} else {
_context.PushGroup ();
Gdk.CairoHelper.SetSourcePixbuf (
_context,
impl.Surface,
-sourceRect.X + destRect.X,
-sourceRect.Y + destRect.Y);
_context.Rectangle (destRect.ToCairo ());
_context.Fill ();
_context.PopGroupToSource ();
_context.PaintWithAlpha (opacityOverride);
}
_context.Restore();
}
@ -121,12 +123,12 @@ namespace Avalonia.Cairo.Media
{
var size = new Rect(p1, p2).Size;
using (var p = SetPen(pen, size))
{
_context.MoveTo(p1.ToCairo());
_context.LineTo(p2.ToCairo());
_context.Stroke();
}
using (var p = SetPen(pen, size))
{
_context.MoveTo(p1.ToCairo());
_context.LineTo(p2.ToCairo());
_context.Stroke();
}
}
/// <summary>
@ -177,11 +179,11 @@ namespace Avalonia.Cairo.Media
/// <param name="rect">The rectangle bounds.</param>
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius)
{
using (var p = SetPen(pen, rect.Size))
{
_context.Rectangle(rect.ToCairo ());
_context.Stroke();
}
using (var p = SetPen(pen, rect.Size))
{
_context.Rectangle(rect.ToCairo ());
_context.Stroke();
}
}
/// <summary>
@ -195,10 +197,10 @@ namespace Avalonia.Cairo.Media
var layout = ((FormattedTextImpl)text.PlatformImpl).Layout;
_context.MoveTo(origin.X, origin.Y);
using (var b = SetBrush(foreground, new Size(0, 0)))
{
Pango.CairoHelper.ShowLayout(_context, layout);
}
using (var b = SetBrush(foreground, new Size(0, 0)))
{
Pango.CairoHelper.ShowLayout(_context, layout);
}
}
/// <summary>
@ -208,11 +210,11 @@ namespace Avalonia.Cairo.Media
/// <param name="rect">The rectangle bounds.</param>
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius)
{
using (var b = SetBrush(brush, rect.Size))
{
_context.Rectangle(rect.ToCairo ());
_context.Fill();
}
using (var b = SetBrush(brush, rect.Size))
{
_context.Rectangle(rect.ToCairo ());
_context.Fill();
}
}
/// <summary>
@ -269,50 +271,57 @@ namespace Avalonia.Cairo.Media
});
}
private double opacityOverride = 1.0f;
private double opacityOverride = 1.0f;
private IDisposable SetBrush(IBrush brush, Size destinationSize)
{
_context.Save ();
_context.Save();
BrushImpl impl = CreateBrushImpl(brush, destinationSize);
_context.SetSource(impl.PlatformBrush);
return Disposable.Create(() =>
{
impl.Dispose();
_context.Restore();
});
}
private BrushImpl CreateBrushImpl(IBrush brush, Size destinationSize)
{
var solid = brush as SolidColorBrush;
var linearGradientBrush = brush as LinearGradientBrush;
var radialGradientBrush = brush as RadialGradientBrush;
var imageBrush = brush as ImageBrush;
var visualBrush = brush as VisualBrush;
BrushImpl impl = null;
if (solid != null)
{
impl = new SolidColorBrushImpl(solid, opacityOverride);
}
else if (linearGradientBrush != null)
{
impl = new LinearGradientBrushImpl(linearGradientBrush, destinationSize);
}
BrushImpl impl = null;
if (solid != null)
{
impl = new SolidColorBrushImpl(solid, opacityOverride);
}
else if (linearGradientBrush != null)
{
impl = new LinearGradientBrushImpl(linearGradientBrush, destinationSize);
}
else if (radialGradientBrush != null)
{
impl = new RadialGradientBrushImpl(radialGradientBrush, destinationSize);
}
else if (imageBrush != null)
{
impl = new ImageBrushImpl(imageBrush, destinationSize);
}
else if (visualBrush != null)
{
impl = new VisualBrushImpl(visualBrush, destinationSize);
}
else
{
impl = new SolidColorBrushImpl(null, opacityOverride);
}
_context.SetSource(impl.PlatformBrush);
return Disposable.Create(() =>
{
impl.Dispose();
_context.Restore();
});
else if (imageBrush != null)
{
impl = new ImageBrushImpl(imageBrush, destinationSize);
}
else if (visualBrush != null)
{
impl = new VisualBrushImpl(visualBrush, destinationSize);
}
else
{
impl = new SolidColorBrushImpl(null, opacityOverride);
}
return impl;
}
private IDisposable SetPen(Pen pen, Size destinationSize)
@ -335,10 +344,35 @@ namespace Avalonia.Cairo.Media
_context.LineJoin = Cairo.LineJoin.Miter;
_context.LineCap = Cairo.LineCap.Butt;
if (pen.Brush == null)
return Disposable.Empty;
return SetBrush(pen.Brush, destinationSize);
if (pen.Brush == null)
return Disposable.Empty;
return SetBrush(pen.Brush, destinationSize);
}
public void PushGeometryClip(Geometry clip)
{
_context.Save();
_context.AppendPath(((StreamGeometryImpl)clip.PlatformImpl).Path);
_context.Clip();
}
public void PopGeometryClip()
{
_context.Restore();
}
public void PushOpacityMask(IBrush mask, Rect bounds)
{
_context.PushGroup();
var impl = CreateBrushImpl(mask, bounds.Size);
_maskStack.Push(impl);
}
public void PopOpacityMask()
{
_context.PopGroupToSource();
_context.Mask(_maskStack.Pop().PlatformBrush);
}
}
}

9
src/Gtk/Avalonia.Cairo/Media/StreamGeometryContextImpl.cs

@ -62,6 +62,15 @@ namespace Avalonia.Cairo.Media
}
}
internal bool FillContains(Point point)
{
using (var context = new Cairo.Context(new Cairo.ImageSurface(Cairo.Format.Argb32, 0, 0)))
{
context.AppendPath(Path);
return context.InFill(point.X, point.Y);
}
}
public void LineTo(Point point)
{
if (this.Path == null)

5
src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs

@ -67,5 +67,10 @@ namespace Avalonia.Cairo.Media
{
return _impl;
}
public bool FillContains(Point point)
{
return _impl.FillContains(point);
}
}
}

1
src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaObjectAssembler.cs

@ -23,6 +23,7 @@ namespace Avalonia.Markup.Xaml.Context
Settings settings = null)
{
var mapping = new DeferredLoaderMapping();
mapping.Map<Template>(x => x.Content, new TemplateLoader());
mapping.Map<ControlTemplate>(x => x.Content, new TemplateLoader());
mapping.Map<DataTemplate>(x => x.Content, new TemplateLoader());
mapping.Map<FocusAdornerTemplate>(x => x.Content, new TemplateLoader());

9
src/Markup/Avalonia.Markup.Xaml/Context/PropertyAccessor.cs

@ -136,7 +136,14 @@ namespace Avalonia.Markup.Xaml.Context
if (control != null)
{
DelayedBinding.Add(control, property, binding);
if (property != Control.DataContextProperty)
{
DelayedBinding.Add(control, property, binding);
}
else
{
control.Bind(property, binding);
}
}
else
{

15
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs

@ -20,6 +20,7 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary>
public Binding()
{
FallbackValue = AvaloniaProperty.UnsetValue;
}
/// <summary>
@ -27,6 +28,7 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary>
/// <param name="path">The binding path.</param>
public Binding(string path)
: this()
{
Path = path;
}
@ -122,12 +124,23 @@ namespace Avalonia.Markup.Xaml.Data
throw new NotSupportedException();
}
var fallback = FallbackValue;
// If we're binding to DataContext and our fallback is UnsetValue then override
// the fallback value to null, as broken bindings to DataContext must reset the
// DataContext in order to not propagate incorrect DataContexts to child controls.
// See Avalonia.Markup.Xaml.UnitTests.Data.DataContext_Binding_Should_Produce_Correct_Results.
if (targetProperty == Control.DataContextProperty && fallback == AvaloniaProperty.UnsetValue)
{
fallback = null;
}
var subject = new ExpressionSubject(
observer,
targetProperty?.PropertyType ?? typeof(object),
fallback,
Converter ?? DefaultValueConverter.Instance,
ConverterParameter,
FallbackValue,
Priority);
return new InstancedBinding(subject, Mode, Priority);

8
src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs

@ -14,11 +14,11 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary>
/// <remarks>
/// The XAML engine applies its bindings in a delayed manner where bindings are only applied
/// when a control has finished initializing. This was done because applying bindings as soon
/// when a control has finished initializing. This is done because applying bindings as soon
/// as controls are created means that long-form bindings (i.e. bindings that don't use the
/// `{Binding}` markup extension) don't work as the binding is applied to the property before
/// the binding properties are set, and looking at WPF it uses a similar mechanism for bindings
/// that come from XAML.
/// `{Binding}` markup extension but instead use `&lt;Binding&gt;`) don't work, as the binding
/// is applied to the property before the properties on the `Binding` object are set. Looking
/// at WPF it uses a similar mechanism for bindings that come from XAML.
/// </remarks>
public static class DelayedBinding
{

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -36,7 +36,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }
public string ElementName { get; set; }
public object FallbackValue { get; set; }
public object FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
public BindingMode Mode { get; set; }
public string Path { get; set; }
public BindingPriority Priority { get; set; } = BindingPriority.LocalValue;

10
src/Markup/Avalonia.Markup.Xaml/Templates/FocusAdornerTemplate.cs

@ -3,17 +3,11 @@
using Avalonia.Controls;
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.Templates
{
public class FocusAdornerTemplate : ITemplate<IControl>
public class FocusAdornerTemplate : Template
{
[Content]
public TemplateContent Content { get; set; }
public IControl Build()
{
return Content.Load();
}
}
}

10
src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.Templates
{
@ -18,5 +16,7 @@ namespace Avalonia.Markup.Xaml.Templates
{
return (IPanel)Content.Load();
}
object ITemplate.Build() => Build();
}
}

2
src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs

@ -51,6 +51,8 @@ namespace Avalonia.Markup.Xaml.Templates
object result = _memberValueNode.CurrentValue.Target;
_expressionNode.Target = null;
if (result == AvaloniaProperty.UnsetValue)
{
return null;

11
src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs

@ -1,10 +1,19 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.Templates
{
public class Template
public class Template : ITemplate<IControl>
{
[Content]
public TemplateContent Content { get; set; }
public IControl Build() => Content.Load();
object ITemplate.Build() => Build();
}
}

2
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -43,7 +43,7 @@ namespace Avalonia.Markup.Xaml.Templates
if (ItemsSource != null)
{
var obs = new ExpressionObserver(item, ItemsSource.Path);
return new InstancedBinding(obs);
return new InstancedBinding(obs, BindingMode.OneWay, BindingPriority.Style);
}
return null;

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

Loading…
Cancel
Save