committed by
GitHub
54 changed files with 1022 additions and 244 deletions
@ -1,11 +1,28 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:viewModels="using:ControlCatalog.ViewModels" |
|||
xmlns:system="clr-namespace:System;assembly=System.Runtime" |
|||
x:Class="ControlCatalog.Pages.NotificationsPage" |
|||
x:DataType="viewModels:NotificationViewModel"> |
|||
<StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Left"> |
|||
<Button Content="Show Standard Managed Notification" Command="{Binding ShowManagedNotificationCommand}" /> |
|||
<Button Content="Show Custom Managed Notification" Command="{Binding ShowCustomManagedNotificationCommand}" /> |
|||
<Button Content="Show Native Notification" Command="{Binding ShowNativeNotificationCommand}" /> |
|||
<DockPanel> |
|||
<TextBlock DockPanel.Dock="Top" |
|||
Margin="2" Classes="h2" TextWrapping="Wrap">TopLevel bound notification manager.</TextBlock> |
|||
<StackPanel DockPanel.Dock="Top" |
|||
Orientation="Vertical" Spacing="4" HorizontalAlignment="Left"> |
|||
<Button Content="Show Standard Managed Notification" Command="{Binding ShowManagedNotificationCommand}" /> |
|||
<Button Content="Show Custom Managed Notification" Command="{Binding ShowCustomManagedNotificationCommand}" /> |
|||
</StackPanel> |
|||
|
|||
<TextBlock DockPanel.Dock="Top" |
|||
Margin="2" Classes="h2" TextWrapping="Wrap">XAML only notification manager.</TextBlock> |
|||
<Button DockPanel.Dock="Top" |
|||
Content="Show XAML only Notification" Command="{Binding #ControlNotifications.Show}"> |
|||
<Button.CommandParameter> |
|||
<Notification Title="Title" Message="Message" OnClick="NotificationOnClick" /> |
|||
</Button.CommandParameter> |
|||
</Button> |
|||
<Border Padding="10" BorderBrush="{DynamicResource SystemAccentColor}"> |
|||
<WindowNotificationManager x:Name="ControlNotifications" /> |
|||
</Border> |
|||
</DockPanel> |
|||
</UserControl> |
|||
|
|||
@ -0,0 +1,100 @@ |
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using Avalonia.Animation.Easings; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Defines how a property should be animated using a transition.
|
|||
/// </summary>
|
|||
public abstract class TransitionBase : AvaloniaObject, ITransition |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Duration"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TransitionBase, TimeSpan> DurationProperty = |
|||
AvaloniaProperty.RegisterDirect<TransitionBase, TimeSpan>( |
|||
nameof(Duration), |
|||
o => o._duration, |
|||
(o, v) => o._duration = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Delay"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TransitionBase, TimeSpan> DelayProperty = |
|||
AvaloniaProperty.RegisterDirect<TransitionBase, TimeSpan>( |
|||
nameof(Delay), |
|||
o => o._delay, |
|||
(o, v) => o._delay = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Easing"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TransitionBase, Easing> EasingProperty = |
|||
AvaloniaProperty.RegisterDirect<TransitionBase, Easing>( |
|||
nameof(Easing), |
|||
o => o._easing, |
|||
(o, v) => o._easing = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Property"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TransitionBase, AvaloniaProperty?> PropertyProperty = |
|||
AvaloniaProperty.RegisterDirect<TransitionBase, AvaloniaProperty?>( |
|||
nameof(Property), |
|||
o => o._prop, |
|||
(o, v) => o._prop = v); |
|||
|
|||
private TimeSpan _duration; |
|||
private TimeSpan _delay = TimeSpan.Zero; |
|||
private Easing _easing = new LinearEasing(); |
|||
private AvaloniaProperty? _prop; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the duration of the transition.
|
|||
/// </summary>
|
|||
public TimeSpan Duration |
|||
{ |
|||
get { return _duration; } |
|||
set { SetAndRaise(DurationProperty, ref _duration, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets delay before starting the transition.
|
|||
/// </summary>
|
|||
public TimeSpan Delay |
|||
{ |
|||
get { return _delay; } |
|||
set { SetAndRaise(DelayProperty, ref _delay, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the easing class to be used.
|
|||
/// </summary>
|
|||
public Easing Easing |
|||
{ |
|||
get { return _easing; } |
|||
set { SetAndRaise(EasingProperty, ref _easing, value); } |
|||
} |
|||
|
|||
/// <inheritdocs/>
|
|||
[DisallowNull] |
|||
public AvaloniaProperty? Property |
|||
{ |
|||
get { return _prop; } |
|||
set { SetAndRaise(PropertyProperty, ref _prop, value); } |
|||
} |
|||
|
|||
AvaloniaProperty ITransition.Property |
|||
{ |
|||
get => Property ?? throw new InvalidOperationException("Transition has no property specified."); |
|||
set => Property = value; |
|||
} |
|||
|
|||
/// <inheritdocs/>
|
|||
IDisposable ITransition.Apply(Animatable control, IClock clock, object? oldValue, object? newValue) |
|||
=> Apply(control, clock, oldValue, newValue); |
|||
|
|||
internal abstract IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Media; |
|||
|
|||
internal class ImmutableGeometry : Geometry |
|||
{ |
|||
public ImmutableGeometry(IGeometryImpl? platformImpl) |
|||
: base(platformImpl) |
|||
{ |
|||
} |
|||
|
|||
public override Geometry Clone() => new ImmutableGeometry(PlatformImpl); |
|||
|
|||
private protected override IGeometryImpl? CreateDefiningGeometry() |
|||
{ |
|||
return PlatformImpl; |
|||
} |
|||
} |
|||
@ -0,0 +1,149 @@ |
|||
// Taken from:
|
|||
// https://github.com/mono/SkiaSharp/blob/main/binding/Binding.Shared/HashCode.cs
|
|||
// Partial code copied from:
|
|||
// https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/System.Private.CoreLib/src/System/HashCode.cs
|
|||
|
|||
#if NETSTANDARD2_0
|
|||
#nullable disable |
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace System; |
|||
|
|||
internal unsafe struct HashCode |
|||
{ |
|||
private static readonly uint s_seed = GenerateGlobalSeed(); |
|||
|
|||
private const uint Prime1 = 2654435761U; |
|||
private const uint Prime2 = 2246822519U; |
|||
private const uint Prime3 = 3266489917U; |
|||
private const uint Prime4 = 668265263U; |
|||
private const uint Prime5 = 374761393U; |
|||
|
|||
private uint _v1, _v2, _v3, _v4; |
|||
private uint _queue1, _queue2, _queue3; |
|||
private uint _length; |
|||
|
|||
private static unsafe uint GenerateGlobalSeed() |
|||
{ |
|||
var rnd = new Random(); |
|||
var result = rnd.Next(); |
|||
return unchecked((uint)result); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) |
|||
{ |
|||
v1 = s_seed + Prime1 + Prime2; |
|||
v2 = s_seed + Prime2; |
|||
v3 = s_seed; |
|||
v4 = s_seed - Prime1; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static uint Round(uint hash, uint input) => |
|||
RotateLeft(hash + input * Prime2, 13) * Prime1; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static uint QueueRound(uint hash, uint queuedValue) => |
|||
RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static uint MixState(uint v1, uint v2, uint v3, uint v4) => |
|||
RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static uint RotateLeft(uint value, int offset) => |
|||
(value << offset) | (value >> (32 - offset)); |
|||
|
|||
private static uint MixEmptyState() => |
|||
s_seed + Prime5; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static uint MixFinal(uint hash) |
|||
{ |
|||
hash ^= hash >> 15; |
|||
hash *= Prime2; |
|||
hash ^= hash >> 13; |
|||
hash *= Prime3; |
|||
hash ^= hash >> 16; |
|||
return hash; |
|||
} |
|||
|
|||
public void Add(void* value) => |
|||
Add(value == null ? 0 : ((IntPtr)value).GetHashCode()); |
|||
|
|||
public void Add<T>(T value) => |
|||
Add(value?.GetHashCode() ?? 0); |
|||
|
|||
private void Add(int value) |
|||
{ |
|||
uint val = (uint)value; |
|||
|
|||
// Storing the value of _length locally shaves of quite a few bytes
|
|||
// in the resulting machine code.
|
|||
uint previousLength = _length++; |
|||
uint position = previousLength % 4; |
|||
|
|||
// Switch can't be inlined.
|
|||
|
|||
if (position == 0) |
|||
_queue1 = val; |
|||
else if (position == 1) |
|||
_queue2 = val; |
|||
else if (position == 2) |
|||
_queue3 = val; |
|||
else // position == 3
|
|||
{ |
|||
if (previousLength == 3) |
|||
Initialize(out _v1, out _v2, out _v3, out _v4); |
|||
|
|||
_v1 = Round(_v1, _queue1); |
|||
_v2 = Round(_v2, _queue2); |
|||
_v3 = Round(_v3, _queue3); |
|||
_v4 = Round(_v4, val); |
|||
} |
|||
} |
|||
|
|||
public int ToHashCode() |
|||
{ |
|||
// Storing the value of _length locally shaves of quite a few bytes
|
|||
// in the resulting machine code.
|
|||
uint length = _length; |
|||
|
|||
// position refers to the *next* queue position in this method, so
|
|||
// position == 1 means that _queue1 is populated; _queue2 would have
|
|||
// been populated on the next call to Add.
|
|||
uint position = length % 4; |
|||
|
|||
// If the length is less than 4, _v1 to _v4 don't contain anything
|
|||
// yet. xxHash32 treats this differently.
|
|||
|
|||
uint hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4); |
|||
|
|||
// _length is incremented once per Add(Int32) and is therefore 4
|
|||
// times too small (xxHash length is in bytes, not ints).
|
|||
|
|||
hash += length * 4; |
|||
|
|||
// Mix what remains in the queue
|
|||
|
|||
// Switch can't be inlined right now, so use as few branches as
|
|||
// possible by manually excluding impossible scenarios (position > 1
|
|||
// is always false if position is not > 0).
|
|||
if (position > 0) |
|||
{ |
|||
hash = QueueRound(hash, _queue1); |
|||
if (position > 1) |
|||
{ |
|||
hash = QueueRound(hash, _queue2); |
|||
if (position > 2) |
|||
hash = QueueRound(hash, _queue3); |
|||
} |
|||
} |
|||
|
|||
hash = MixFinal(hash); |
|||
return (int)hash; |
|||
} |
|||
} |
|||
#endif
|
|||
@ -0,0 +1,38 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Skia.Helpers; |
|||
|
|||
internal static class PenHelper |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a hash code for a pen, optionally including the brush.
|
|||
/// </summary>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="includeBrush">Whether to include the brush in the hash code.</param>
|
|||
/// <returns>The hash code.</returns>
|
|||
public static int GetHashCode(IPen? pen, bool includeBrush) |
|||
{ |
|||
if (pen is null) |
|||
return 0; |
|||
|
|||
var hash = new HashCode(); |
|||
hash.Add(pen.LineCap); |
|||
hash.Add(pen.LineJoin); |
|||
hash.Add(pen.MiterLimit); |
|||
hash.Add(pen.Thickness); |
|||
|
|||
if (pen.DashStyle is { } dashStyle) |
|||
{ |
|||
hash.Add(dashStyle.Offset); |
|||
|
|||
for (var i = 0; i < dashStyle.Dashes?.Count; i++) |
|||
hash.Add(dashStyle.Dashes[i]); |
|||
} |
|||
|
|||
if (includeBrush) |
|||
hash.Add(pen.Brush); |
|||
|
|||
return hash.ToHashCode(); |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Utilities; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Skia.Helpers; |
|||
|
|||
internal static class SKPathHelper |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new path that is a closed version of the source path.
|
|||
/// </summary>
|
|||
/// <param name="path">The source path.</param>
|
|||
/// <returns>A closed path.</returns>
|
|||
public static SKPath CreateClosedPath(SKPath path) |
|||
{ |
|||
using var iter = path.CreateIterator(true); |
|||
SKPathVerb verb; |
|||
var points = new SKPoint[4]; |
|||
var rv = new SKPath(); |
|||
while ((verb = iter.Next(points)) != SKPathVerb.Done) |
|||
{ |
|||
if (verb == SKPathVerb.Move) |
|||
rv.MoveTo(points[0]); |
|||
else if (verb == SKPathVerb.Line) |
|||
rv.LineTo(points[1]); |
|||
else if (verb == SKPathVerb.Close) |
|||
rv.Close(); |
|||
else if (verb == SKPathVerb.Quad) |
|||
rv.QuadTo(points[1], points[2]); |
|||
else if (verb == SKPathVerb.Cubic) |
|||
rv.CubicTo(points[1], points[2], points[3]); |
|||
else if (verb == SKPathVerb.Conic) |
|||
rv.ConicTo(points[1], points[2], iter.ConicWeight()); |
|||
|
|||
} |
|||
|
|||
return rv; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a path that is the result of a pen being applied to the stroke of the given path.
|
|||
/// </summary>
|
|||
/// <param name="path">The path to stroke.</param>
|
|||
/// <param name="pen">The pen to use to stroke the path.</param>
|
|||
/// <returns>The resulting path, or null if the pen has 0 thickness.</returns>
|
|||
public static SKPath? CreateStrokedPath(SKPath path, IPen pen) |
|||
{ |
|||
if (MathUtilities.IsZero(pen.Thickness)) |
|||
return null; |
|||
|
|||
var paint = SKPaintCache.Shared.Get(); |
|||
paint.IsStroke = true; |
|||
paint.StrokeWidth = (float)pen.Thickness; |
|||
paint.StrokeCap = pen.LineCap.ToSKStrokeCap(); |
|||
paint.StrokeJoin = pen.LineJoin.ToSKStrokeJoin(); |
|||
paint.StrokeMiter = (float)pen.MiterLimit; |
|||
|
|||
if (DrawingContextHelper.TryCreateDashEffect(pen, out var dashEffect)) |
|||
paint.PathEffect = dashEffect; |
|||
|
|||
var result = new SKPath(); |
|||
paint.GetFillPath(path, result); |
|||
paint.PathEffect?.Dispose(); |
|||
SKPaintCache.Shared.ReturnReset(paint); |
|||
return result; |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
Loading…
Reference in new issue