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

fix(ShadowContainer): prevent unnecessary repaint #830

Merged
merged 4 commits into from
Oct 6, 2023
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 @@ -310,7 +310,51 @@
<StackPanel Spacing="8">
<TextBlock Text="Complex Shadow Shapes" Style="{StaticResource TitleTextBlockStyle}" />

<StackPanel>
<!--
Should be the same as: https://developer.mozilla.org/fr/docs/Web/CSS/CSS_backgrounds_and_borders/Box-shadow_generator
roubachof marked this conversation as resolved.
Show resolved Hide resolved
element {
width: 300px; height: 100px; background-color: #7A67F8; position: relative;
box-shadow:
6px -5px 5px 0px #E88BAB,
inset 5px -5px 5px 0px #94DB6D,
inset -20px -10px 10px 10px #000000;
}
-->
<utu:ShadowContainer Margin="0,30" Background="{StaticResource UnoColor}">
<utu:ShadowContainer.Shadows>
<utu:ShadowCollection>
<utu:Shadow IsInner="True"
OffsetX="-20"
OffsetY="-10"
BlurRadius="10"
Spread="10"
Color="Black"
Opacity="1" />
<utu:Shadow IsInner="True"
OffsetX="5"
OffsetY="-5"
BlurRadius="5"
Spread="0"
Color="#94DB6D"
Opacity="1" />
<utu:Shadow OffsetX="6"
OffsetY="-5"
BlurRadius="5"
Spread="0"
Color="#E88BAB"
Opacity="1" />
</utu:ShadowCollection>
</utu:ShadowContainer.Shadows>
<Button Width="300"
Height="100"
Background="Transparent"
BorderThickness="0"
Content="CSS box-shadow"
CornerRadius="0"
Foreground="White" />
</utu:ShadowContainer>

<StackPanel>
<TextBlock Text="Border 200x100 CR=10,50,10,50" />
<StackPanel Orientation="Horizontal">
<Border Width="200" Height="100" CornerRadius="10,50,10,50" Background="Blue" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public partial class ShadowContainer

private ShadowPaintState? _lastPaintState;
private bool _isShadowDirty;
private int _paintCount;

internal event EventHandler<SurfacePaintCompletedEventArgs>? SurfacePaintCompleted;

Expand All @@ -38,10 +39,11 @@ private void OnSurfacePaintCompleted(bool createdNewCanvas)

private bool NeedsPaint(ShadowPaintState state, out bool pixelRatioChanged)
{
var needsPaint = state != _lastPaintState;
pixelRatioChanged = state.PixelRatio != _lastPaintState?.PixelRatio;
var needsPaint = state != _lastPaintState || _isShadowHostDirty;
pixelRatioChanged = _lastPaintState != null && state.PixelRatio != _lastPaintState.PixelRatio;
roubachof marked this conversation as resolved.
Show resolved Hide resolved

_lastPaintState = state;
_isShadowHostDirty = false;
return needsPaint;
}

Expand Down Expand Up @@ -76,11 +78,15 @@ private void OnSurfacePainted(object? sender, SKPaintSurfaceEventArgs e)

if (state.Shadows.Length == 0)
{
canvas.Clear(SKColors.Transparent);
shape.DrawContentBackground(state, canvas, background ?? Colors.Transparent);
return;
}

if (_logger.IsEnabled(LogLevel.Trace))
{
_logger.Trace($"[ShadowContainer] Painting shadows (x{++_paintCount}) for content {Content.GetType().Name}, actualSize: {contentAsFE.ActualWidth}x{contentAsFE.ActualHeight}");
}

using var _ = canvas.SnapshotState();

var key =
Expand Down Expand Up @@ -148,7 +154,7 @@ private void OnSurfacePainted(object? sender, SKPaintSurfaceEventArgs e)
};
}

private IShadowShapeContext GetShadowShapeContext(object content)
private ShadowShapeContext GetShadowShapeContext(object content)
{
return content switch
{
Expand All @@ -163,28 +169,29 @@ private IShadowShapeContext GetShadowShapeContext(object content)

/// <summary>
/// Serves both as a record of states relevant to shadow shape (not <see cref="Shape"/>, but in the broad sense), and the implementations for painting the shadows.
/// We can't use an interface here or we will lose the ability to compare instances of this class (thanks to generated Equals).
/// </summary>
private interface IShadowShapeContext
private abstract record ShadowShapeContext
{
void ClipToContent(ShadowPaintState state, SKCanvas canvas);
public abstract void ClipToContent(ShadowPaintState state, SKCanvas canvas);

void DrawContentBackground(ShadowPaintState state, SKCanvas canvas, Color color);
public abstract void DrawContentBackground(ShadowPaintState state, SKCanvas canvas, Color color);

void DrawDropShadow(ShadowPaintState state, SKCanvas canvas, SKPaint paint, ShadowInfo shadow);
public abstract void DrawDropShadow(ShadowPaintState state, SKCanvas canvas, SKPaint paint, ShadowInfo shadow);

void DrawInnerShadow(ShadowPaintState state, SKCanvas canvas, SKPaint paint, ShadowInfo shadow);
public abstract void DrawInnerShadow(ShadowPaintState state, SKCanvas canvas, SKPaint paint, ShadowInfo shadow);
}

private abstract record RoundRectShadowShapeContext(double Width, double Height) : IShadowShapeContext
private abstract record RoundRectShadowShapeContext(double Width, double Height) : ShadowShapeContext
{
protected abstract SKRoundRect GetContentShape(ShadowPaintState state);

public void ClipToContent(ShadowPaintState state, SKCanvas canvas)
public override void ClipToContent(ShadowPaintState state, SKCanvas canvas)
{
canvas.ClipRoundRect(GetContentShape(state), antialias: true);
}

public void DrawContentBackground(ShadowPaintState state, SKCanvas canvas, Color color)
public override void DrawContentBackground(ShadowPaintState state, SKCanvas canvas, Color color)
{
var shape = GetContentShape(state);
using var backgroundPaint = new SKPaint
Expand All @@ -200,7 +207,7 @@ public void DrawContentBackground(ShadowPaintState state, SKCanvas canvas, Color
canvas.DrawRoundRect(shape, backgroundPaint);
}

public void DrawDropShadow(ShadowPaintState state, SKCanvas canvas, SKPaint paint, ShadowInfo shadow)
public override void DrawDropShadow(ShadowPaintState state, SKCanvas canvas, SKPaint paint, ShadowInfo shadow)
{
var spread = (float)shadow.Spread * state.PixelRatio;
var offsetX = (float)shadow.OffsetX * state.PixelRatio;
Expand Down Expand Up @@ -238,7 +245,7 @@ public void DrawDropShadow(ShadowPaintState state, SKCanvas canvas, SKPaint pain
canvas.DrawRoundRect(shape, paint);
}

public void DrawInnerShadow(ShadowPaintState state, SKCanvas canvas, SKPaint paint, ShadowInfo shadow)
public override void DrawInnerShadow(ShadowPaintState state, SKCanvas canvas, SKPaint paint, ShadowInfo shadow)
{
var spread = (float)shadow.Spread * state.PixelRatio;
var offsetX = (float)shadow.OffsetX * state.PixelRatio;
Expand Down Expand Up @@ -329,7 +336,7 @@ public float GetBlurSigma(float pixelRatio)
/// Used in comparison to determine if the shadow needs to be repainted.
/// </summary>
private sealed record ShadowPaintState(
IShadowShapeContext Shape,
ShadowShapeContext Shape,
Color? Background,
float PixelRatio,
ShadowInfo[] Shadows)
Expand All @@ -355,11 +362,11 @@ public override int GetHashCode()
}

public bool Equals(ShadowPaintState? x) =>
x is { } y &&
Shape == y.Shape &&
Background == y.Background &&
PixelRatio == y.PixelRatio &&
Shadows.SequenceEqual(y.Shadows);
x is not null &&
Shape == x.Shape &&
Background == x.Background &&
PixelRatio == x.PixelRatio &&
Shadows.SequenceEqual(x.Shadows);
}

public class SurfacePaintCompletedEventArgs : EventArgs
Expand Down
39 changes: 32 additions & 7 deletions src/Uno.Toolkit.Skia.WinUI/Controls/Shadows/ShadowContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
Expand Down Expand Up @@ -36,8 +35,11 @@ public partial class ShadowContainer : ContentControl

private Grid? _panel;
private Canvas? _canvas;

private SKXamlCanvas? _shadowHost;

private bool _isShadowHostDirty = true;

public ShadowContainer()
{
DefaultStyleKey = typeof(ShadowContainer);
Expand Down Expand Up @@ -73,6 +75,8 @@ private void BindToPaintingProperties()
this.RegisterDisposablePropertyChangedCallback(ShadowsProperty, OnShadowsChanged),
this.RegisterDisposablePropertyChangedCallback(ContentProperty, OnContentChanged),

RegisterDisposableShadowHostSizeChangedCallback(OnShadowHostSizeChanged),

backgroundNestedDisposable,
shadowsNestedDisposable,
contentNestedDisposable,
Expand All @@ -86,7 +90,7 @@ private void BindToPaintingProperties()
// This method should not fire any of InvalidateXyz-methods directly,
// in order to avoid duplicated invalidate calls.
// Which is why the BindToXyz has been separated from the OnXyzChanged.

void OnBackgroundChanged(DependencyObject sender, DependencyProperty dp)
{
BindToBackgroundMemberProperties(Background);
Expand All @@ -106,6 +110,11 @@ void OnContentChanged(DependencyObject sender, DependencyProperty dp)

InvalidateShadows();
}
// When the skia canvas size changes, the whole canvas is cleared: we'll need to redraw the shadows.
void OnShadowHostSizeChanged(object sender, SizeChangedEventArgs args)
{
_isShadowHostDirty = true;
}

void OnShadowPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Expand All @@ -117,6 +126,7 @@ void OnShadowPropertyChanged(object? sender, PropertyChangedEventArgs e)
}
InvalidateShadows();
}

void OnInnerPropertyChanged(DependencyObject sender, DependencyProperty dp)
{
InvalidateShadows();
Expand All @@ -137,6 +147,21 @@ void OnContentSizeChanged(object sender, SizeChangedEventArgs e)
InvalidateShadows();
}

SerialDisposable RegisterDisposableShadowHostSizeChangedCallback(SizeChangedEventHandler sizeChanged)
{
if (_shadowHost == null)
{
return new SerialDisposable();
}

_shadowHost.SizeChanged += OnShadowHostSizeChanged;
return new SerialDisposable
{
Disposable = Disposable.Create(() => _shadowHost.SizeChanged -= sizeChanged),
};

}

void BindToBackgroundMemberProperties(Brush? background)
{
backgroundNestedDisposable.Disposable = background switch
Expand Down Expand Up @@ -244,6 +269,7 @@ protected override void OnApplyTemplate()
_panel = GetTemplateChild(nameof(PART_ShadowOwner)) as Grid;

var skiaCanvas = new SKXamlCanvas();

skiaCanvas.PaintSurface += OnSurfacePainted;

#if __IOS__ || __MACCATALYST__
Expand All @@ -254,6 +280,8 @@ protected override void OnApplyTemplate()
_canvas?.Children.Insert(0, _shadowHost!);
}



private void InvalidateCanvasLayout()
{
if (Content is not FrameworkElement contentAsFE ||
Expand Down Expand Up @@ -288,7 +316,6 @@ private void InvalidateCanvasLayoutSize()
return;
}


#if __ANDROID__ || __IOS__
_canvas.GetDispatcherCompat().Schedule(() => _canvas.InvalidateMeasure());
#endif
Expand Down Expand Up @@ -318,15 +345,13 @@ private void InvalidateCanvasLayoutSize()
_canvas.HorizontalAlignment = contentAsFE.HorizontalAlignment;
_canvas.VerticalAlignment = contentAsFE.VerticalAlignment;



double left =
+(contentAsFE.HorizontalAlignment == HorizontalAlignment.Left ? -newHostSpreedWidth : 0)
+ (contentAsFE.HorizontalAlignment == HorizontalAlignment.Left ? -newHostSpreedWidth : 0)
+ (contentAsFE.HorizontalAlignment == HorizontalAlignment.Right ? -newHostSpreedWidth - childWidth / 2 : 0)
+ (contentAsFE.HorizontalAlignment == HorizontalAlignment.Stretch ? -newHostSpreedWidth - childWidth / 4 : 0)
+ (contentAsFE.HorizontalAlignment == HorizontalAlignment.Center ? -newHostSpreedWidth - childWidth / 4 : 0);
double top =
+(contentAsFE.VerticalAlignment == VerticalAlignment.Top ? -newHostSpreedHeight : 0)
+ (contentAsFE.VerticalAlignment == VerticalAlignment.Top ? -newHostSpreedHeight : 0)
+ (contentAsFE.VerticalAlignment == VerticalAlignment.Bottom ? -newHostSpreedHeight - childHeight / 2 : 0)
+ (contentAsFE.VerticalAlignment == VerticalAlignment.Stretch ? -newHostSpreedHeight - childHeight / 4 : 0)
+ (contentAsFE.VerticalAlignment == VerticalAlignment.Center ? -newHostSpreedHeight - childHeight / 4 : 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Uno.Logging;

namespace Uno.Toolkit.UI;

public class ShadowsCache
{
private static readonly ILogger _logger = typeof(ShadowsCache).Log();
Expand Down
Loading