committed by
GitHub
870 changed files with 24585 additions and 19604 deletions
@ -1,7 +1,7 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="HarfBuzzSharp" Version="2.8.2-preview.171" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2-preview.171" /> |
|||
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2-preview.171"/> |
|||
<PackageReference Include="HarfBuzzSharp" Version="2.8.2-preview.178" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2-preview.178" /> |
|||
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2-preview.178"/> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,35 +0,0 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
|
|||
<!-- Ensure that code generator is actually built --> |
|||
<ItemGroup> |
|||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\tools\MicroComGenerator\MicroComGenerator.csproj"> |
|||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly> |
|||
<ExcludeAssets>all</ExcludeAssets> |
|||
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties> |
|||
<SetTargetFramework>TargetFramework=net6.0</SetTargetFramework> |
|||
</ProjectReference> |
|||
</ItemGroup> |
|||
|
|||
<Target Name="GenerateAvaloniaNativeComInterop" |
|||
BeforeTargets="CoreCompile" |
|||
DependsOnTargets="ResolveReferences" |
|||
Inputs="@(AvnComIdl);$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/**/*.cs" |
|||
Outputs="%(AvnComIdl.OutputFile)"> |
|||
<Message Importance="high" Text="Generating file %(AvnComIdl.OutputFile) from @(AvnComIdl)" /> |
|||
<Exec Command="dotnet "$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/bin/$(Configuration)/net6.0/MicroComGenerator.dll" -i @(AvnComIdl) --cs %(AvnComIdl.OutputFile)" |
|||
LogStandardErrorAsError="true" /> |
|||
<ItemGroup> |
|||
<!-- Remove and re-add generated file, this is needed for the clean build --> |
|||
<Compile Remove="%(AvnComIdl.OutputFile)"/> |
|||
<Compile Include="%(AvnComIdl.OutputFile)"/> |
|||
</ItemGroup> |
|||
</Target> |
|||
<ItemGroup> |
|||
<UpToDateCheckInput Include="@(AvnComIdl)"/> |
|||
<UpToDateCheckInput Include="$(MSBuildThisFileDirectory)/../src/tools/MicroComGenerator/**/*.cs"/> |
|||
</ItemGroup> |
|||
<PropertyGroup> |
|||
<_AvaloniaPatchComInterop>true</_AvaloniaPatchComInterop> |
|||
</PropertyGroup> |
|||
<Import Project="$(MSBuildThisFileDirectory)/BuildTargets.targets" /> |
|||
</Project> |
|||
@ -1,7 +1,7 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.171" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.0-preview.171" /> |
|||
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.0-preview.171"/> |
|||
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.178" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.0-preview.178" /> |
|||
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.0-preview.178"/> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,14 +1,14 @@ |
|||
using System.IO; |
|||
using MicroComGenerator; |
|||
using MicroCom.CodeGenerator; |
|||
using Nuke.Common; |
|||
|
|||
partial class Build : NukeBuild |
|||
{ |
|||
Target GenerateCppHeaders => _ => _.Executes(() => |
|||
{ |
|||
var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"); |
|||
var ast = AstParser.Parse(text); |
|||
var file = MicroComCodeGenerator.Parse( |
|||
File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl")); |
|||
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h", |
|||
CppGen.GenerateCpp(ast)); |
|||
file.GenerateCppHeader()); |
|||
}); |
|||
} |
|||
@ -1,77 +1,95 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.ComboBoxPage" |
|||
xmlns:sys="using:System" |
|||
xmlns:col="using:System.Collections"> |
|||
<StackPanel Orientation="Vertical" Spacing="4"> |
|||
<TextBlock Classes="h2">A drop-down list.</TextBlock> |
|||
<UserControl |
|||
x:Class="ControlCatalog.Pages.ComboBoxPage" |
|||
xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:col="using:System.Collections" |
|||
xmlns:sys="using:System"> |
|||
<StackPanel Orientation="Vertical" Spacing="4"> |
|||
<TextBlock Classes="h2">A drop-down list.</TextBlock> |
|||
|
|||
<WrapPanel HorizontalAlignment="Center" Margin="0 16 0 0" |
|||
MaxWidth="660"> |
|||
<WrapPanel.Styles> |
|||
<Style Selector="ComboBox"> |
|||
<Setter Property="Width" Value="250" /> |
|||
<Setter Property="Margin" Value="10" /> |
|||
</Style> |
|||
</WrapPanel.Styles> |
|||
<ComboBox PlaceholderText="Pick an Item"> |
|||
<ComboBoxItem>Inline Items</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 2</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 3</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 4</ComboBoxItem> |
|||
</ComboBox> |
|||
<StackPanel |
|||
Margin="0,16,0,0" |
|||
HorizontalAlignment="Center" |
|||
Orientation="Horizontal" |
|||
Spacing="8"> |
|||
<WrapPanel |
|||
MaxWidth="660" |
|||
Margin="0,16,0,0" |
|||
HorizontalAlignment="Center"> |
|||
<WrapPanel.Styles> |
|||
<Style Selector="ComboBox"> |
|||
<Setter Property="Width" Value="250" /> |
|||
<Setter Property="Margin" Value="10" /> |
|||
</Style> |
|||
</WrapPanel.Styles> |
|||
|
|||
<ComboBox> |
|||
<ComboBox.Items> |
|||
<col:ArrayList> |
|||
<x:Null /> |
|||
<sys:String>Hello</sys:String> |
|||
<sys:String>World</sys:String> |
|||
</col:ArrayList> |
|||
</ComboBox.Items> |
|||
<ComboBox.ItemTemplate> |
|||
<DataTemplate> |
|||
<Panel> |
|||
<TextBlock Text="{Binding}" /> |
|||
<TextBlock Text="Null object" IsVisible="{Binding Converter={x:Static ObjectConverters.IsNull}}" /> |
|||
</Panel> |
|||
</DataTemplate> |
|||
</ComboBox.ItemTemplate> |
|||
</ComboBox> |
|||
<ComboBox PlaceholderText="Pick an Item" WrapSelection="{Binding WrapSelection}"> |
|||
<ComboBoxItem>Inline Items</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 2</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 3</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 4</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<ComboBox SelectedIndex="0"> |
|||
<ComboBoxItem> |
|||
<Panel> |
|||
<Rectangle Fill="{DynamicResource SystemAccentColor}"/> |
|||
<TextBlock Margin="8">Control Items</TextBlock> |
|||
</Panel> |
|||
</ComboBoxItem> |
|||
<ComboBoxItem> |
|||
<Ellipse Width="50" Height="50" Fill="Yellow"/> |
|||
</ComboBoxItem> |
|||
<ComboBoxItem> |
|||
<TextBox Text="TextBox"/> |
|||
</ComboBoxItem> |
|||
</ComboBox> |
|||
<ComboBox WrapSelection="{Binding WrapSelection}"> |
|||
<ComboBox.Items> |
|||
<col:ArrayList> |
|||
<x:Null /> |
|||
<sys:String>Hello</sys:String> |
|||
<sys:String>World</sys:String> |
|||
</col:ArrayList> |
|||
</ComboBox.Items> |
|||
<ComboBox.ItemTemplate> |
|||
<DataTemplate> |
|||
<Panel> |
|||
<TextBlock Text="{Binding}" /> |
|||
<TextBlock IsVisible="{Binding Converter={x:Static ObjectConverters.IsNull}}" Text="Null object" /> |
|||
</Panel> |
|||
</DataTemplate> |
|||
</ComboBox.ItemTemplate> |
|||
</ComboBox> |
|||
|
|||
<ComboBox x:Name="fontComboBox" SelectedIndex="0"> |
|||
<ComboBox.ItemTemplate> |
|||
<DataTemplate> |
|||
<TextBlock Text="{Binding Name}" FontFamily="{Binding}" /> |
|||
</DataTemplate> |
|||
</ComboBox.ItemTemplate> |
|||
</ComboBox> |
|||
|
|||
<ComboBox PlaceholderText="Pick an Item"> |
|||
<ComboBoxItem>Inline Items</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 2</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 3</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 4</ComboBoxItem> |
|||
<DataValidationErrors.Error> |
|||
<sys:Exception /> |
|||
</DataValidationErrors.Error> |
|||
</ComboBox> |
|||
</WrapPanel> |
|||
<ComboBox SelectedIndex="0" WrapSelection="{Binding WrapSelection}"> |
|||
<ComboBoxItem> |
|||
<Panel> |
|||
<Rectangle Fill="{DynamicResource SystemAccentColor}" /> |
|||
<TextBlock Margin="8">Control Items</TextBlock> |
|||
</Panel> |
|||
</ComboBoxItem> |
|||
<ComboBoxItem> |
|||
<Ellipse |
|||
Width="50" |
|||
Height="50" |
|||
Fill="Yellow" /> |
|||
</ComboBoxItem> |
|||
<ComboBoxItem> |
|||
<TextBox Text="TextBox" /> |
|||
</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
</StackPanel> |
|||
<ComboBox |
|||
x:Name="fontComboBox" |
|||
SelectedIndex="0" |
|||
WrapSelection="{Binding WrapSelection}"> |
|||
<ComboBox.ItemTemplate> |
|||
<DataTemplate> |
|||
<TextBlock FontFamily="{Binding}" Text="{Binding Name}" /> |
|||
</DataTemplate> |
|||
</ComboBox.ItemTemplate> |
|||
</ComboBox> |
|||
|
|||
<ComboBox PlaceholderText="Pick an Item" WrapSelection="{Binding WrapSelection}"> |
|||
<ComboBoxItem>Inline Items</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 2</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 3</ComboBoxItem> |
|||
<ComboBoxItem>Inline Item 4</ComboBoxItem> |
|||
<DataValidationErrors.Error> |
|||
<sys:Exception /> |
|||
</DataValidationErrors.Error> |
|||
</ComboBox> |
|||
</WrapPanel> |
|||
|
|||
<CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox> |
|||
|
|||
</StackPanel> |
|||
</StackPanel> |
|||
</UserControl> |
|||
|
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
using System.Collections.ObjectModel; |
|||
using System.Linq; |
|||
using System.Reactive; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Selection; |
|||
using MiniMvvm; |
|||
|
|||
namespace ControlCatalog.ViewModels |
|||
{ |
|||
public class ComboBoxPageViewModel : ViewModelBase |
|||
{ |
|||
private bool _wrapSelection; |
|||
|
|||
public bool WrapSelection |
|||
{ |
|||
get => _wrapSelection; |
|||
set => this.RaiseAndSetIfChanged(ref _wrapSelection, value); |
|||
} |
|||
} |
|||
} |
|||
@ -1,35 +1,18 @@ |
|||
using System; |
|||
using System.Reactive.Linq; |
|||
using Avalonia; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data; |
|||
using Avalonia.Markup.Xaml; |
|||
using Avalonia.Media; |
|||
|
|||
namespace RenderDemo.Pages |
|||
{ |
|||
public class ClippingPage : UserControl |
|||
{ |
|||
private Geometry _clip; |
|||
|
|||
public ClippingPage() |
|||
{ |
|||
InitializeComponent(); |
|||
WireUpCheckbox(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
private void WireUpCheckbox() |
|||
{ |
|||
var useMask = this.FindControl<CheckBox>("useMask"); |
|||
var clipped = this.FindControl<Border>("clipped"); |
|||
_clip = clipped.Clip; |
|||
useMask.Click += (s, e) => clipped.Clip = clipped.Clip == null ? _clip : null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,7 @@ |
|||
<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="RenderDemo.Pages.FormattedTextPage"> |
|||
</UserControl> |
|||
@ -0,0 +1,60 @@ |
|||
using System.Globalization; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
using Avalonia.Media; |
|||
|
|||
namespace RenderDemo.Pages |
|||
{ |
|||
public class FormattedTextPage : UserControl |
|||
{ |
|||
public FormattedTextPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
const string testString = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor"; |
|||
|
|||
// Create the initial formatted text string.
|
|||
var formattedText = new FormattedText( |
|||
testString, |
|||
CultureInfo.GetCultureInfo("en-us"), |
|||
FlowDirection.LeftToRight, |
|||
new Typeface("Verdana"), |
|||
32, |
|||
Brushes.Black) { MaxTextWidth = 300, MaxTextHeight = 240 }; |
|||
|
|||
// Set a maximum width and height. If the text overflows these values, an ellipsis "..." appears.
|
|||
|
|||
// Use a larger font size beginning at the first (zero-based) character and continuing for 5 characters.
|
|||
// The font size is calculated in terms of points -- not as device-independent pixels.
|
|||
formattedText.SetFontSize(36 * (96.0 / 72.0), 0, 5); |
|||
|
|||
// Use a Bold font weight beginning at the 6th character and continuing for 11 characters.
|
|||
formattedText.SetFontWeight(FontWeight.Bold, 6, 11); |
|||
|
|||
var gradient = new LinearGradientBrush |
|||
{ |
|||
GradientStops = |
|||
new GradientStops { new GradientStop(Colors.Orange, 0), new GradientStop(Colors.Teal, 1) }, |
|||
StartPoint = new RelativePoint(0,0, RelativeUnit.Relative), |
|||
EndPoint = new RelativePoint(0,1, RelativeUnit.Relative) |
|||
}; |
|||
|
|||
// Use a linear gradient brush beginning at the 6th character and continuing for 11 characters.
|
|||
formattedText.SetForegroundBrush(gradient, 6, 11); |
|||
|
|||
// Use an Italic font style beginning at the 28th character and continuing for 28 characters.
|
|||
formattedText.SetFontStyle(FontStyle.Italic, 28, 28); |
|||
|
|||
context.DrawText(formattedText, new Point(10, 0)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Shared.PlatformSupport |
|||
{ |
|||
internal partial class StandardRuntimePlatform |
|||
{ |
|||
public RuntimePlatformInfo GetRuntimeInfo() => new RuntimePlatformInfo |
|||
{ |
|||
IsCoreClr = false, |
|||
IsDesktop = false, |
|||
IsMobile = true, |
|||
IsDotNetFramework = false, |
|||
IsMono = true, |
|||
IsUnix = true, |
|||
OperatingSystem = OperatingSystemType.Android |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
using System; |
|||
using System.IO; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
class WindowingPlatformStub : IWindowingPlatform |
|||
{ |
|||
public IWindowImpl CreateWindow() => throw new NotSupportedException(); |
|||
|
|||
public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException(); |
|||
|
|||
public ITrayIconImpl CreateTrayIcon() => null; |
|||
} |
|||
|
|||
class PlatformIconLoaderStub : IPlatformIconLoader |
|||
{ |
|||
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) |
|||
{ |
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
bitmap.Save(stream); |
|||
return LoadIcon(stream); |
|||
} |
|||
} |
|||
|
|||
public IWindowIconImpl LoadIcon(Stream stream) |
|||
{ |
|||
var ms = new MemoryStream(); |
|||
stream.CopyTo(ms); |
|||
return new IconStub(ms); |
|||
} |
|||
|
|||
public IWindowIconImpl LoadIcon(string fileName) |
|||
{ |
|||
using (var file = File.Open(fileName, FileMode.Open)) |
|||
return LoadIcon(file); |
|||
} |
|||
} |
|||
|
|||
public class IconStub : IWindowIconImpl |
|||
{ |
|||
private readonly MemoryStream _ms; |
|||
|
|||
public IconStub(MemoryStream stream) |
|||
{ |
|||
_ms = stream; |
|||
} |
|||
|
|||
public void Save(Stream outputStream) |
|||
{ |
|||
_ms.Position = 0; |
|||
_ms.CopyTo(outputStream); |
|||
} |
|||
} |
|||
} |
|||
@ -1,10 +1,14 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks> |
|||
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" /> |
|||
</ItemGroup> |
|||
<Import Project="..\..\build\Rx.props" /> |
|||
<Import Project="..\..\build\ApiDiff.props" /> |
|||
<Import Project="..\..\build\NullableEnable.props" /> |
|||
</Project> |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
Compat issues with assembly Avalonia.Base: |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Threading.IDispatcher.Post<T>(System.Action<T>, T, Avalonia.Threading.DispatcherPriority)' is present in the implementation but not in the contract. |
|||
Total Issues: 1 |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Utilities; |
|||
|
|||
/// <summary>
|
|||
/// Defines a listener to a event subscribed vis the <see cref="WeakEvent{TTarget, TEventArgs}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
|
|||
public interface IWeakEventSubscriber<in TEventArgs> where TEventArgs : EventArgs |
|||
{ |
|||
void OnEvent(object? sender, WeakEvent ev, TEventArgs e); |
|||
} |
|||
@ -0,0 +1,187 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Utilities; |
|||
|
|||
/// <summary>
|
|||
/// Manages subscriptions to events using weak listeners.
|
|||
/// </summary>
|
|||
public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : EventArgs where TSender : class |
|||
{ |
|||
private readonly Func<TSender, EventHandler<TEventArgs>, Action> _subscribe; |
|||
|
|||
readonly ConditionalWeakTable<object, Subscription> _subscriptions = new(); |
|||
|
|||
internal WeakEvent( |
|||
Action<TSender, EventHandler<TEventArgs>> subscribe, |
|||
Action<TSender, EventHandler<TEventArgs>> unsubscribe) |
|||
{ |
|||
_subscribe = (t, s) => |
|||
{ |
|||
subscribe(t, s); |
|||
return () => unsubscribe(t, s); |
|||
}; |
|||
} |
|||
|
|||
internal WeakEvent(Func<TSender, EventHandler<TEventArgs>, Action> subscribe) |
|||
{ |
|||
_subscribe = subscribe; |
|||
} |
|||
|
|||
public void Subscribe(TSender target, IWeakEventSubscriber<TEventArgs> subscriber) |
|||
{ |
|||
if (!_subscriptions.TryGetValue(target, out var subscription)) |
|||
_subscriptions.Add(target, subscription = new Subscription(this, target)); |
|||
subscription.Add(new WeakReference<IWeakEventSubscriber<TEventArgs>>(subscriber)); |
|||
} |
|||
|
|||
public void Unsubscribe(TSender target, IWeakEventSubscriber<TEventArgs> subscriber) |
|||
{ |
|||
if (_subscriptions.TryGetValue(target, out var subscription)) |
|||
subscription.Remove(subscriber); |
|||
} |
|||
|
|||
private class Subscription |
|||
{ |
|||
private readonly WeakEvent<TSender, TEventArgs> _ev; |
|||
private readonly TSender _target; |
|||
private readonly Action _compact; |
|||
|
|||
private WeakReference<IWeakEventSubscriber<TEventArgs>>?[] _data = |
|||
new WeakReference<IWeakEventSubscriber<TEventArgs>>[16]; |
|||
private int _count; |
|||
private readonly Action _unsubscribe; |
|||
private bool _compactScheduled; |
|||
|
|||
public Subscription(WeakEvent<TSender, TEventArgs> ev, TSender target) |
|||
{ |
|||
_ev = ev; |
|||
_target = target; |
|||
_compact = Compact; |
|||
_unsubscribe = ev._subscribe(target, OnEvent); |
|||
} |
|||
|
|||
void Destroy() |
|||
{ |
|||
_unsubscribe(); |
|||
_ev._subscriptions.Remove(_target); |
|||
} |
|||
|
|||
public void Add(WeakReference<IWeakEventSubscriber<TEventArgs>> s) |
|||
{ |
|||
if (_count == _data.Length) |
|||
{ |
|||
//Extend capacity
|
|||
var extendedData = new WeakReference<IWeakEventSubscriber<TEventArgs>>?[_data.Length * 2]; |
|||
Array.Copy(_data, extendedData, _data.Length); |
|||
_data = extendedData; |
|||
} |
|||
|
|||
_data[_count] = s; |
|||
_count++; |
|||
} |
|||
|
|||
public void Remove(IWeakEventSubscriber<TEventArgs> s) |
|||
{ |
|||
var removed = false; |
|||
|
|||
for (int c = 0; c < _count; ++c) |
|||
{ |
|||
var reference = _data[c]; |
|||
|
|||
if (reference != null && reference.TryGetTarget(out var instance) && instance == s) |
|||
{ |
|||
_data[c] = null; |
|||
removed = true; |
|||
} |
|||
} |
|||
|
|||
if (removed) |
|||
{ |
|||
ScheduleCompact(); |
|||
} |
|||
} |
|||
|
|||
void ScheduleCompact() |
|||
{ |
|||
if(_compactScheduled) |
|||
return; |
|||
_compactScheduled = true; |
|||
Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background); |
|||
} |
|||
|
|||
void Compact() |
|||
{ |
|||
_compactScheduled = false; |
|||
int empty = -1; |
|||
for (var c = 0; c < _count; c++) |
|||
{ |
|||
var r = _data[c]; |
|||
//Mark current index as first empty
|
|||
if (r == null && empty == -1) |
|||
empty = c; |
|||
//If current element isn't null and we have an empty one
|
|||
if (r != null && empty != -1) |
|||
{ |
|||
_data[c] = null; |
|||
_data[empty] = r; |
|||
empty++; |
|||
} |
|||
} |
|||
|
|||
if (empty != -1) |
|||
_count = empty; |
|||
if (_count == 0) |
|||
Destroy(); |
|||
} |
|||
|
|||
void OnEvent(object? sender, TEventArgs eventArgs) |
|||
{ |
|||
var needCompact = false; |
|||
for (var c = 0; c < _count; c++) |
|||
{ |
|||
var r = _data[c]; |
|||
if (r?.TryGetTarget(out var sub) == true) |
|||
sub!.OnEvent(_target, _ev, eventArgs); |
|||
else |
|||
needCompact = true; |
|||
} |
|||
|
|||
if (needCompact) |
|||
ScheduleCompact(); |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
public class WeakEvent |
|||
{ |
|||
public static WeakEvent<TSender, TEventArgs> Register<TSender, TEventArgs>( |
|||
Action<TSender, EventHandler<TEventArgs>> subscribe, |
|||
Action<TSender, EventHandler<TEventArgs>> unsubscribe) where TSender : class where TEventArgs : EventArgs |
|||
{ |
|||
return new WeakEvent<TSender, TEventArgs>(subscribe, unsubscribe); |
|||
} |
|||
|
|||
public static WeakEvent<TSender, TEventArgs> Register<TSender, TEventArgs>( |
|||
Func<TSender, EventHandler<TEventArgs>, Action> subscribe) where TSender : class where TEventArgs : EventArgs |
|||
{ |
|||
return new WeakEvent<TSender, TEventArgs>(subscribe); |
|||
} |
|||
|
|||
public static WeakEvent<TSender, EventArgs> Register<TSender>( |
|||
Action<TSender, EventHandler> subscribe, |
|||
Action<TSender, EventHandler> unsubscribe) where TSender : class |
|||
{ |
|||
return Register<TSender, EventArgs>((s, h) => |
|||
{ |
|||
EventHandler handler = (_, e) => h(s, e); |
|||
subscribe(s, handler); |
|||
return () => unsubscribe(s, handler); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using System.Collections.Specialized; |
|||
using System.ComponentModel; |
|||
using System.Windows.Input; |
|||
|
|||
namespace Avalonia.Utilities; |
|||
|
|||
public class WeakEvents |
|||
{ |
|||
/// <summary>
|
|||
/// Represents CollectionChanged event from <see cref="INotifyCollectionChanged"/>
|
|||
/// </summary>
|
|||
public static readonly WeakEvent<INotifyCollectionChanged, NotifyCollectionChangedEventArgs> |
|||
CollectionChanged = WeakEvent.Register<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>( |
|||
(c, s) => |
|||
{ |
|||
NotifyCollectionChangedEventHandler handler = (_, e) => s(c, e); |
|||
c.CollectionChanged += handler; |
|||
return () => c.CollectionChanged -= handler; |
|||
}); |
|||
|
|||
/// <summary>
|
|||
/// Represents PropertyChanged event from <see cref="INotifyPropertyChanged"/>
|
|||
/// </summary>
|
|||
public static readonly WeakEvent<INotifyPropertyChanged, PropertyChangedEventArgs> |
|||
PropertyChanged = WeakEvent.Register<INotifyPropertyChanged, PropertyChangedEventArgs>( |
|||
(s, h) => |
|||
{ |
|||
PropertyChangedEventHandler handler = (_, e) => h(s, e); |
|||
s.PropertyChanged += handler; |
|||
return () => s.PropertyChanged -= handler; |
|||
}); |
|||
|
|||
/// <summary>
|
|||
/// Represents CanExecuteChanged event from <see cref="ICommand"/>
|
|||
/// </summary>
|
|||
public static readonly WeakEvent<ICommand, EventArgs> CommandCanExecuteChanged = |
|||
WeakEvent.Register<ICommand>((s, h) => s.CanExecuteChanged += h, |
|||
(s, h) => s.CanExecuteChanged -= h); |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue