283 changed files with 7873 additions and 4011 deletions
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,27 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
xmlns:viewModels="using:ControlCatalog.ViewModels" |
||||
|
mc:Ignorable="d" |
||||
|
d:DesignWidth="800" |
||||
|
d:DesignHeight="450" |
||||
|
x:DataType="viewModels:RefreshContainerViewModel" |
||||
|
x:Class="ControlCatalog.Pages.RefreshContainerPage"> |
||||
|
<DockPanel HorizontalAlignment="Stretch" |
||||
|
Height="600" |
||||
|
VerticalAlignment="Top"> |
||||
|
<Label DockPanel.Dock="Top">A control that supports pull to refresh</Label> |
||||
|
<RefreshContainer Name="Refresh" |
||||
|
DockPanel.Dock="Bottom" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
PullDirection="TopToBottom" |
||||
|
RefreshRequested="RefreshContainerPage_RefreshRequested" |
||||
|
Margin="5"> |
||||
|
<ListBox HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Top" |
||||
|
Items="{Binding Items}"/> |
||||
|
</RefreshContainer> |
||||
|
</DockPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using ControlCatalog.ViewModels; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class RefreshContainerPage : UserControl |
||||
|
{ |
||||
|
private RefreshContainerViewModel _viewModel; |
||||
|
|
||||
|
public RefreshContainerPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
|
||||
|
_viewModel = new RefreshContainerViewModel(); |
||||
|
|
||||
|
DataContext = _viewModel; |
||||
|
} |
||||
|
|
||||
|
private async void RefreshContainerPage_RefreshRequested(object? sender, RefreshRequestedEventArgs e) |
||||
|
{ |
||||
|
var deferral = e.GetDeferral(); |
||||
|
|
||||
|
await _viewModel.AddToTop(); |
||||
|
|
||||
|
deferral.Complete(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System.Collections.ObjectModel; |
||||
|
using System.Linq; |
||||
|
using System.Reactive; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls.Notifications; |
||||
|
using ControlCatalog.Pages; |
||||
|
using MiniMvvm; |
||||
|
|
||||
|
namespace ControlCatalog.ViewModels |
||||
|
{ |
||||
|
public class RefreshContainerViewModel : ViewModelBase |
||||
|
{ |
||||
|
public ObservableCollection<string> Items { get; } |
||||
|
|
||||
|
public RefreshContainerViewModel() |
||||
|
{ |
||||
|
Items = new ObservableCollection<string>(Enumerable.Range(1, 200).Select(i => $"Item {i}")); |
||||
|
} |
||||
|
|
||||
|
public async Task AddToTop() |
||||
|
{ |
||||
|
await Task.Delay(3000); |
||||
|
Items.Insert(0, $"Item {200 - Items.Count}"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
<Application |
||||
|
xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="ReactiveUIDemo.App"> |
||||
|
<Application.Styles> |
||||
|
<FluentTheme /> |
||||
|
</Application.Styles> |
||||
|
</Application> |
||||
@ -0,0 +1,37 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Controls.ApplicationLifetimes; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using Avalonia.ReactiveUI; |
||||
|
using ReactiveUI; |
||||
|
using ReactiveUIDemo.ViewModels; |
||||
|
using ReactiveUIDemo.Views; |
||||
|
using Splat; |
||||
|
|
||||
|
namespace ReactiveUIDemo |
||||
|
{ |
||||
|
public class App : Application |
||||
|
{ |
||||
|
public override void Initialize() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
Locator.CurrentMutable.Register(() => new FooView(), typeof(IViewFor<FooViewModel>)); |
||||
|
Locator.CurrentMutable.Register(() => new BarView(), typeof(IViewFor<BarViewModel>)); |
||||
|
} |
||||
|
|
||||
|
public override void OnFrameworkInitializationCompleted() |
||||
|
{ |
||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) |
||||
|
desktop.MainWindow = new MainWindow(); |
||||
|
base.OnFrameworkInitializationCompleted(); |
||||
|
} |
||||
|
|
||||
|
public static int Main(string[] args) |
||||
|
=> BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); |
||||
|
|
||||
|
public static AppBuilder BuildAvaloniaApp() |
||||
|
=> AppBuilder.Configure<App>() |
||||
|
.UsePlatformDetect() |
||||
|
.UseReactiveUI() |
||||
|
.LogToTrace(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Window xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' |
||||
|
x:Class="ReactiveUIDemo.MainWindow" |
||||
|
xmlns:vm="using:ReactiveUIDemo.ViewModels" |
||||
|
xmlns:rxui="using:Avalonia.ReactiveUI" |
||||
|
Title="AvaloniaUI ReactiveUI Demo" |
||||
|
x:DataType="vm:MainWindowViewModel"> |
||||
|
<TabControl TabStripPlacement="Left"> |
||||
|
<TabItem Header="RoutedViewHost"> |
||||
|
<DockPanel DataContext="{Binding RoutedViewHost}"> |
||||
|
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="8"> |
||||
|
<Button Command="{Binding ShowFoo}">Foo</Button> |
||||
|
<Button Command="{Binding ShowBar}">Bar</Button> |
||||
|
</StackPanel> |
||||
|
<rxui:RoutedViewHost Router="{Binding Router}"/> |
||||
|
</DockPanel> |
||||
|
</TabItem> |
||||
|
</TabControl> |
||||
|
</Window> |
||||
@ -0,0 +1,22 @@ |
|||||
|
using ReactiveUIDemo.ViewModels; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace ReactiveUIDemo |
||||
|
{ |
||||
|
public class MainWindow : Window |
||||
|
{ |
||||
|
public MainWindow() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
this.DataContext = new MainWindowViewModel(); |
||||
|
this.AttachDevTools(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<Nullable>enable</Nullable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Compile Update="Views\BarView.axaml.cs"> |
||||
|
<DependentUpon>BarView.axaml</DependentUpon> |
||||
|
</Compile> |
||||
|
<Compile Update="Views\FooView.axaml.cs"> |
||||
|
<DependentUpon>FooView.axaml</DependentUpon> |
||||
|
</Compile> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<Import Project="..\..\build\SampleApp.props" /> |
||||
|
<Import Project="..\..\build\ReferenceCoreLibraries.props" /> |
||||
|
<Import Project="..\..\build\BuildTargets.targets" /> |
||||
|
<Import Project="..\..\build\Rx.props" /> |
||||
|
<Import Project="..\..\build\ReactiveUI.props" /> |
||||
|
</Project> |
||||
@ -0,0 +1,11 @@ |
|||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace ReactiveUIDemo.ViewModels |
||||
|
{ |
||||
|
internal class BarViewModel : ReactiveObject, IRoutableViewModel |
||||
|
{ |
||||
|
public BarViewModel(IScreen screen) => HostScreen = screen; |
||||
|
public string UrlPathSegment => "Bar"; |
||||
|
public IScreen HostScreen { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace ReactiveUIDemo.ViewModels |
||||
|
{ |
||||
|
internal class FooViewModel : ReactiveObject, IRoutableViewModel |
||||
|
{ |
||||
|
public FooViewModel(IScreen screen) => HostScreen = screen; |
||||
|
public string UrlPathSegment => "Foo"; |
||||
|
public IScreen HostScreen { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace ReactiveUIDemo.ViewModels |
||||
|
{ |
||||
|
internal class MainWindowViewModel : ReactiveObject |
||||
|
{ |
||||
|
public RoutedViewHostPageViewModel RoutedViewHost { get; } = new(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace ReactiveUIDemo.ViewModels |
||||
|
{ |
||||
|
internal class RoutedViewHostPageViewModel : ReactiveObject, IScreen |
||||
|
{ |
||||
|
public RoutedViewHostPageViewModel() |
||||
|
{ |
||||
|
Foo = new(this); |
||||
|
Bar = new(this); |
||||
|
Router.Navigate.Execute(Foo); |
||||
|
} |
||||
|
|
||||
|
public RoutingState Router { get; } = new(); |
||||
|
public FooViewModel Foo { get; } |
||||
|
public BarViewModel Bar { get; } |
||||
|
|
||||
|
public void ShowFoo() => Router.Navigate.Execute(Foo); |
||||
|
public void ShowBar() => Router.Navigate.Execute(Bar); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
|
x:Class="ReactiveUIDemo.Views.BarView" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch"> |
||||
|
<Border Background="Blue"> |
||||
|
<TextBlock HorizontalAlignment="Center" |
||||
|
VerticalAlignment="Center" |
||||
|
FontSize="48"> |
||||
|
Bar! |
||||
|
</TextBlock> |
||||
|
</Border> |
||||
|
</UserControl> |
||||
@ -0,0 +1,28 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using ReactiveUI; |
||||
|
using ReactiveUIDemo.ViewModels; |
||||
|
|
||||
|
namespace ReactiveUIDemo.Views |
||||
|
{ |
||||
|
internal partial class BarView : UserControl, IViewFor<BarViewModel> |
||||
|
{ |
||||
|
public BarView() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
public BarViewModel? ViewModel { get; set; } |
||||
|
|
||||
|
object? IViewFor.ViewModel |
||||
|
{ |
||||
|
get => ViewModel; |
||||
|
set => ViewModel = (BarViewModel?)value; |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
|
x:Class="ReactiveUIDemo.Views.FooView" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch"> |
||||
|
<Border Background="Red"> |
||||
|
<TextBlock HorizontalAlignment="Center" |
||||
|
VerticalAlignment="Center" |
||||
|
FontSize="48"> |
||||
|
Foo! |
||||
|
</TextBlock> |
||||
|
</Border> |
||||
|
</UserControl> |
||||
@ -0,0 +1,28 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using ReactiveUI; |
||||
|
using ReactiveUIDemo.ViewModels; |
||||
|
|
||||
|
namespace ReactiveUIDemo.Views |
||||
|
{ |
||||
|
internal partial class FooView : UserControl, IViewFor<FooViewModel> |
||||
|
{ |
||||
|
public FooView() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
public FooViewModel? ViewModel { get; set; } |
||||
|
|
||||
|
object? IViewFor.ViewModel |
||||
|
{ |
||||
|
get => ViewModel; |
||||
|
set => ViewModel = (FooViewModel?)value; |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,32 +0,0 @@ |
|||||
using Avalonia.OpenGL; |
|
||||
using Avalonia.OpenGL.Egl; |
|
||||
using Avalonia.OpenGL.Surfaces; |
|
||||
|
|
||||
namespace Avalonia.Android.OpenGL |
|
||||
{ |
|
||||
internal sealed class GlPlatformSurface : EglGlPlatformSurfaceBase |
|
||||
{ |
|
||||
private readonly EglPlatformOpenGlInterface _egl; |
|
||||
private readonly IEglWindowGlPlatformSurfaceInfo _info; |
|
||||
|
|
||||
private GlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info) |
|
||||
{ |
|
||||
_egl = egl; |
|
||||
_info = info; |
|
||||
} |
|
||||
|
|
||||
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() => |
|
||||
new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle), _info.Handle); |
|
||||
|
|
||||
public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info) |
|
||||
{ |
|
||||
var feature = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>(); |
|
||||
if (feature is EglPlatformOpenGlInterface egl) |
|
||||
{ |
|
||||
return new GlPlatformSurface(egl, info); |
|
||||
} |
|
||||
|
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,30 +0,0 @@ |
|||||
using System; |
|
||||
|
|
||||
using Avalonia.OpenGL.Egl; |
|
||||
using Avalonia.OpenGL.Surfaces; |
|
||||
|
|
||||
namespace Avalonia.Android.OpenGL |
|
||||
{ |
|
||||
internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase, IGlPlatformSurfaceRenderTargetWithCorruptionInfo |
|
||||
{ |
|
||||
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; |
|
||||
private readonly EglSurface _surface; |
|
||||
private readonly IntPtr _handle; |
|
||||
|
|
||||
public GlRenderTarget( |
|
||||
EglPlatformOpenGlInterface egl, |
|
||||
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, |
|
||||
EglSurface surface, |
|
||||
IntPtr handle) |
|
||||
: base(egl) |
|
||||
{ |
|
||||
_info = info; |
|
||||
_surface = surface; |
|
||||
_handle = handle; |
|
||||
} |
|
||||
|
|
||||
public bool IsCorrupted => _handle != _info.Handle; |
|
||||
|
|
||||
public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,152 @@ |
|||||
|
using Avalonia.Input.GestureRecognizers; |
||||
|
|
||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
public class PullGestureRecognizer : StyledElement, IGestureRecognizer |
||||
|
{ |
||||
|
private IInputElement? _target; |
||||
|
private IGestureRecognizerActionsDispatcher? _actions; |
||||
|
private Point _initialPosition; |
||||
|
private int _gestureId; |
||||
|
private IPointer? _tracking; |
||||
|
private PullDirection _pullDirection; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="PullDirection"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<PullGestureRecognizer, PullDirection> PullDirectionProperty = |
||||
|
AvaloniaProperty.RegisterDirect<PullGestureRecognizer, PullDirection>( |
||||
|
nameof(PullDirection), |
||||
|
o => o.PullDirection, |
||||
|
(o, v) => o.PullDirection = v); |
||||
|
|
||||
|
public PullDirection PullDirection |
||||
|
{ |
||||
|
get => _pullDirection; |
||||
|
set => SetAndRaise(PullDirectionProperty, ref _pullDirection, value); |
||||
|
} |
||||
|
|
||||
|
public PullGestureRecognizer(PullDirection pullDirection) |
||||
|
{ |
||||
|
PullDirection = pullDirection; |
||||
|
} |
||||
|
|
||||
|
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) |
||||
|
{ |
||||
|
_target = target; |
||||
|
_actions = actions; |
||||
|
|
||||
|
_target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); |
||||
|
_target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); |
||||
|
} |
||||
|
|
||||
|
private void OnPointerPressed(object? sender, PointerPressedEventArgs e) |
||||
|
{ |
||||
|
PointerPressed(e); |
||||
|
} |
||||
|
|
||||
|
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) |
||||
|
{ |
||||
|
PointerReleased(e); |
||||
|
} |
||||
|
|
||||
|
public void PointerCaptureLost(IPointer pointer) |
||||
|
{ |
||||
|
if (_tracking == pointer) |
||||
|
{ |
||||
|
EndPull(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void PointerMoved(PointerEventArgs e) |
||||
|
{ |
||||
|
if (_tracking == e.Pointer && _target is Visual visual) |
||||
|
{ |
||||
|
var currentPosition = e.GetPosition(visual); |
||||
|
_actions!.Capture(e.Pointer, this); |
||||
|
|
||||
|
Vector delta = default; |
||||
|
switch (PullDirection) |
||||
|
{ |
||||
|
case PullDirection.TopToBottom: |
||||
|
if (currentPosition.Y > _initialPosition.Y) |
||||
|
{ |
||||
|
delta = new Vector(0, currentPosition.Y - _initialPosition.Y); |
||||
|
} |
||||
|
break; |
||||
|
case PullDirection.BottomToTop: |
||||
|
if (currentPosition.Y < _initialPosition.Y) |
||||
|
{ |
||||
|
delta = new Vector(0, _initialPosition.Y - currentPosition.Y); |
||||
|
} |
||||
|
break; |
||||
|
case PullDirection.LeftToRight: |
||||
|
if (currentPosition.X > _initialPosition.X) |
||||
|
{ |
||||
|
delta = new Vector(currentPosition.X - _initialPosition.X, 0); |
||||
|
} |
||||
|
break; |
||||
|
case PullDirection.RightToLeft: |
||||
|
if (currentPosition.X < _initialPosition.X) |
||||
|
{ |
||||
|
delta = new Vector(_initialPosition.X - currentPosition.X, 0); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
_target?.RaiseEvent(new PullGestureEventArgs(_gestureId, delta, PullDirection)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void PointerPressed(PointerPressedEventArgs e) |
||||
|
{ |
||||
|
if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) |
||||
|
{ |
||||
|
var position = e.GetPosition(visual); |
||||
|
|
||||
|
var canPull = false; |
||||
|
|
||||
|
var bounds = visual.Bounds; |
||||
|
|
||||
|
switch (PullDirection) |
||||
|
{ |
||||
|
case PullDirection.TopToBottom: |
||||
|
canPull = position.Y < bounds.Height * 0.1; |
||||
|
break; |
||||
|
case PullDirection.BottomToTop: |
||||
|
canPull = position.Y > bounds.Height - (bounds.Height * 0.1); |
||||
|
break; |
||||
|
case PullDirection.LeftToRight: |
||||
|
canPull = position.X < bounds.Width * 0.1; |
||||
|
break; |
||||
|
case PullDirection.RightToLeft: |
||||
|
canPull = position.X > bounds.Width - (bounds.Width * 0.1); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (canPull) |
||||
|
{ |
||||
|
_gestureId = PullGestureEventArgs.GetNextFreeId(); |
||||
|
_tracking = e.Pointer; |
||||
|
_initialPosition = position; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void PointerReleased(PointerReleasedEventArgs e) |
||||
|
{ |
||||
|
if (_tracking == e.Pointer) |
||||
|
{ |
||||
|
EndPull(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void EndPull() |
||||
|
{ |
||||
|
_tracking = null; |
||||
|
_initialPosition = default; |
||||
|
|
||||
|
_target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Interactivity; |
||||
|
|
||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
public class PullGestureEventArgs : RoutedEventArgs |
||||
|
{ |
||||
|
public int Id { get; } |
||||
|
public Vector Delta { get; } |
||||
|
public PullDirection PullDirection { get; } |
||||
|
|
||||
|
private static int _nextId = 1; |
||||
|
|
||||
|
internal static int GetNextFreeId() => _nextId++; |
||||
|
|
||||
|
public PullGestureEventArgs(int id, Vector delta, PullDirection pullDirection) : base(Gestures.PullGestureEvent) |
||||
|
{ |
||||
|
Id = id; |
||||
|
Delta = delta; |
||||
|
PullDirection = pullDirection; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class PullGestureEndedEventArgs : RoutedEventArgs |
||||
|
{ |
||||
|
public int Id { get; } |
||||
|
public PullDirection PullDirection { get; } |
||||
|
|
||||
|
public PullGestureEndedEventArgs(int id, PullDirection pullDirection) : base(Gestures.PullGestureEndedEvent) |
||||
|
{ |
||||
|
Id = id; |
||||
|
PullDirection = pullDirection; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public enum PullDirection |
||||
|
{ |
||||
|
TopToBottom, |
||||
|
BottomToTop, |
||||
|
LeftToRight, |
||||
|
RightToLeft |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,293 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Media.TextFormatting |
||||
|
{ |
||||
|
public readonly struct CharacterBufferRange : IReadOnlyList<char> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Getting an empty character string
|
||||
|
/// </summary>
|
||||
|
public static CharacterBufferRange Empty => new CharacterBufferRange(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Construct <see cref="CharacterBufferRange"/> from character array
|
||||
|
/// </summary>
|
||||
|
/// <param name="characterArray">character array</param>
|
||||
|
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
||||
|
/// <param name="characterLength">character length</param>
|
||||
|
public CharacterBufferRange( |
||||
|
char[] characterArray, |
||||
|
int offsetToFirstChar, |
||||
|
int characterLength |
||||
|
) |
||||
|
: this( |
||||
|
new CharacterBufferReference(characterArray, offsetToFirstChar), |
||||
|
characterLength |
||||
|
) |
||||
|
{ } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Construct <see cref="CharacterBufferRange"/> from string
|
||||
|
/// </summary>
|
||||
|
/// <param name="characterString">character string</param>
|
||||
|
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
||||
|
/// <param name="characterLength">character length</param>
|
||||
|
public CharacterBufferRange( |
||||
|
string characterString, |
||||
|
int offsetToFirstChar, |
||||
|
int characterLength |
||||
|
) |
||||
|
: this( |
||||
|
new CharacterBufferReference(characterString, offsetToFirstChar), |
||||
|
characterLength |
||||
|
) |
||||
|
{ } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Construct a <see cref="CharacterBufferRange"/> from <see cref="CharacterBufferReference"/>
|
||||
|
/// </summary>
|
||||
|
/// <param name="characterBufferReference">character buffer reference</param>
|
||||
|
/// <param name="characterLength">number of characters</param>
|
||||
|
public CharacterBufferRange( |
||||
|
CharacterBufferReference characterBufferReference, |
||||
|
int characterLength |
||||
|
) |
||||
|
{ |
||||
|
if (characterLength < 0) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException("characterLength", "ParameterCannotBeNegative"); |
||||
|
} |
||||
|
|
||||
|
int maxLength = characterBufferReference.CharacterBuffer.Length > 0 ? |
||||
|
characterBufferReference.CharacterBuffer.Length - characterBufferReference.OffsetToFirstChar : |
||||
|
0; |
||||
|
|
||||
|
if (characterLength > maxLength) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException("characterLength", $"ParameterCannotBeGreaterThan {maxLength}"); |
||||
|
} |
||||
|
|
||||
|
CharacterBufferReference = characterBufferReference; |
||||
|
Length = characterLength; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Construct a <see cref="CharacterBufferRange"/> from part of another <see cref="CharacterBufferRange"/>
|
||||
|
/// </summary>
|
||||
|
internal CharacterBufferRange( |
||||
|
CharacterBufferRange characterBufferRange, |
||||
|
int offsetToFirstChar, |
||||
|
int characterLength |
||||
|
) : |
||||
|
this( |
||||
|
characterBufferRange.CharacterBuffer, |
||||
|
characterBufferRange.OffsetToFirstChar + offsetToFirstChar, |
||||
|
characterLength |
||||
|
) |
||||
|
{ } |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Construct a <see cref="CharacterBufferRange"/> from string
|
||||
|
/// </summary>
|
||||
|
internal CharacterBufferRange( |
||||
|
string charString |
||||
|
) : |
||||
|
this( |
||||
|
charString, |
||||
|
0, |
||||
|
charString.Length |
||||
|
) |
||||
|
{ } |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Construct <see cref="CharacterBufferRange"/> from memory buffer
|
||||
|
/// </summary>
|
||||
|
internal CharacterBufferRange( |
||||
|
ReadOnlyMemory<char> charBuffer, |
||||
|
int offsetToFirstChar, |
||||
|
int characterLength |
||||
|
) : |
||||
|
this( |
||||
|
new CharacterBufferReference(charBuffer, offsetToFirstChar), |
||||
|
characterLength |
||||
|
) |
||||
|
{ } |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Construct a <see cref="CharacterBufferRange"/> by extracting text info from a text run
|
||||
|
/// </summary>
|
||||
|
internal CharacterBufferRange(TextRun textRun) |
||||
|
{ |
||||
|
CharacterBufferReference = textRun.CharacterBufferReference; |
||||
|
Length = textRun.Length; |
||||
|
} |
||||
|
|
||||
|
public char this[int index] |
||||
|
{ |
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
get |
||||
|
{ |
||||
|
#if DEBUG
|
||||
|
if (index.CompareTo(0) < 0 || index.CompareTo(Length) > 0) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException(nameof(index)); |
||||
|
} |
||||
|
#endif
|
||||
|
return Span[index]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a reference to the character buffer
|
||||
|
/// </summary>
|
||||
|
public CharacterBufferReference CharacterBufferReference { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of characters in text source character store
|
||||
|
/// </summary>
|
||||
|
public int Length { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a span from the character buffer range
|
||||
|
/// </summary>
|
||||
|
public ReadOnlySpan<char> Span => |
||||
|
CharacterBufferReference.CharacterBuffer.Span.Slice(CharacterBufferReference.OffsetToFirstChar, Length); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the character memory buffer
|
||||
|
/// </summary>
|
||||
|
internal ReadOnlyMemory<char> CharacterBuffer |
||||
|
{ |
||||
|
get { return CharacterBufferReference.CharacterBuffer; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the character offset relative to the beginning of buffer to
|
||||
|
/// the first character of the run
|
||||
|
/// </summary>
|
||||
|
internal int OffsetToFirstChar |
||||
|
{ |
||||
|
get { return CharacterBufferReference.OffsetToFirstChar; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicate whether the character buffer range is empty
|
||||
|
/// </summary>
|
||||
|
internal bool IsEmpty |
||||
|
{ |
||||
|
get { return CharacterBufferReference.CharacterBuffer.Length == 0 || Length <= 0; } |
||||
|
} |
||||
|
|
||||
|
internal CharacterBufferRange Take(int length) |
||||
|
{ |
||||
|
if (IsEmpty) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
if (length > Length) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException(nameof(length)); |
||||
|
} |
||||
|
|
||||
|
return new CharacterBufferRange(CharacterBufferReference, length); |
||||
|
} |
||||
|
|
||||
|
internal CharacterBufferRange Skip(int length) |
||||
|
{ |
||||
|
if (IsEmpty) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
if (length > Length) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException(nameof(length)); |
||||
|
} |
||||
|
|
||||
|
if (length == Length) |
||||
|
{ |
||||
|
return new CharacterBufferRange(new CharacterBufferReference(), 0); |
||||
|
} |
||||
|
|
||||
|
var characterBufferReference = new CharacterBufferReference( |
||||
|
CharacterBufferReference.CharacterBuffer, |
||||
|
CharacterBufferReference.OffsetToFirstChar + length); |
||||
|
|
||||
|
return new CharacterBufferRange(characterBufferReference, Length - length); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compute hash code
|
||||
|
/// </summary>
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
return CharacterBufferReference.GetHashCode() ^ Length; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Test equality with the input object
|
||||
|
/// </summary>
|
||||
|
/// <param name="obj"> The object to test </param>
|
||||
|
public override bool Equals(object? obj) |
||||
|
{ |
||||
|
if (obj is CharacterBufferRange range) |
||||
|
{ |
||||
|
return Equals(range); |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Test equality with the input CharacterBufferRange
|
||||
|
/// </summary>
|
||||
|
/// <param name="value"> The CharacterBufferRange value to test </param>
|
||||
|
public bool Equals(CharacterBufferRange value) |
||||
|
{ |
||||
|
return CharacterBufferReference.Equals(value.CharacterBufferReference) |
||||
|
&& Length == value.Length; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compare two CharacterBufferRange for equality
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">left operand</param>
|
||||
|
/// <param name="right">right operand</param>
|
||||
|
/// <returns>whether or not two operands are equal</returns>
|
||||
|
public static bool operator ==(CharacterBufferRange left, CharacterBufferRange right) |
||||
|
{ |
||||
|
return left.Equals(right); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compare two CharacterBufferRange for inequality
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">left operand</param>
|
||||
|
/// <param name="right">right operand</param>
|
||||
|
/// <returns>whether or not two operands are equal</returns>
|
||||
|
public static bool operator !=(CharacterBufferRange left, CharacterBufferRange right) |
||||
|
{ |
||||
|
return !(left == right); |
||||
|
} |
||||
|
|
||||
|
int IReadOnlyCollection<char>.Count => Length; |
||||
|
|
||||
|
public IEnumerator<char> GetEnumerator() |
||||
|
{ |
||||
|
return new ImmutableReadOnlyListStructEnumerator<char>(this); |
||||
|
} |
||||
|
|
||||
|
IEnumerator IEnumerable.GetEnumerator() |
||||
|
{ |
||||
|
return GetEnumerator(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Media.TextFormatting |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Text character buffer reference
|
||||
|
/// </summary>
|
||||
|
public readonly struct CharacterBufferReference : IEquatable<CharacterBufferReference> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Construct character buffer reference from character array
|
||||
|
/// </summary>
|
||||
|
/// <param name="characterArray">character array</param>
|
||||
|
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
||||
|
public CharacterBufferReference(char[] characterArray, int offsetToFirstChar = 0) |
||||
|
: this(characterArray.AsMemory(), offsetToFirstChar) |
||||
|
{ } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Construct character buffer reference from string
|
||||
|
/// </summary>
|
||||
|
/// <param name="characterString">character string</param>
|
||||
|
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
||||
|
public CharacterBufferReference(string characterString, int offsetToFirstChar = 0) |
||||
|
: this(characterString.AsMemory(), offsetToFirstChar) |
||||
|
{ } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Construct character buffer reference from memory buffer
|
||||
|
/// </summary>
|
||||
|
internal CharacterBufferReference(ReadOnlyMemory<char> characterBuffer, int offsetToFirstChar = 0) |
||||
|
{ |
||||
|
if (offsetToFirstChar < 0) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException("offsetToFirstChar", "ParameterCannotBeNegative"); |
||||
|
} |
||||
|
|
||||
|
// maximum offset is one less than CharacterBuffer.Count, except that zero is always a valid offset
|
||||
|
// even in the case of an empty or null character buffer
|
||||
|
var maxOffset = characterBuffer.Length == 0 ? 0 : Math.Max(0, characterBuffer.Length - 1); |
||||
|
if (offsetToFirstChar > maxOffset) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException("offsetToFirstChar", $"ParameterCannotBeGreaterThan, {maxOffset}"); |
||||
|
} |
||||
|
|
||||
|
CharacterBuffer = characterBuffer; |
||||
|
OffsetToFirstChar = offsetToFirstChar; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the character memory buffer
|
||||
|
/// </summary>
|
||||
|
public ReadOnlyMemory<char> CharacterBuffer { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the character offset relative to the beginning of buffer to
|
||||
|
/// the first character of the run
|
||||
|
/// </summary>
|
||||
|
public int OffsetToFirstChar { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compute hash code
|
||||
|
/// </summary>
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
return CharacterBuffer.IsEmpty ? 0 : CharacterBuffer.GetHashCode(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Test equality with the input object
|
||||
|
/// </summary>
|
||||
|
/// <param name="obj"> The object to test. </param>
|
||||
|
public override bool Equals(object? obj) |
||||
|
{ |
||||
|
if (obj is CharacterBufferReference reference) |
||||
|
{ |
||||
|
return Equals(reference); |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Test equality with the input CharacterBufferReference
|
||||
|
/// </summary>
|
||||
|
/// <param name="value"> The characterBufferReference value to test </param>
|
||||
|
public bool Equals(CharacterBufferReference value) |
||||
|
{ |
||||
|
return CharacterBuffer.Equals(value.CharacterBuffer); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compare two CharacterBufferReference for equality
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">left operand</param>
|
||||
|
/// <param name="right">right operand</param>
|
||||
|
/// <returns>whether or not two operands are equal</returns>
|
||||
|
public static bool operator ==(CharacterBufferReference left, CharacterBufferReference right) |
||||
|
{ |
||||
|
return left.Equals(right); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compare two CharacterBufferReference for inequality
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">left operand</param>
|
||||
|
/// <param name="right">right operand</param>
|
||||
|
/// <returns>whether or not two operands are equal</returns>
|
||||
|
public static bool operator !=(CharacterBufferReference left, CharacterBufferReference right) |
||||
|
{ |
||||
|
return !(left == right); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,18 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Platform; |
||||
|
|
||||
|
public interface IOptionalFeatureProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Queries for an optional feature
|
||||
|
/// </summary>
|
||||
|
/// <param name="featureType">Feature type</param>
|
||||
|
public object? TryGetFeature(Type featureType); |
||||
|
} |
||||
|
|
||||
|
public static class OptionalFeatureProviderExtensions |
||||
|
{ |
||||
|
public static T? TryGetFeature<T>(this IOptionalFeatureProvider provider) where T : class => |
||||
|
(T?)provider.TryGetFeature(typeof(T)); |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Rendering; |
||||
|
|
||||
|
struct OwnedDisposable<T> :IDisposable where T : class, IDisposable |
||||
|
{ |
||||
|
private readonly bool _owns; |
||||
|
private T? _value; |
||||
|
|
||||
|
public T Value => _value ?? throw new ObjectDisposedException("OwnedDisposable"); |
||||
|
|
||||
|
public OwnedDisposable(T value, bool owns) |
||||
|
{ |
||||
|
_owns = owns; |
||||
|
_value = value; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
if(_owns) |
||||
|
_value?.Dispose(); |
||||
|
_value = null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Reactive.Disposables; |
||||
|
using Avalonia.Metadata; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Rendering; |
||||
|
|
||||
|
[Unstable] |
||||
|
// TODO: Make it internal once legacy renderers are removed
|
||||
|
public class PlatformRenderInterfaceContextManager |
||||
|
{ |
||||
|
private readonly IPlatformGraphics? _graphics; |
||||
|
private IPlatformRenderInterfaceContext? _backend; |
||||
|
private OwnedDisposable<IPlatformGraphicsContext>? _gpuContext; |
||||
|
|
||||
|
public PlatformRenderInterfaceContextManager(IPlatformGraphics? graphics) |
||||
|
{ |
||||
|
_graphics = graphics; |
||||
|
} |
||||
|
|
||||
|
public void EnsureValidBackendContext() |
||||
|
{ |
||||
|
if (_backend == null || _gpuContext?.Value.IsLost == true) |
||||
|
{ |
||||
|
_backend?.Dispose(); |
||||
|
_backend = null; |
||||
|
_gpuContext?.Dispose(); |
||||
|
_gpuContext = null; |
||||
|
|
||||
|
if (_graphics != null) |
||||
|
{ |
||||
|
if (_graphics.UsesSharedContext) |
||||
|
_gpuContext = new OwnedDisposable<IPlatformGraphicsContext>(_graphics.GetSharedContext(), false); |
||||
|
else |
||||
|
_gpuContext = new OwnedDisposable<IPlatformGraphicsContext>(_graphics.CreateContext(), true); |
||||
|
} |
||||
|
|
||||
|
_backend = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>() |
||||
|
.CreateBackendContext(_gpuContext?.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IPlatformRenderInterfaceContext Value |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
EnsureValidBackendContext(); |
||||
|
return _backend!; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IDisposable EnsureCurrent() |
||||
|
{ |
||||
|
EnsureValidBackendContext(); |
||||
|
if (_gpuContext.HasValue) |
||||
|
return _gpuContext.Value.Value.EnsureCurrent(); |
||||
|
return Disposable.Empty; |
||||
|
} |
||||
|
|
||||
|
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) |
||||
|
{ |
||||
|
EnsureValidBackendContext(); |
||||
|
return _backend!.CreateRenderTarget(surfaces); |
||||
|
} |
||||
|
} |
||||
@ -1,239 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Diagnostics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
|
|
||||
namespace Avalonia.Utilities |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// ReadOnlySlice enables the ability to work with a sequence within a region of memory and retains the position in within that region.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The type of elements in the slice.</typeparam>
|
|
||||
[DebuggerTypeProxy(typeof(ReadOnlySlice<>.ReadOnlySliceDebugView))] |
|
||||
public readonly record struct ReadOnlySlice<T> : IReadOnlyList<T> where T : struct |
|
||||
{ |
|
||||
private readonly int _bufferOffset; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets an empty <see cref="ReadOnlySlice{T}"/>
|
|
||||
/// </summary>
|
|
||||
public static ReadOnlySlice<T> Empty => new ReadOnlySlice<T>(Array.Empty<T>()); |
|
||||
|
|
||||
private readonly ReadOnlyMemory<T> _buffer; |
|
||||
|
|
||||
public ReadOnlySlice(ReadOnlyMemory<T> buffer) : this(buffer, 0, buffer.Length) { } |
|
||||
|
|
||||
public ReadOnlySlice(ReadOnlyMemory<T> buffer, int start, int length, int bufferOffset = 0) |
|
||||
{ |
|
||||
#if DEBUG
|
|
||||
if (start.CompareTo(0) < 0) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof (start)); |
|
||||
} |
|
||||
|
|
||||
if (length.CompareTo(buffer.Length) > 0) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof (length)); |
|
||||
} |
|
||||
#endif
|
|
||||
|
|
||||
_buffer = buffer; |
|
||||
Start = start; |
|
||||
Length = length; |
|
||||
_bufferOffset = bufferOffset; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the start.
|
|
||||
/// </summary>
|
|
||||
/// <value>
|
|
||||
/// The start.
|
|
||||
/// </value>
|
|
||||
public int Start { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the end.
|
|
||||
/// </summary>
|
|
||||
/// <value>
|
|
||||
/// The end.
|
|
||||
/// </value>
|
|
||||
public int End => Start + Length - 1; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the length.
|
|
||||
/// </summary>
|
|
||||
/// <value>
|
|
||||
/// The length.
|
|
||||
/// </value>
|
|
||||
public int Length { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets a value that indicates whether this instance of <see cref="ReadOnlySlice{T}"/> is Empty.
|
|
||||
/// </summary>
|
|
||||
public bool IsEmpty => Length == 0; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get the underlying span.
|
|
||||
/// </summary>
|
|
||||
public ReadOnlySpan<T> Span => _buffer.Span.Slice(_bufferOffset, Length); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get the buffer offset.
|
|
||||
/// </summary>
|
|
||||
public int BufferOffset => _bufferOffset; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get the underlying buffer.
|
|
||||
/// </summary>
|
|
||||
public ReadOnlyMemory<T> Buffer => _buffer; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Returns a value to specified element of the slice.
|
|
||||
/// </summary>
|
|
||||
/// <param name="index">The index of the element to return.</param>
|
|
||||
/// <returns>The <typeparamref name="T"/>.</returns>
|
|
||||
/// <exception cref="IndexOutOfRangeException">
|
|
||||
/// Thrown when index less than 0 or index greater than or equal to <see cref="Length"/>.
|
|
||||
/// </exception>
|
|
||||
public T this[int index] |
|
||||
{ |
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
get |
|
||||
{ |
|
||||
#if DEBUG
|
|
||||
if (index.CompareTo(0) < 0 || index.CompareTo(Length) > 0) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof (index)); |
|
||||
} |
|
||||
#endif
|
|
||||
return Span[index]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Returns a sub slice of elements that start at the specified index and has the specified number of elements.
|
|
||||
/// </summary>
|
|
||||
/// <param name="start">The start of the sub slice.</param>
|
|
||||
/// <param name="length">The length of the sub slice.</param>
|
|
||||
/// <returns>A <see cref="ReadOnlySlice{T}"/> that contains the specified number of elements from the specified start.</returns>
|
|
||||
public ReadOnlySlice<T> AsSlice(int start, int length) |
|
||||
{ |
|
||||
if (IsEmpty) |
|
||||
{ |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
if (length == 0) |
|
||||
{ |
|
||||
return Empty; |
|
||||
} |
|
||||
|
|
||||
if (start < 0 || _bufferOffset + start > _buffer.Length - 1) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof(start)); |
|
||||
} |
|
||||
|
|
||||
if (_bufferOffset + start + length > _buffer.Length) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof(length)); |
|
||||
} |
|
||||
|
|
||||
return new ReadOnlySlice<T>(_buffer, start, length, _bufferOffset); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Returns a specified number of contiguous elements from the start of the slice.
|
|
||||
/// </summary>
|
|
||||
/// <param name="length">The number of elements to return.</param>
|
|
||||
/// <returns>A <see cref="ReadOnlySlice{T}"/> that contains the specified number of elements from the start of this slice.</returns>
|
|
||||
public ReadOnlySlice<T> Take(int length) |
|
||||
{ |
|
||||
if (IsEmpty) |
|
||||
{ |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
if (length > Length) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof(length)); |
|
||||
} |
|
||||
|
|
||||
return new ReadOnlySlice<T>(_buffer, Start, length, _bufferOffset); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Bypasses a specified number of elements in the slice and then returns the remaining elements.
|
|
||||
/// </summary>
|
|
||||
/// <param name="length">The number of elements to skip before returning the remaining elements.</param>
|
|
||||
/// <returns>A <see cref="ReadOnlySlice{T}"/> that contains the elements that occur after the specified index in this slice.</returns>
|
|
||||
public ReadOnlySlice<T> Skip(int length) |
|
||||
{ |
|
||||
if (IsEmpty) |
|
||||
{ |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
if (length > Length) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof(length)); |
|
||||
} |
|
||||
|
|
||||
return new ReadOnlySlice<T>(_buffer, Start + length, Length - length, _bufferOffset + length); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Returns an enumerator for the slice.
|
|
||||
/// </summary>
|
|
||||
public ImmutableReadOnlyListStructEnumerator<T> GetEnumerator() |
|
||||
{ |
|
||||
return new ImmutableReadOnlyListStructEnumerator<T>(this); |
|
||||
} |
|
||||
|
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() |
|
||||
{ |
|
||||
return GetEnumerator(); |
|
||||
} |
|
||||
|
|
||||
IEnumerator IEnumerable.GetEnumerator() |
|
||||
{ |
|
||||
return GetEnumerator(); |
|
||||
} |
|
||||
|
|
||||
int IReadOnlyCollection<T>.Count => Length; |
|
||||
|
|
||||
T IReadOnlyList<T>.this[int index] => this[index]; |
|
||||
|
|
||||
public static implicit operator ReadOnlySlice<T>(T[] array) |
|
||||
{ |
|
||||
return new ReadOnlySlice<T>(array); |
|
||||
} |
|
||||
|
|
||||
public static implicit operator ReadOnlySlice<T>(ReadOnlyMemory<T> memory) |
|
||||
{ |
|
||||
return new ReadOnlySlice<T>(memory); |
|
||||
} |
|
||||
|
|
||||
public static implicit operator ReadOnlySpan<T>(ReadOnlySlice<T> slice) => slice.Span; |
|
||||
|
|
||||
internal class ReadOnlySliceDebugView |
|
||||
{ |
|
||||
private readonly ReadOnlySlice<T> _readOnlySlice; |
|
||||
|
|
||||
public ReadOnlySliceDebugView(ReadOnlySlice<T> readOnlySlice) |
|
||||
{ |
|
||||
_readOnlySlice = readOnlySlice; |
|
||||
} |
|
||||
|
|
||||
public int Start => _readOnlySlice.Start; |
|
||||
|
|
||||
public int End => _readOnlySlice.End; |
|
||||
|
|
||||
public int Length => _readOnlySlice.Length; |
|
||||
|
|
||||
public bool IsEmpty => _readOnlySlice.IsEmpty; |
|
||||
|
|
||||
public ReadOnlySpan<T> Items => _readOnlySlice.Span; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue