169 changed files with 6629 additions and 1364 deletions
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="utf-8" ?> |
|||
<configuration> |
|||
<startup> |
|||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> |
|||
</startup> |
|||
</configuration> |
|||
@ -0,0 +1,6 @@ |
|||
<Application xmlns="https://github.com/avaloniaui"> |
|||
<Application.Styles> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/> |
|||
</Application.Styles> |
|||
</Application> |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace VirtualizationTest |
|||
{ |
|||
public class App : Application |
|||
{ |
|||
public override void Initialize() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
Title="Avalonia Virtualization Test"> |
|||
<DockPanel LastChildFill="True" Margin="16"> |
|||
<StackPanel DockPanel.Dock="Right" |
|||
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}"/> |
|||
<TextBox Watermark="Extent" |
|||
UseFloatingWatermark="True" |
|||
Text="{Binding #listBox.Scroll.Extent, Mode=OneWay}"/> |
|||
<TextBox Watermark="Offset" |
|||
UseFloatingWatermark="True" |
|||
Text="{Binding #listBox.Scroll.Offset, Mode=OneWay}"/> |
|||
<TextBox Watermark="Viewport" |
|||
UseFloatingWatermark="True" |
|||
Text="{Binding #listBox.Scroll.Viewport, Mode=OneWay}"/> |
|||
<TextBox Watermark="Item to Create" |
|||
UseFloatingWatermark="True" |
|||
Text="{Binding NewItemString}"/> |
|||
<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" |
|||
VirtualizationMode="{Binding VirtualizationMode}"> |
|||
<ListBox.ItemsPanel> |
|||
<ItemsPanelTemplate> |
|||
<VirtualizingStackPanel Orientation="{Binding Orientation}"/> |
|||
</ItemsPanelTemplate> |
|||
</ListBox.ItemsPanel> |
|||
<ListBox.ItemTemplate> |
|||
<DataTemplate> |
|||
<TextBlock Text="{Binding Header}"/> |
|||
</DataTemplate> |
|||
</ListBox.ItemTemplate> |
|||
</ListBox> |
|||
</DockPanel> |
|||
</Window> |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
using VirtualizationTest.ViewModels; |
|||
|
|||
namespace VirtualizationTest |
|||
{ |
|||
public class MainWindow : Window |
|||
{ |
|||
public MainWindow() |
|||
{ |
|||
this.InitializeComponent(); |
|||
this.AttachDevTools(); |
|||
DataContext = new MainWindowViewModel(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Logging.Serilog; |
|||
using Serilog; |
|||
|
|||
namespace VirtualizationTest |
|||
{ |
|||
class Program |
|||
{ |
|||
static void Main(string[] args) |
|||
{ |
|||
InitializeLogging(); |
|||
|
|||
AppBuilder.Configure<App>() |
|||
.UseWin32() |
|||
.UseDirect2D1() |
|||
.Start<MainWindow>(); |
|||
} |
|||
|
|||
private static void InitializeLogging() |
|||
{ |
|||
#if DEBUG
|
|||
SerilogLogger.Initialize(new LoggerConfiguration() |
|||
.MinimumLevel.Warning() |
|||
.WriteTo.Trace(outputTemplate: "{Area}: {Message}") |
|||
.CreateLogger()); |
|||
#endif
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using System.Reflection; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
// General Information about an assembly is controlled through the following
|
|||
// set of attributes. Change these attribute values to modify the information
|
|||
// associated with an assembly.
|
|||
[assembly: AssemblyTitle("VirtualizationTest")] |
|||
[assembly: AssemblyDescription("")] |
|||
[assembly: AssemblyConfiguration("")] |
|||
[assembly: AssemblyCompany("")] |
|||
[assembly: AssemblyProduct("VirtualizationTest")] |
|||
[assembly: AssemblyCopyright("Copyright © 2016")] |
|||
[assembly: AssemblyTrademark("")] |
|||
[assembly: AssemblyCulture("")] |
|||
|
|||
// Setting ComVisible to false makes the types in this assembly not visible
|
|||
// to COM components. If you need to access a type in this assembly from
|
|||
// COM, set the ComVisible attribute to true on that type.
|
|||
[assembly: ComVisible(false)] |
|||
|
|||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|||
[assembly: Guid("fbcaf3d0-2808-4934-8e96-3f607594517b")] |
|||
|
|||
// Version information for an assembly consists of the following four values:
|
|||
//
|
|||
// Major Version
|
|||
// Minor Version
|
|||
// Build Number
|
|||
// Revision
|
|||
//
|
|||
// You can specify all the values or you can default the Build and Revision Numbers
|
|||
// by using the '*' as shown below:
|
|||
// [assembly: AssemblyVersion("1.0.*")]
|
|||
[assembly: AssemblyVersion("1.0.0.0")] |
|||
[assembly: AssemblyFileVersion("1.0.0.0")] |
|||
@ -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.
|
|||
|
|||
using System; |
|||
using ReactiveUI; |
|||
|
|||
namespace VirtualizationTest.ViewModels |
|||
{ |
|||
internal class ItemViewModel : ReactiveObject |
|||
{ |
|||
private string _prefix; |
|||
private int _index; |
|||
|
|||
public ItemViewModel(int index, string prefix = "Item") |
|||
{ |
|||
_prefix = prefix; |
|||
_index = index; |
|||
} |
|||
|
|||
public string Header => $"{_prefix} {_index}"; |
|||
} |
|||
} |
|||
@ -0,0 +1,141 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Controls; |
|||
using ReactiveUI; |
|||
|
|||
namespace VirtualizationTest.ViewModels |
|||
{ |
|||
internal class MainWindowViewModel : ReactiveObject |
|||
{ |
|||
private int _itemCount = 200; |
|||
private string _newItemString = "New Item"; |
|||
private int _newItemIndex; |
|||
private IReactiveList<ItemViewModel> _items; |
|||
private string _prefix = "Item"; |
|||
private Orientation _orientation; |
|||
private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple; |
|||
|
|||
public MainWindowViewModel() |
|||
{ |
|||
this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems); |
|||
RecreateCommand = ReactiveCommand.Create(); |
|||
RecreateCommand.Subscribe(_ => Recreate()); |
|||
|
|||
AddItemCommand = ReactiveCommand.Create(); |
|||
AddItemCommand.Subscribe(_ => AddItem()); |
|||
|
|||
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 |
|||
{ |
|||
get { return _newItemString; } |
|||
set { this.RaiseAndSetIfChanged(ref _newItemString, value); } |
|||
} |
|||
|
|||
public int ItemCount |
|||
{ |
|||
get { return _itemCount; } |
|||
set { this.RaiseAndSetIfChanged(ref _itemCount, value); } |
|||
} |
|||
|
|||
public AvaloniaList<ItemViewModel> SelectedItems { get; } |
|||
= new AvaloniaList<ItemViewModel>(); |
|||
|
|||
public IReactiveList<ItemViewModel> Items |
|||
{ |
|||
get { return _items; } |
|||
private set { this.RaiseAndSetIfChanged(ref _items, value); } |
|||
} |
|||
|
|||
public Orientation Orientation |
|||
{ |
|||
get { return _orientation; } |
|||
set { this.RaiseAndSetIfChanged(ref _orientation, value); } |
|||
} |
|||
|
|||
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) |
|||
{ |
|||
if (Items == null) |
|||
{ |
|||
var items = Enumerable.Range(0, count) |
|||
.Select(x => new ItemViewModel(x)); |
|||
Items = new ReactiveList<ItemViewModel>(items); |
|||
} |
|||
else if (count > Items.Count) |
|||
{ |
|||
var items = Enumerable.Range(Items.Count, count - Items.Count) |
|||
.Select(x => new ItemViewModel(x)); |
|||
Items.AddRange(items); |
|||
} |
|||
else if (count < Items.Count) |
|||
{ |
|||
Items.RemoveRange(count, Items.Count - count); |
|||
} |
|||
} |
|||
|
|||
private void AddItem() |
|||
{ |
|||
var index = Items.Count; |
|||
|
|||
if (SelectedItems.Count > 0) |
|||
{ |
|||
index = Items.IndexOf(SelectedItems[0]); |
|||
} |
|||
|
|||
Items.Insert(index, new ItemViewModel(_newItemIndex++, NewItemString)); |
|||
} |
|||
|
|||
private void Remove() |
|||
{ |
|||
if (SelectedItems.Count > 0) |
|||
{ |
|||
Items.RemoveAll(SelectedItems); |
|||
} |
|||
} |
|||
|
|||
private void Recreate() |
|||
{ |
|||
_prefix = _prefix == "Item" ? "Recreated" : "Item"; |
|||
var items = Enumerable.Range(0, _itemCount) |
|||
.Select(x => new ItemViewModel(x, _prefix)); |
|||
Items = new ReactiveList<ItemViewModel>(items); |
|||
} |
|||
|
|||
private void SelectItem(int index) |
|||
{ |
|||
SelectedItems.Clear(); |
|||
SelectedItems.Add(Items[index]); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,180 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<Project ToolsVersion="14.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> |
|||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
|||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
|||
<ProjectGuid>{FBCAF3D0-2808-4934-8E96-3F607594517B}</ProjectGuid> |
|||
<OutputType>WinExe</OutputType> |
|||
<AppDesignerFolder>Properties</AppDesignerFolder> |
|||
<RootNamespace>VirtualizationTest</RootNamespace> |
|||
<AssemblyName>VirtualizationTest</AssemblyName> |
|||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> |
|||
<FileAlignment>512</FileAlignment> |
|||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
|||
<PlatformTarget>AnyCPU</PlatformTarget> |
|||
<DebugSymbols>true</DebugSymbols> |
|||
<DebugType>full</DebugType> |
|||
<Optimize>false</Optimize> |
|||
<OutputPath>bin\Debug\</OutputPath> |
|||
<DefineConstants>DEBUG;TRACE</DefineConstants> |
|||
<ErrorReport>prompt</ErrorReport> |
|||
<WarningLevel>4</WarningLevel> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
|||
<PlatformTarget>AnyCPU</PlatformTarget> |
|||
<DebugType>pdbonly</DebugType> |
|||
<Optimize>true</Optimize> |
|||
<OutputPath>bin\Release\</OutputPath> |
|||
<DefineConstants>TRACE</DefineConstants> |
|||
<ErrorReport>prompt</ErrorReport> |
|||
<WarningLevel>4</WarningLevel> |
|||
</PropertyGroup> |
|||
<PropertyGroup> |
|||
<StartupObject /> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> |
|||
<HintPath>..\..\packages\Serilog.1.5.14\lib\net45\Serilog.dll</HintPath> |
|||
<Private>True</Private> |
|||
</Reference> |
|||
<Reference Include="Serilog.FullNetFx, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> |
|||
<HintPath>..\..\packages\Serilog.1.5.14\lib\net45\Serilog.FullNetFx.dll</HintPath> |
|||
<Private>True</Private> |
|||
</Reference> |
|||
<Reference Include="Splat, Version=1.6.2.0, Culture=neutral, processorArchitecture=MSIL"> |
|||
<HintPath>..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll</HintPath> |
|||
<Private>True</Private> |
|||
</Reference> |
|||
<Reference Include="System" /> |
|||
<Reference Include="System.Core" /> |
|||
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> |
|||
<HintPath>..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll</HintPath> |
|||
<Private>True</Private> |
|||
</Reference> |
|||
<Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> |
|||
<HintPath>..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll</HintPath> |
|||
<Private>True</Private> |
|||
</Reference> |
|||
<Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> |
|||
<HintPath>..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll</HintPath> |
|||
<Private>True</Private> |
|||
</Reference> |
|||
<Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> |
|||
<HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath> |
|||
<Private>True</Private> |
|||
</Reference> |
|||
<Reference Include="System.Xml.Linq" /> |
|||
<Reference Include="System.Data.DataSetExtensions" /> |
|||
<Reference Include="Microsoft.CSharp" /> |
|||
<Reference Include="System.Data" /> |
|||
<Reference Include="System.Net.Http" /> |
|||
<Reference Include="System.Xml" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<Compile Include="App.xaml.cs"> |
|||
<DependentUpon>App.xaml</DependentUpon> |
|||
</Compile> |
|||
<Compile Include="MainWindow.xaml.cs"> |
|||
<DependentUpon>MainWindow.xaml</DependentUpon> |
|||
</Compile> |
|||
<Compile Include="Program.cs" /> |
|||
<Compile Include="Properties\AssemblyInfo.cs" /> |
|||
<Compile Include="ViewModels\ItemViewModel.cs" /> |
|||
<Compile Include="ViewModels\MainWindowViewModel.cs" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<None Include="App.config" /> |
|||
<None Include="packages.config" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj"> |
|||
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project> |
|||
<Name>Avalonia.Animation</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj"> |
|||
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project> |
|||
<Name>Avalonia.Base</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj"> |
|||
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project> |
|||
<Name>Avalonia.Controls</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj"> |
|||
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project> |
|||
<Name>Avalonia.DesignerSupport</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj"> |
|||
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project> |
|||
<Name>Avalonia.Diagnostics</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj"> |
|||
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project> |
|||
<Name>Avalonia.Input</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj"> |
|||
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project> |
|||
<Name>Avalonia.Interactivity</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj"> |
|||
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project> |
|||
<Name>Avalonia.Layout</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj"> |
|||
<Project>{B61B66A3-B82D-4875-8001-89D3394FE0C9}</Project> |
|||
<Name>Avalonia.Logging.Serilog</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj"> |
|||
<Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project> |
|||
<Name>Avalonia.ReactiveUI</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.SceneGraph\Avalonia.SceneGraph.csproj"> |
|||
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project> |
|||
<Name>Avalonia.SceneGraph</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj"> |
|||
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project> |
|||
<Name>Avalonia.Styling</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj"> |
|||
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project> |
|||
<Name>Avalonia.Themes.Default</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj"> |
|||
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project> |
|||
<Name>Avalonia.Markup.Xaml</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj"> |
|||
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project> |
|||
<Name>Avalonia.Markup</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj"> |
|||
<Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project> |
|||
<Name>Avalonia.Direct2D1</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj"> |
|||
<Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project> |
|||
<Name>Avalonia.Win32</Name> |
|||
</ProjectReference> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<EmbeddedResource Include="App.xaml"> |
|||
<SubType>Designer</SubType> |
|||
</EmbeddedResource> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<EmbeddedResource Include="MainWindow.xaml"> |
|||
<SubType>Designer</SubType> |
|||
</EmbeddedResource> |
|||
</ItemGroup> |
|||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
|||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. |
|||
Other similar extension points exist, see Microsoft.Common.targets. |
|||
<Target Name="BeforeBuild"> |
|||
</Target> |
|||
<Target Name="AfterBuild"> |
|||
</Target> |
|||
--> |
|||
</Project> |
|||
@ -0,0 +1,26 @@ |
|||
<ProjectConfiguration> |
|||
<AutoDetectNugetBuildDependencies>true</AutoDetectNugetBuildDependencies> |
|||
<BuildPriority>1000</BuildPriority> |
|||
<CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace> |
|||
<ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing> |
|||
<PreloadReferencedAssemblies>false</PreloadReferencedAssemblies> |
|||
<AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> |
|||
<AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> |
|||
<AllowCodeAnalysis>false</AllowCodeAnalysis> |
|||
<IgnoreThisComponentCompletely>true</IgnoreThisComponentCompletely> |
|||
<RunPreBuildEvents>false</RunPreBuildEvents> |
|||
<RunPostBuildEvents>false</RunPostBuildEvents> |
|||
<PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully> |
|||
<InstrumentAssembly>true</InstrumentAssembly> |
|||
<PreventSigningOfAssembly>false</PreventSigningOfAssembly> |
|||
<AnalyseExecutionTimes>true</AnalyseExecutionTimes> |
|||
<DetectStackOverflow>true</DetectStackOverflow> |
|||
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> |
|||
<DefaultTestTimeout>60000</DefaultTestTimeout> |
|||
<UseBuildConfiguration></UseBuildConfiguration> |
|||
<UseBuildPlatform></UseBuildPlatform> |
|||
<ProxyProcessPath></ProxyProcessPath> |
|||
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture> |
|||
<MSTestThreadApartmentState>STA</MSTestThreadApartmentState> |
|||
<BuildProcessArchitecture>x86</BuildProcessArchitecture> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<packages> |
|||
<package id="Rx-Core" version="2.2.5" targetFramework="net452" /> |
|||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net452" /> |
|||
<package id="Rx-Linq" version="2.2.5" targetFramework="net452" /> |
|||
<package id="Rx-Main" version="2.2.5" targetFramework="net452" /> |
|||
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="net452" /> |
|||
<package id="Serilog" version="1.5.14" targetFramework="net452" /> |
|||
<package id="Splat" version="1.6.2" targetFramework="net452" /> |
|||
</packages> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Interface implemented by scrollable controls.
|
|||
/// </summary>
|
|||
public interface IScrollable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the extent of the scrollable content, in logical units
|
|||
/// </summary>
|
|||
Size Extent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current scroll offset, in logical units.
|
|||
/// </summary>
|
|||
Vector Offset { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of the viewport, in logical units.
|
|||
/// </summary>
|
|||
Size Viewport { get; } |
|||
} |
|||
} |
|||
@ -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>
|
|||
/// Defines an interface through which a <see cref="Control"/>'s inheritance parent can be set.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// You should not usually need to use this interface - it is for advanced scenarios only.
|
|||
/// Additionally, <see cref="ISetLogicalParent"/> also sets the inheritance parent; this
|
|||
/// interface is only needed where the logical and inheritance parents differ.
|
|||
/// </remarks>
|
|||
public interface ISetInheritanceParent |
|||
{ |
|||
/// <summary>
|
|||
/// Sets the control's inheritance parent.
|
|||
/// </summary>
|
|||
/// <param name="parent">The parent.</param>
|
|||
void SetParent(IAvaloniaObject parent); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
// 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>
|
|||
/// A panel that can be used to virtualize items.
|
|||
/// </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>
|
|||
/// Gets the direction of scroll.
|
|||
/// </summary>
|
|||
Orientation ScrollDirection { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the average size of the materialized items in the direction of scroll.
|
|||
/// </summary>
|
|||
double AverageItemSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a size in pixels by which the content is overflowing the panel, in the
|
|||
/// direction of scroll.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This may be non-zero even when <see cref="OverflowCount"/> is zero if the last item
|
|||
/// overflows the panel bounds.
|
|||
/// </remarks>
|
|||
double PixelOverflow { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current pixel offset of the items in the direction of scroll.
|
|||
/// </summary>
|
|||
double PixelOffset { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// 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>
|
|||
/// Describes the item virtualization method to use for a list.
|
|||
/// </summary>
|
|||
public enum ItemVirtualizationMode |
|||
{ |
|||
/// <summary>
|
|||
/// Do not virtualize items.
|
|||
/// </summary>
|
|||
None, |
|||
|
|||
/// <summary>
|
|||
/// Virtualize items without smooth scrolling.
|
|||
/// </summary>
|
|||
Simple, |
|||
} |
|||
} |
|||
@ -0,0 +1,198 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Utils; |
|||
using Avalonia.Input; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for classes which handle virtualization for an <see cref="ItemsPresenter"/>.
|
|||
/// </summary>
|
|||
internal abstract class ItemVirtualizer : IVirtualizingController, IDisposable |
|||
{ |
|||
private bool disposedValue; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ItemVirtualizer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="owner"></param>
|
|||
public ItemVirtualizer(ItemsPresenter owner) |
|||
{ |
|||
Owner = owner; |
|||
Items = owner.Items; |
|||
ItemCount = owner.Items.Count(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ItemsPresenter"/> which owns the virtualizer.
|
|||
/// </summary>
|
|||
public ItemsPresenter Owner { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="IVirtualizingPanel"/> which will host the items.
|
|||
/// </summary>
|
|||
public IVirtualizingPanel VirtualizingPanel => Owner.Panel as IVirtualizingPanel; |
|||
|
|||
/// <summary>
|
|||
/// Gets the items to display.
|
|||
/// </summary>
|
|||
public IEnumerable Items { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of items in <see cref="Items"/>.
|
|||
/// </summary>
|
|||
public int ItemCount { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the index of the first item displayed in the panel.
|
|||
/// </summary>
|
|||
public int FirstIndex { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the index of the first item beyond those displayed in the panel.
|
|||
/// </summary>
|
|||
public int NextIndex { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the items should be scroll horizontally or vertically.
|
|||
/// </summary>
|
|||
public bool Vertical => VirtualizingPanel.ScrollDirection == Orientation.Vertical; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether logical scrolling is enabled.
|
|||
/// </summary>
|
|||
public abstract bool IsLogicalScrollEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the scroll extent.
|
|||
/// </summary>
|
|||
public abstract double ExtentValue { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the value of the current scroll offset.
|
|||
/// </summary>
|
|||
public abstract double OffsetValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the scrollable viewport.
|
|||
/// </summary>
|
|||
public abstract double ViewportValue { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ExtentValue"/> as a <see cref="Size"/>.
|
|||
/// </summary>
|
|||
public Size Extent => Vertical ? new Size(0, ExtentValue) : new Size(ExtentValue, 0); |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ViewportValue"/> as a <see cref="Size"/>.
|
|||
/// </summary>
|
|||
public Size Viewport => Vertical ? new Size(0, ViewportValue) : new Size(ViewportValue, 0); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="OffsetValue"/> as a <see cref="Vector"/>.
|
|||
/// </summary>
|
|||
public Vector Offset |
|||
{ |
|||
get |
|||
{ |
|||
return Vertical ? new Vector(0, OffsetValue) : new Vector(OffsetValue, 0); |
|||
} |
|||
|
|||
set |
|||
{ |
|||
OffsetValue = Vertical ? value.Y : value.X; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an <see cref="ItemVirtualizer"/> based on an item presenter's
|
|||
/// <see cref="ItemVirtualizationMode"/>.
|
|||
/// </summary>
|
|||
/// <param name="owner">The items presenter.</param>
|
|||
/// <returns>An <see cref="ItemVirtualizer"/>.</returns>
|
|||
public static ItemVirtualizer Create(ItemsPresenter owner) |
|||
{ |
|||
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: |
|||
result = new ItemVirtualizerSimple(owner); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (result == null) |
|||
{ |
|||
result = new ItemVirtualizerNone(owner); |
|||
} |
|||
|
|||
if (virtualizingPanel != null) |
|||
{ |
|||
virtualizingPanel.Controller = result; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public virtual void UpdateControls() |
|||
{ |
|||
} |
|||
|
|||
/// <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>
|
|||
public virtual IControl GetControlInDirection(NavigationDirection direction, IControl from) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the items for the presenter change, either because
|
|||
/// <see cref="ItemsPresenterBase.Items"/> has been set, the items collection has been
|
|||
/// modified, or the panel has been created.
|
|||
/// </summary>
|
|||
/// <param name="items">The items.</param>
|
|||
/// <param name="e">A description of the change.</param>
|
|||
public virtual void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,173 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Controls.Generators; |
|||
using Avalonia.Controls.Utils; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an item virtualizer for an <see cref="ItemsPresenter"/> that doesn't actually
|
|||
/// virtualize items - it just creates a container for every item.
|
|||
/// </summary>
|
|||
internal class ItemVirtualizerNone : ItemVirtualizer |
|||
{ |
|||
public ItemVirtualizerNone(ItemsPresenter owner) |
|||
: base(owner) |
|||
{ |
|||
if (Items != null && owner.Panel != null) |
|||
{ |
|||
AddContainers(0, Items); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool IsLogicalScrollEnabled => false; |
|||
|
|||
/// <summary>
|
|||
/// This property should never be accessed because <see cref="IsLogicalScrollEnabled"/> is
|
|||
/// false.
|
|||
/// </summary>
|
|||
public override double ExtentValue |
|||
{ |
|||
get { throw new NotSupportedException(); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This property should never be accessed because <see cref="IsLogicalScrollEnabled"/> is
|
|||
/// false.
|
|||
/// </summary>
|
|||
public override double OffsetValue |
|||
{ |
|||
get { throw new NotSupportedException(); } |
|||
set { throw new NotSupportedException(); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This property should never be accessed because <see cref="IsLogicalScrollEnabled"/> is
|
|||
/// false.
|
|||
/// </summary>
|
|||
public override double ViewportValue |
|||
{ |
|||
get { throw new NotSupportedException(); } |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
base.ItemsChanged(items, e); |
|||
|
|||
var generator = Owner.ItemContainerGenerator; |
|||
var panel = Owner.Panel; |
|||
|
|||
switch (e.Action) |
|||
{ |
|||
case NotifyCollectionChangedAction.Add: |
|||
if (e.NewStartingIndex + e.NewItems.Count < Items.Count()) |
|||
{ |
|||
generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count); |
|||
} |
|||
|
|||
AddContainers(e.NewStartingIndex, e.NewItems); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Remove: |
|||
RemoveContainers(generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count)); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Replace: |
|||
RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count)); |
|||
var containers = AddContainers(e.NewStartingIndex, e.NewItems); |
|||
|
|||
var i = e.NewStartingIndex; |
|||
|
|||
foreach (var container in containers) |
|||
{ |
|||
panel.Children[i++] = container.ContainerControl; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Move: |
|||
// TODO: Handle move in a more efficient manner. At the moment we just
|
|||
// drop through to Reset to recreate all the containers.
|
|||
|
|||
case NotifyCollectionChangedAction.Reset: |
|||
RemoveContainers(generator.Clear()); |
|||
|
|||
if (Items != null) |
|||
{ |
|||
AddContainers(0, Items); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
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; |
|||
var result = new List<ItemContainerInfo>(); |
|||
var panel = Owner.Panel; |
|||
|
|||
foreach (var item in items) |
|||
{ |
|||
var i = generator.Materialize(index++, item, Owner.MemberSelector); |
|||
|
|||
if (i.ContainerControl != null) |
|||
{ |
|||
if (i.Index < panel.Children.Count) |
|||
{ |
|||
// TODO: This will insert at the wrong place when there are null items.
|
|||
panel.Children.Insert(i.Index, i.ContainerControl); |
|||
} |
|||
else |
|||
{ |
|||
panel.Children.Add(i.ContainerControl); |
|||
} |
|||
} |
|||
|
|||
result.Add(i); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private void RemoveContainers(IEnumerable<ItemContainerInfo> items) |
|||
{ |
|||
var panel = Owner.Panel; |
|||
|
|||
foreach (var i in items) |
|||
{ |
|||
if (i.ContainerControl != null) |
|||
{ |
|||
panel.Children.Remove(i.ContainerControl); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,504 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Specialized; |
|||
using System.Linq; |
|||
using Avalonia.Controls.Utils; |
|||
using Avalonia.Input; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
/// <summary>
|
|||
/// Handles virtualization in an <see cref="ItemsPresenter"/> for
|
|||
/// <see cref="ItemVirtualizationMode.Simple"/>.
|
|||
/// </summary>
|
|||
internal class ItemVirtualizerSimple : ItemVirtualizer |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ItemVirtualizerSimple"/> class.
|
|||
/// </summary>
|
|||
/// <param name="owner"></param>
|
|||
public ItemVirtualizerSimple(ItemsPresenter owner) |
|||
: base(owner) |
|||
{ |
|||
// Don't need to add children here as UpdateControls should be called by the panel
|
|||
// measure/arrange.
|
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool IsLogicalScrollEnabled => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override double ExtentValue => ItemCount; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override double OffsetValue |
|||
{ |
|||
get |
|||
{ |
|||
var offset = VirtualizingPanel.PixelOffset > 0 ? 1 : 0; |
|||
return FirstIndex + offset; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
var panel = VirtualizingPanel; |
|||
var offset = VirtualizingPanel.PixelOffset > 0 ? 1 : 0; |
|||
var delta = (int)(value - (FirstIndex + offset)); |
|||
|
|||
if (delta != 0) |
|||
{ |
|||
var newLastIndex = (NextIndex - 1) + delta; |
|||
|
|||
if (newLastIndex < ItemCount) |
|||
{ |
|||
if (panel.PixelOffset > 0) |
|||
{ |
|||
panel.PixelOffset = 0; |
|||
delta += 1; |
|||
} |
|||
|
|||
if (delta != 0) |
|||
{ |
|||
RecycleContainersForMove(delta); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// 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.ScrollDirection == Orientation.Vertical ? |
|||
panel.Children[0].Bounds.Height : |
|||
panel.Children[0].Bounds.Width; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override double ViewportValue |
|||
{ |
|||
get |
|||
{ |
|||
// If we can't fit the last item in the panel fully, subtract 1 from the viewport.
|
|||
var overflow = VirtualizingPanel.PixelOverflow > 0 ? 1 : 0; |
|||
return VirtualizingPanel.Children.Count - overflow; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void UpdateControls() |
|||
{ |
|||
CreateAndRemoveContainers(); |
|||
InvalidateScroll(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
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) |
|||
{ |
|||
RecycleContainers(); |
|||
} |
|||
|
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Remove: |
|||
if (e.OldStartingIndex >= FirstIndex && |
|||
e.OldStartingIndex + e.OldItems.Count <= NextIndex) |
|||
{ |
|||
RecycleContainersOnRemove(); |
|||
} |
|||
|
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Move: |
|||
case NotifyCollectionChangedAction.Replace: |
|||
RecycleContainers(); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Reset: |
|||
RecycleContainersOnRemove(); |
|||
CreateAndRemoveContainers(); |
|||
break; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Owner.ItemContainerGenerator.Clear(); |
|||
VirtualizingPanel.Children.Clear(); |
|||
FirstIndex = NextIndex = 0; |
|||
} |
|||
|
|||
// 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>
|
|||
/// Creates and removes containers such that we have at most enough containers to fill
|
|||
/// the panel.
|
|||
/// </summary>
|
|||
private void CreateAndRemoveContainers() |
|||
{ |
|||
var generator = Owner.ItemContainerGenerator; |
|||
var panel = VirtualizingPanel; |
|||
|
|||
if (!panel.IsFull && Items != null) |
|||
{ |
|||
var memberSelector = Owner.MemberSelector; |
|||
var index = NextIndex; |
|||
var step = 1; |
|||
|
|||
while (!panel.IsFull) |
|||
{ |
|||
if (index >= ItemCount) |
|||
{ |
|||
// We can fit more containers in the panel, but we're at the end of the
|
|||
// items. If we're scrolled to the top (FirstIndex == 0), then there are
|
|||
// no more items to create. Otherwise, go backwards adding containers to
|
|||
// the beginning of the panel.
|
|||
if (FirstIndex == 0) |
|||
{ |
|||
break; |
|||
} |
|||
else |
|||
{ |
|||
index = FirstIndex - 1; |
|||
step = -1; |
|||
} |
|||
} |
|||
|
|||
var materialized = generator.Materialize(index, Items.ElementAt(index), memberSelector); |
|||
|
|||
if (step == 1) |
|||
{ |
|||
panel.Children.Add(materialized.ContainerControl); |
|||
} |
|||
else |
|||
{ |
|||
panel.Children.Insert(0, materialized.ContainerControl); |
|||
} |
|||
|
|||
index += step; |
|||
} |
|||
|
|||
if (step == 1) |
|||
{ |
|||
NextIndex = index; |
|||
} |
|||
else |
|||
{ |
|||
NextIndex = ItemCount; |
|||
FirstIndex = index + 1; |
|||
} |
|||
} |
|||
|
|||
if (panel.OverflowCount > 0) |
|||
{ |
|||
RemoveContainers(panel.OverflowCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates the containers in the panel to make sure they are displaying the correct item
|
|||
/// based on <see cref="ItemVirtualizer.FirstIndex"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method requires that <see cref="ItemVirtualizer.FirstIndex"/> + the number of
|
|||
/// materialized containers is not more than <see cref="ItemVirtualizer.ItemCount"/>.
|
|||
/// </remarks>
|
|||
private void RecycleContainers() |
|||
{ |
|||
var panel = VirtualizingPanel; |
|||
var generator = Owner.ItemContainerGenerator; |
|||
var selector = Owner.MemberSelector; |
|||
var containers = generator.Containers.ToList(); |
|||
var itemIndex = FirstIndex; |
|||
|
|||
foreach (var container in containers) |
|||
{ |
|||
var item = Items.ElementAt(itemIndex); |
|||
|
|||
if (!object.Equals(container.Item, item)) |
|||
{ |
|||
if (!generator.TryRecycle(itemIndex, itemIndex, item, selector)) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
|
|||
++itemIndex; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Recycles containers when a move occurs.
|
|||
/// </summary>
|
|||
/// <param name="delta">The delta of the move.</param>
|
|||
/// <remarks>
|
|||
/// If the move is less than a page, then this method moves the containers for the items
|
|||
/// that are still visible to the correct place, and recyles and moves the others. For
|
|||
/// example: if there are 20 items and 10 containers visible and the user scrolls 5
|
|||
/// items down, then the bottom 5 containers will be moved to the top and the top 5 will
|
|||
/// be moved to the bottom and recycled to display the newly visible item. Updates
|
|||
/// <see cref="ItemVirtualizer.FirstIndex"/> and <see cref="ItemVirtualizer.NextIndex"/>
|
|||
/// with their new values.
|
|||
/// </remarks>
|
|||
private void RecycleContainersForMove(int delta) |
|||
{ |
|||
var panel = VirtualizingPanel; |
|||
var generator = Owner.ItemContainerGenerator; |
|||
var selector = Owner.MemberSelector; |
|||
var sign = delta < 0 ? -1 : 1; |
|||
var count = Math.Min(Math.Abs(delta), panel.Children.Count); |
|||
var move = count < panel.Children.Count; |
|||
var first = delta < 0 && move ? panel.Children.Count + delta : 0; |
|||
var containers = panel.Children.GetRange(first, count).ToList(); |
|||
|
|||
for (var i = 0; i < count; ++i) |
|||
{ |
|||
var oldItemIndex = FirstIndex + first + i; |
|||
var newItemIndex = oldItemIndex + delta + ((panel.Children.Count - count) * sign); |
|||
|
|||
var item = Items.ElementAt(newItemIndex); |
|||
|
|||
if (!generator.TryRecycle(oldItemIndex, newItemIndex, item, selector)) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
|
|||
if (move) |
|||
{ |
|||
if (delta > 0) |
|||
{ |
|||
panel.Children.MoveRange(first, count, panel.Children.Count); |
|||
} |
|||
else |
|||
{ |
|||
panel.Children.MoveRange(first, count, 0); |
|||
} |
|||
} |
|||
|
|||
FirstIndex += delta; |
|||
NextIndex += delta; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Recycles containers due to items being removed.
|
|||
/// </summary>
|
|||
private void RecycleContainersOnRemove() |
|||
{ |
|||
var panel = VirtualizingPanel; |
|||
|
|||
if (NextIndex <= ItemCount) |
|||
{ |
|||
// Items have been removed but FirstIndex..NextIndex is still a valid range in the
|
|||
// items, so just recycle the containers to adapt to the new state.
|
|||
RecycleContainers(); |
|||
} |
|||
else |
|||
{ |
|||
// Items have been removed and now the range FirstIndex..NextIndex goes out of
|
|||
// the item bounds. Remove any excess containers, try to scroll up and then recycle
|
|||
// the containers to make sure they point to the correct item.
|
|||
var newFirstIndex = Math.Max(0, FirstIndex - (NextIndex - ItemCount)); |
|||
var delta = newFirstIndex - FirstIndex; |
|||
var newNextIndex = NextIndex + delta; |
|||
|
|||
if (newNextIndex > ItemCount) |
|||
{ |
|||
RemoveContainers(newNextIndex - ItemCount); |
|||
} |
|||
|
|||
if (delta != 0) |
|||
{ |
|||
RecycleContainersForMove(delta); |
|||
} |
|||
|
|||
RecycleContainers(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the specified number of containers from the end of the panel and updates
|
|||
/// <see cref="ItemVirtualizer.NextIndex"/>.
|
|||
/// </summary>
|
|||
/// <param name="count">The number of containers to remove.</param>
|
|||
private void RemoveContainers(int count) |
|||
{ |
|||
var index = VirtualizingPanel.Children.Count - count; |
|||
|
|||
VirtualizingPanel.Children.RemoveRange(index, count); |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Input; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Interface implemented by controls that handle their own scrolling when placed inside a
|
|||
/// <see cref="ScrollViewer"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Controls that implement this interface, when placed inside a <see cref="ScrollViewer"/>
|
|||
/// can override the physical scrolling behavior of the scroll viewer with logical scrolling.
|
|||
/// Physical scrolling means that the scroll viewer is a simple viewport onto a larger canvas
|
|||
/// whereas logical scrolling means that the scrolling is handled by the child control itself
|
|||
/// and it can choose to do handle the scroll information as it sees fit.
|
|||
/// </remarks>
|
|||
public interface ILogicalScrollable : IScrollable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether logical scrolling is enabled on the control.
|
|||
/// </summary>
|
|||
bool IsLogicalScrollEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the scroll invalidation method.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// This method notifies the attached <see cref="ScrollViewer"/> of a change in
|
|||
/// the <see cref="IScrollable.Extent"/>, <see cref="IScrollable.Offset"/> or
|
|||
/// <see cref="IScrollable.Viewport"/> properties.
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// This property is set by the parent <see cref="ScrollViewer"/> when the
|
|||
/// <see cref="ILogicalScrollable"/> is placed inside it.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
Action InvalidateScroll { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size to scroll by, in logical units.
|
|||
/// </summary>
|
|||
Size ScrollSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size to page by, in logical units.
|
|||
/// </summary>
|
|||
Size PageScrollSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Attempts to bring a portion of the target visual into view by scrolling the content.
|
|||
/// </summary>
|
|||
/// <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(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); |
|||
} |
|||
} |
|||
@ -1,54 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Interface implemented by controls that handle their own scrolling when placed inside a
|
|||
/// <see cref="ScrollViewer"/>.
|
|||
/// </summary>
|
|||
public interface IScrollable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the scroll invalidation method.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// This method notifies the attached <see cref="ScrollViewer"/> of a change in
|
|||
/// the <see cref="Extent"/>, <see cref="Offset"/> or <see cref="Viewport"/> properties.
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// This property is set by the parent <see cref="ScrollViewer"/> when the
|
|||
/// <see cref="IScrollable"/> is placed inside it.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
Action InvalidateScroll { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the extent of the scrollable content, in logical units
|
|||
/// </summary>
|
|||
Size Extent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current scroll offset, in logical units.
|
|||
/// </summary>
|
|||
Vector Offset { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of the viewport, in logical units.
|
|||
/// </summary>
|
|||
Size Viewport { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size to scroll by, in logical units.
|
|||
/// </summary>
|
|||
Size ScrollSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size to page by, in logical units.
|
|||
/// </summary>
|
|||
Size PageScrollSize { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,229 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.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; |
|||
private int _averageCount; |
|||
private double _pixelOffset; |
|||
|
|||
bool IVirtualizingPanel.IsFull |
|||
{ |
|||
get |
|||
{ |
|||
return Orientation == Orientation.Horizontal ? |
|||
_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 |
|||
{ |
|||
get |
|||
{ |
|||
var bounds = Orientation == Orientation.Horizontal ? |
|||
_availableSpace.Width : _availableSpace.Height; |
|||
return Math.Max(0, _takenSpace - bounds); |
|||
} |
|||
} |
|||
|
|||
double IVirtualizingPanel.PixelOffset |
|||
{ |
|||
get { return _pixelOffset; } |
|||
|
|||
set |
|||
{ |
|||
if (_pixelOffset != value) |
|||
{ |
|||
_pixelOffset = value; |
|||
InvalidateArrange(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private IVirtualizingController Controller => ((IVirtualizingPanel)this).Controller; |
|||
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
if (availableSize != ((ILayoutable)this).PreviousMeasure) |
|||
{ |
|||
// TODO: We need to put a reasonable limit on this, probably based on the max
|
|||
// window size.
|
|||
_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; |
|||
} |
|||
|
|||
protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
base.ChildrenChanged(sender, e); |
|||
|
|||
switch (e.Action) |
|||
{ |
|||
case NotifyCollectionChangedAction.Add: |
|||
foreach (IControl control in e.NewItems) |
|||
{ |
|||
UpdateAdd(control); |
|||
} |
|||
|
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Remove: |
|||
foreach (IControl control in e.OldItems) |
|||
{ |
|||
UpdateRemove(control); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
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, |
|||
Size panelSize, |
|||
Orientation orientation) |
|||
{ |
|||
if (orientation == Orientation.Vertical) |
|||
{ |
|||
rect = new Rect(rect.X, rect.Y - _pixelOffset, rect.Width, rect.Height); |
|||
child.Arrange(rect); |
|||
|
|||
if (rect.Y >= _availableSpace.Height) |
|||
{ |
|||
++_canBeRemoved; |
|||
} |
|||
|
|||
if (rect.Bottom >= _takenSpace) |
|||
{ |
|||
_takenSpace = rect.Bottom; |
|||
} |
|||
|
|||
AddToAverageItemSize(rect.Height); |
|||
} |
|||
else |
|||
{ |
|||
rect = new Rect(rect.X - _pixelOffset, rect.Y, rect.Width, rect.Height); |
|||
child.Arrange(rect); |
|||
|
|||
if (rect.X >= _availableSpace.Width) |
|||
{ |
|||
++_canBeRemoved; |
|||
} |
|||
|
|||
if (rect.Right >= _takenSpace) |
|||
{ |
|||
_takenSpace = rect.Right; |
|||
} |
|||
|
|||
AddToAverageItemSize(rect.Width); |
|||
} |
|||
} |
|||
|
|||
private void UpdateAdd(IControl child) |
|||
{ |
|||
var bounds = Bounds; |
|||
var gap = Gap; |
|||
|
|||
child.Measure(_availableSpace); |
|||
++_averageCount; |
|||
|
|||
if (Orientation == Orientation.Vertical) |
|||
{ |
|||
var height = child.DesiredSize.Height; |
|||
_takenSpace += height + gap; |
|||
AddToAverageItemSize(height); |
|||
} |
|||
else |
|||
{ |
|||
var width = child.DesiredSize.Width; |
|||
_takenSpace += width + gap; |
|||
AddToAverageItemSize(width); |
|||
} |
|||
} |
|||
|
|||
private void UpdateRemove(IControl child) |
|||
{ |
|||
var bounds = Bounds; |
|||
var gap = Gap; |
|||
|
|||
if (Orientation == Orientation.Vertical) |
|||
{ |
|||
var height = child.DesiredSize.Height; |
|||
_takenSpace -= height + gap; |
|||
RemoveFromAverageItemSize(height); |
|||
} |
|||
else |
|||
{ |
|||
var width = child.DesiredSize.Width; |
|||
_takenSpace -= width + gap; |
|||
RemoveFromAverageItemSize(width); |
|||
} |
|||
|
|||
if (_canBeRemoved > 0) |
|||
{ |
|||
--_canBeRemoved; |
|||
} |
|||
} |
|||
|
|||
private void AddToAverageItemSize(double value) |
|||
{ |
|||
++_averageCount; |
|||
_averageItemSize += (value - _averageItemSize) / _averageCount; |
|||
} |
|||
|
|||
private void RemoveFromAverageItemSize(double value) |
|||
{ |
|||
_averageItemSize = ((_averageItemSize * _averageCount) - value) / (_averageCount - 1); |
|||
--_averageCount; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue