Skip to content
This repository has been archived by the owner on Dec 28, 2023. It is now read-only.

Commit

Permalink
Merge pull request #91 from rookiejava/master
Browse files Browse the repository at this point in the history
[CommonUI] Added AllowShadowClipping to ShadowFrame
  • Loading branch information
rookiejava authored Apr 6, 2021
2 parents 4c1519d + f71a029 commit ad3336a
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 57 deletions.
9 changes: 9 additions & 0 deletions sample/Sample/ShadowFrame/ShadowFrameTest.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
<Label Text="Toggle HasShadow: " TextColor ="Black"/>
<Button x:Name="hasShadow" HeightRequest="20" Clicked="hasShadow_Clicked" Text="{Binding HasShadow, Source={x:Reference Frame1}}"/>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Toggle AllowShadowClipping: " TextColor ="Black"/>
<Button x:Name="allowShadowClipping" HeightRequest="20" Clicked="allowShadowClipping_Clicked" Text="{Binding AllowShadowClipping, Source={x:Reference Frame1}}"/>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="ShadowClippingWidth: " TextColor ="Black"/>
<Slider x:Name="shadowCllippingWidthSlider" Minimum="0" Maximum="20" HorizontalOptions="FillAndExpand" Value="{Binding ShadowClippingWidth, Source={x:Reference Frame1}}"/>
<!--ValueChanged="borderWidth_ValueChanged"/>-->
</StackLayout>

<Label Text="ShadowColor" FontSize="Large" TextColor="Coral"/>
<StackLayout Orientation="Horizontal">
Expand Down
5 changes: 5 additions & 0 deletions sample/Sample/ShadowFrame/ShadowFrameTest.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,10 @@ private void hasShadow_Clicked(object sender, EventArgs e)
{
Frame1.HasShadow = !Frame1.HasShadow;
}

private void allowShadowClipping_Clicked(object sender, EventArgs e)
{
Frame1.AllowShadowClipping = !Frame1.AllowShadowClipping;
}
}
}
161 changes: 104 additions & 57 deletions src/Tizen.Theme.Common/Renderer/ShadowFrameRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class ShadowFrameRenderer : LayoutRenderer
SKCanvasView _shadowCanvasView;
ShadowFrame ShadowElement => Element as ShadowFrame;

bool UseShadowClipping => ShadowElement.AllowShadowClipping;

public ShadowFrameRenderer()
{
if (!Forms.UseSkiaSharp)
Expand All @@ -51,6 +53,8 @@ public ShadowFrameRenderer()
RegisterPropertyHandler(ShadowFrame.ShadowOffsetXProperty, UpdateCanvas);
RegisterPropertyHandler(ShadowFrame.ShadowOffsetYProperty, UpdateCanvas);
RegisterPropertyHandler(ShadowFrame.ShadowOpacityProperty, UpdateCanvas);
RegisterPropertyHandler(ShadowFrame.AllowShadowClippingProperty, UpdateAllowShadowClipping);
RegisterPropertyHandler(ShadowFrame.ShadowClippingWidthProperty, UpdateCanvas);
}

protected override void OnElementChanged(ElementChangedEventArgs<Layout> e)
Expand All @@ -62,14 +66,10 @@ protected override void OnElementChanged(ElementChangedEventArgs<Layout> e)
_clipper.PaintSurface += OnCliperPaint;
Control.Children.Add(_clipper);
BackgroundCanvas?.StackAbove(_clipper);

_shadowCanvasView = new SKCanvasView(Forms.NativeParent);
_shadowCanvasView.Show();
_shadowCanvasView.PassEvents = true;
_shadowCanvasView.PaintSurface += OnShadowPaint;
Control.Children.Add(_shadowCanvasView);
_shadowCanvasView.Lower();
_shadowCanvasView.SetClip(null);
if (!UseShadowClipping)
{
CreateShadowCanvas();
}
}

protected override void UpdateBackgroundColor(bool initialize)
Expand All @@ -90,10 +90,13 @@ protected override void OnBackgroundLayoutUpdated(object sender, NLayoutEventArg
_clipper.Invalidate();
}

if (_shadowCanvasView != null && ShadowElement.HasShadow)
if (!UseShadowClipping && ShadowElement.HasShadow)
{
UpdateShadowGeometry();
_shadowCanvasView.Invalidate();
UpdateShadowCanvasGeometry();
}
else
{
_shadowCanvasView?.Invalidate();
}
}

Expand All @@ -110,8 +113,36 @@ protected override void OnBackgroundPaint(object sender, SKPaintSurfaceEventArgs
IsAntialias = true,
})
{
using (var path = CreateRoundRectPath(bound.Width, bound.Height))
using (var path = GetRoundRectPath(false))
{
// Draw shadow here only if you choose useShadowClip = true. (this means no use of shadow canvas)
if (UseShadowClipping && ShadowElement.HasShadow)
{
using (var path2 = GetRoundRectPath(true))
{
paint.Style = SKPaintStyle.StrokeAndFill;
var scaledOffsetX = Forms.ConvertToScaledPixel(ShadowElement.ShadowOffsetX);
var scaledOffsetY = Forms.ConvertToScaledPixel(ShadowElement.ShadowOffsetY);
var scaledBlurRadius = Forms.ConvertToScaledPixel(ShadowElement.ShadowBlurRadius);

canvas.Save();
canvas.ClipPath(path2, SKClipOperation.Difference, true);
paint.ImageFilter = SKImageFilter.CreateDropShadowOnly(
scaledOffsetX,
scaledOffsetY,
scaledBlurRadius,
scaledBlurRadius,
ShadowElement.ShadowColor.MultiplyAlpha(ShadowElement.ShadowOpacity).ToSK());
canvas.DrawPath(path2, paint);
canvas.Restore();

canvas.Save();
canvas.ClipPath(path2, SKClipOperation.Intersect, true);
canvas.DrawPath(path2, paint);
canvas.Restore();
}
}

// Draw background color
paint.ImageFilter = null;
paint.Style = SKPaintStyle.Fill;
Expand All @@ -126,11 +157,14 @@ protected override void OnBackgroundPaint(object sender, SKPaintSurfaceEventArgs
}

// Draw border
paint.IsAntialias = true;
paint.Style = SKPaintStyle.Stroke;
paint.StrokeWidth = Forms.ConvertToScaledPixel(ShadowElement.BorderWidth);
paint.Color = borderColor;
canvas.DrawPath(path, paint);
if (ShadowElement.BorderWidth != 0)
{
paint.IsAntialias = true;
paint.Style = SKPaintStyle.Stroke;
paint.StrokeWidth = Forms.ConvertToScaledPixel(ShadowElement.BorderWidth);
paint.Color = borderColor;
canvas.DrawPath(path, paint);
}
}
}
}
Expand All @@ -141,7 +175,7 @@ protected virtual void OnShadowPaint(object sender, SKPaintSurfaceEventArgs e)
var bound = e.Info.Rect;
canvas.Clear();

// Draw shadow
// Draw shadow on shadow canvas
if (ShadowElement.HasShadow)
{
using (var paint = new SKPaint
Expand All @@ -150,7 +184,7 @@ protected virtual void OnShadowPaint(object sender, SKPaintSurfaceEventArgs e)
Style = SKPaintStyle.StrokeAndFill
})
{
using (var path = CreateShadowPath())
using (var path = GetRoundRectPath(true))
{
var scaledOffsetX = Forms.ConvertToScaledPixel(ShadowElement.ShadowOffsetX);
var scaledOffsetY = Forms.ConvertToScaledPixel(ShadowElement.ShadowOffsetY);
Expand Down Expand Up @@ -193,7 +227,7 @@ protected virtual void OnCliperPaint(object sender, SKPaintSurfaceEventArgs e)
Color = SKColors.White,
})
{
using (var path = CreateRoundRectPath(bound.Width, bound.Height))
using (var path = GetRoundRectPath(false))
{
canvas.DrawPath(path, paint);
}
Expand All @@ -205,44 +239,56 @@ void UpdateCanvas()
{
BackgroundCanvas?.Invalidate();
_clipper?.Invalidate();
if (ShadowElement.HasShadow)

if (!UseShadowClipping &&ShadowElement.HasShadow)
{
UpdateShadowGeometry();
UpdateShadowCanvasGeometry();
}
else
{
_shadowCanvasView?.Invalidate();
}
}

SKPath CreateRoundRectPath(float width, float height)
void UpdateAllowShadowClipping()
{
var path = new SKPath();
var topLeft = Forms.ConvertToScaledPixel(ShadowElement.CornerRadius.TopLeft);
var topRight = Forms.ConvertToScaledPixel(ShadowElement.CornerRadius.TopRight);
var bottomLeft = Forms.ConvertToScaledPixel(ShadowElement.CornerRadius.BottomLeft);
var bottomRight = Forms.ConvertToScaledPixel(ShadowElement.CornerRadius.BottomRight);
var padding = Convert.ToSingle(ShadowElement.BorderWidth);
var diameter = padding * 2;
width = width > diameter ? width - diameter : 0;
height = height > diameter ? height - diameter : 0;
var startX = topLeft + padding;
var startY = padding;

path.MoveTo(startX, startY);
path.LineTo(width - topRight + padding, startY);
path.ArcTo(topRight, new SKPoint(width + padding, topRight + padding));
path.LineTo(width + padding, height - bottomRight + padding);
path.ArcTo(bottomRight, new SKPoint(width - bottomRight + padding, height + padding));
path.LineTo(bottomLeft + padding, height + padding);
path.ArcTo(bottomLeft, new SKPoint(padding, height - bottomLeft + padding));
path.LineTo(padding, topLeft + padding);
path.ArcTo(topLeft, new SKPoint(startX, startY));
path.Close();
return path;
if (!UseShadowClipping)
{
CreateShadowCanvas();
}
else
{
DeleteShadowCanvas();
}
UpdateCanvas();
}

void CreateShadowCanvas()
{
if (_shadowCanvasView != null)
return;

_shadowCanvasView = new SKCanvasView(Forms.NativeParent);
_shadowCanvasView.Show();
_shadowCanvasView.PassEvents = true;
_shadowCanvasView.PaintSurface += OnShadowPaint;
Control.Children.Add(_shadowCanvasView);
_shadowCanvasView.Lower();
_shadowCanvasView.SetClip(null);
}

void DeleteShadowCanvas()
{
if (_shadowCanvasView == null)
return;
Control.Children.Remove(_shadowCanvasView);
_shadowCanvasView.Hide();
_shadowCanvasView.PaintSurface -= OnShadowPaint;
_shadowCanvasView.Unrealize();
_shadowCanvasView = null;
}

SKPath CreateShadowPath()
SKPath GetRoundRectPath(bool isShadow)
{
var geometry = NativeView.Geometry;
if (ShadowElement.Content != null)
Expand All @@ -254,10 +300,16 @@ SKPath CreateShadowPath()
}
}

var canvasViewGeometry = !UseShadowClipping && isShadow ? _shadowCanvasView.Geometry : BackgroundCanvas.Geometry;
var path = new SKPath();
var left = geometry.Left - _shadowCanvasView.Geometry.Left;
var top = geometry.Top - _shadowCanvasView.Geometry.Top;
var rect = new SKRect(left, top, left + geometry.Width, top + geometry.Height);
var padding = Convert.ToSingle(ShadowElement.BorderWidth);
// Set margin for shadow in case of using shadow clip.
if (UseShadowClipping) padding += Forms.ConvertToScaledPixel(ShadowElement.ShadowClippingWidth);
var left = geometry.Left - canvasViewGeometry.Left + padding;
var top = geometry.Top - canvasViewGeometry.Top + padding;
var right = left + geometry.Width - (padding * 2);
var bottom = top + geometry.Height - (padding * 2);
var rect = new SKRect(left, top, right, bottom);
var scaledTLRadius = Forms.ConvertToScaledPixel(ShadowElement.CornerRadius.TopLeft) * 2;
var scaledTRRadius = Forms.ConvertToScaledPixel(ShadowElement.CornerRadius.TopRight) * 2;
var scaledBLRadius = Forms.ConvertToScaledPixel(ShadowElement.CornerRadius.BottomLeft) * 2;
Expand All @@ -274,9 +326,9 @@ SKPath CreateShadowPath()
return path;
}

void UpdateShadowGeometry()
void UpdateShadowCanvasGeometry()
{
var geometry = NativeView.Geometry;
var geometry = Control.Geometry;
if (ShadowElement.Content != null)
{
var contentNativeView = Platform.GetOrCreateRenderer(ShadowElement.Content)?.NativeView;
Expand Down Expand Up @@ -317,11 +369,6 @@ void UpdateShadowGeometry()

internal static class SkExtensions
{
internal static SKPath ArcTo(this SKPath path, float radius, SKPoint finalPoint)
{
path.ArcTo(new SKPoint(radius, radius), 0, SKPathArcSize.Small, SKPathDirection.Clockwise, finalPoint);
return path;
}
internal static SKColor ToSK(this Color color)
{
return new SKColor((byte)(color.R * 255), (byte)(color.G * 255), (byte)(color.B * 255), (byte)(color.A * 255));
Expand Down
28 changes: 28 additions & 0 deletions src/Tizen.Theme.Common/ShadowFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ public class ShadowFrame : Frame
/// </summary>
public static readonly BindableProperty ShadowBlurRadiusProperty = BindableProperty.Create(nameof(ShadowBlurRadius), typeof(double), typeof(ShadowFrame), 10d);

/// <summary>
/// Identifies the AllowShadowClipping bindable property.
/// </summary>
public static readonly BindableProperty AllowShadowClippingProperty = BindableProperty.Create(nameof(AllowShadowClipping), typeof(bool), typeof(ShadowFrame), false);

/// <summary>
/// Identifies the ShadowClippingWidth bindable property.
/// </summary>
public static readonly BindableProperty ShadowClippingWidthProperty = BindableProperty.Create(nameof(ShadowClippingWidth), typeof(double), typeof(ShadowFrame), 6.0);

/// <summary>
/// Gets or sets a value that represents CornerRadius.
/// </summary>
Expand Down Expand Up @@ -121,5 +131,23 @@ public double ShadowBlurRadius
get => (double)GetValue(ShadowBlurRadiusProperty);
set => SetValue(ShadowBlurRadiusProperty, value);
}

/// <summary>
/// Gets or sets a value indicating whether shadow clipping is allowed or not.
/// </summary>
public bool AllowShadowClipping
{
get => (bool)GetValue(AllowShadowClippingProperty);
set => SetValue(AllowShadowClippingProperty, value);
}

/// <summary>
/// Gets or sets a value that represents ShadowClippingWidth.
/// </summary>
public double ShadowClippingWidth
{
get => (double)GetValue(ShadowClippingWidthProperty);
set => SetValue(ShadowClippingWidthProperty, value);
}
}
}

0 comments on commit ad3336a

Please sign in to comment.