Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split the base out of the confetti view #113

Merged
merged 6 commits into from
Jun 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<Using Include="Xamarin.Forms.Internals" />
<Using Include="Xamarin.Forms.Xaml" />
<Using Include="SkiaSharp.Views.Forms" />
<Using Include="Xamarin.Forms.Platform.WPF" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,5 @@
<ItemGroup>
<None Include="..\SkiaSharp.Extended.UI\**\*.cs" Exclude="@(Compile)" />
</ItemGroup>
<ItemGroup>
<None Remove="..\SkiaSharp.Extended.UI\Controls\ExtendedTypeConverter2.cs" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace SkiaSharp.Extended.UI;

#if NETSTANDARD
using IVisualElementRenderer = System.Object;
#endif

internal static class PlatformExtensions
{
internal static void StartTimer(this IDispatcher dispatcher, TimeSpan interval, Func<bool> callback) =>
Device.StartTimer(interval, callback);

internal static IVisualElementRenderer? GetRenderer(this VisualElement element) =>
#if NETSTANDARD
default;
#else
Platform.GetRenderer(element);
#endif

internal static bool IsLoadedEx(this VisualElement element) =>
element.GetRenderer() is not null;

internal static void RegisterLoadedUnloaded(this VisualElement element, Action? loaded, Action? unloaded)
{
element.PropertyChanged += (sender, e) =>
{
if (e.PropertyName != "Renderer")
return;

if (element.GetRenderer() is null)
unloaded?.Invoke();
else
loaded?.Invoke();
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<Using Include="System.ComponentModel" />
<Using Include="Microsoft.Maui.Graphics" />
<Using Include="Microsoft.Maui.Graphics.Converters" />
<Using Include="Microsoft.Maui.Dispatching" />
<Using Include="Microsoft.Maui.Controls.Xaml" />
<Using Include="SkiaSharp.Views.Maui" />
<Using Include="SkiaSharp.Views.Maui.Controls" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace SkiaSharp.Extended.UI;

internal static class PlatformExtensions
{
internal static bool IsLoadedEx(this VisualElement element) =>
element.IsLoaded;
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public float Size

public bool IsComplete { get; private set; }

public void Draw(SKCanvas canvas, TimeSpan deltaTime)
public void Draw(SKCanvas canvas)
{
if (IsComplete || Shape == null)
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ public bool IsComplete

internal int ParticleCount => particles.Count;

public void Draw(SKCanvas canvas, TimeSpan deltaTime)
public void Update(TimeSpan deltaTime)
{
if (IsRunning)
Emitter?.Update(deltaTime);
Expand All @@ -243,11 +243,7 @@ public void Draw(SKCanvas canvas, TimeSpan deltaTime)

particle.ApplyForce(g, deltaTime);

if (!particle.IsComplete && lastViewBounds.IntersectsWith(particle.Bounds))
{
particle.Draw(canvas, deltaTime);
}
else
if (particle.IsComplete || !lastViewBounds.IntersectsWith(particle.Bounds))
{
particles.RemoveAt(i);
removed = true;
Expand All @@ -258,6 +254,14 @@ public void Draw(SKCanvas canvas, TimeSpan deltaTime)
UpdateIsComplete();
}

public void Draw(SKCanvas canvas)
{
foreach (var particle in particles)
{
particle.Draw(canvas);
}
}

public void UpdateEmitterBounds(double width, double height)
{
lastViewBounds = new SKRect(0, 0, (float)width, (float)height);
Expand Down
156 changes: 43 additions & 113 deletions source/SkiaSharp.Extended.UI/Controls/Confetti/SKConfettiView.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@

namespace SkiaSharp.Extended.UI.Controls;

public class SKConfettiView : TemplatedView
public class SKConfettiView : SKAnimatedSurfaceView
{
public static readonly BindableProperty IsRunningProperty = BindableProperty.Create(
nameof(IsRunning),
typeof(bool),
typeof(SKConfettiView),
true,
propertyChanged: OnIsRunningPropertyChanged);

private static readonly BindablePropertyKey IsCompletePropertyKey = BindableProperty.CreateReadOnly(
nameof(IsComplete),
typeof(bool),
Expand All @@ -27,31 +20,20 @@ public class SKConfettiView : TemplatedView
propertyChanged: OnSystemsPropertyChanged,
defaultValueCreator: _ => CreateDefaultSystems());

private readonly SKFrameCounter frameCounter = new SKFrameCounter();

#if DEBUG
private readonly SKPaint fpsPaint = new SKPaint { IsAntialias = true };
#endif

private SKCanvasView? canvasView;
private SKGLView? glView;

public SKConfettiView()
{
DebugUtils.LogPropertyChanged(this);

Themes.SKConfettiViewResources.EnsureRegistered();

SizeChanged += OnSizeChanged;
PropertyChanged += (_, e) =>
{
if (nameof(IsRunning).Equals(e.PropertyName, StringComparison.OrdinalIgnoreCase))
OnIsRunningPropertyChanged();
};

OnIsRunningPropertyChanged(this, false, IsRunning);
OnSystemsPropertyChanged(this, null, Systems);
}
IsRunning = true;

public bool IsRunning
{
get => (bool)GetValue(IsRunningProperty);
set => SetValue(IsRunningProperty, value);
OnSystemsPropertyChanged(this, null, Systems);
}

public bool IsComplete
Expand All @@ -66,94 +48,54 @@ public SKConfettiSystemCollection? Systems
set => SetValue(SystemsProperty, value);
}

protected override void OnApplyTemplate()
public override void Update(TimeSpan deltaTime)
{
var templateChild = GetTemplateChild("PART_DrawingSurface");

if (canvasView != null)
{
canvasView.PaintSurface -= OnPaintSurface;
canvasView = null;
}

if (glView != null)
{
glView.PaintSurface -= OnPaintSurface;
glView = null;
}
if (Systems is null)
return;

if (templateChild is SKCanvasView view)
for (var i = Systems.Count - 1; i >= 0; i--)
{
canvasView = view;
canvasView.PaintSurface += OnPaintSurface;
}
var system = Systems[i];
system.Update(deltaTime);

if (templateChild is SKGLView gl)
{
glView = gl;
glView.PaintSurface += OnPaintSurface;
if (system.IsComplete)
Systems.RemoveAt(i);
}
}

private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e) =>
OnPaintSurface(e.Surface, e.Info.Size);

private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs e) =>
OnPaintSurface(e.Surface, e.BackendRenderTarget.Size);

private void OnPaintSurface(SKSurface surface, SKSize size)
protected override void OnPaintSurface(SKCanvas canvas, SKSize size)
{
var deltaTime = frameCounter.NextFrame();

var canvas = surface.Canvas;

canvas.Clear(SKColors.Transparent);
canvas.Scale(size.Width / (float)Width);

var particles = 0;
if (Systems != null)

if (Systems is not null)
{
for (var i = Systems.Count - 1; i >= 0; i--)
foreach (var system in Systems)
{
var system = Systems[i];
system.Draw(canvas, deltaTime);

if (system.IsComplete)
Systems.RemoveAt(i);
system.Draw(canvas);

particles += system.ParticleCount;
}
}

#if DEBUG
canvas.DrawText($"{frameCounter.Rate:0.0}", 10f, fpsPaint.TextSize + 10f, fpsPaint);
canvas.DrawText($"{particles}", 10f, fpsPaint.TextSize * 2 + 20f, fpsPaint);
WriteDebugStatus($"Particles: {particles}");
#endif

if (Systems != null && Systems.Count == 0)
frameCounter.Reset();
}

private void OnSizeChanged(object? sender, EventArgs e)
{
if (Systems != null)
if (Systems is null)
return;

foreach (var system in Systems)
{
foreach (var system in Systems)
{
system.UpdateEmitterBounds(Width, Height);
}
system.UpdateEmitterBounds(Width, Height);
}
}

private void Invalidate()
{
canvasView?.InvalidateSurface();
glView?.InvalidateSurface();
}

private void OnSystemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
if (e.NewItems is not null)
{
foreach (SKConfettiSystem system in e.NewItems)
{
Expand All @@ -167,46 +109,34 @@ private void OnSystemsChanged(object? sender, NotifyCollectionChangedEventArgs e
UpdateIsComplete();
}

private static void OnIsRunningPropertyChanged(BindableObject bindable, object? oldValue, object? newValue)
private void OnIsRunningPropertyChanged()
{
if (bindable is SKConfettiView cv && newValue is bool isRunning)
{
if (cv.Systems != null)
{
foreach (var system in cv.Systems)
{
system.IsRunning = isRunning;
}
}

cv.frameCounter.Reset();

Device.StartTimer(TimeSpan.FromMilliseconds(16), () =>
{
cv.Invalidate();
if (Systems is null)
return;

return cv.IsRunning;
});
foreach (var system in Systems)
{
system.IsRunning = IsRunning;
}
}

private static void OnSystemsPropertyChanged(BindableObject bindable, object? oldValue, object? newValue)
{
if (bindable is SKConfettiView cv)
{
if (oldValue is SKConfettiSystemCollection oldCollection)
oldCollection.CollectionChanged -= cv.OnSystemsChanged;
if (bindable is not SKConfettiView cv)
return;

if (newValue is SKConfettiSystemCollection newCollection)
newCollection.CollectionChanged += cv.OnSystemsChanged;
if (oldValue is SKConfettiSystemCollection oldCollection)
oldCollection.CollectionChanged -= cv.OnSystemsChanged;

cv.UpdateIsComplete();
}
if (newValue is SKConfettiSystemCollection newCollection)
newCollection.CollectionChanged += cv.OnSystemsChanged;

cv.UpdateIsComplete();
}

private void UpdateIsComplete()
{
if (Systems == null || Systems.Count == 0)
if (Systems is null || Systems.Count == 0)
{
IsComplete = true;
return;
Expand Down
Loading