Skip to content

Commit

Permalink
chore: Break down elevation implementation to platform-specific files
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund committed Mar 22, 2022
1 parent a11300f commit 1b2e8df
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 268 deletions.
165 changes: 1 addition & 164 deletions src/Uno.UI.Toolkit/UIElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,173 +69,10 @@ private static void OnElevationChanged(DependencyObject dependencyObject,
{
if (args.NewValue is double elevation)
{
ElevationHelper.SetElevation(dependencyObject, elevation, ElevationColor);
Uno.UI.Helpers.ElevationHelper.SetElevation(dependencyObject, elevation, ElevationColor);
}
}

#if __IOS__ || __MACOS__
internal static void SetElevationInternal(this DependencyObject element, double elevation, Color shadowColor, CGPath path = null)
#elif NETFX_CORE || NETCOREAPP
internal static void SetElevationInternal(this DependencyObject element, double elevation, Color shadowColor, DependencyObject host = null, CornerRadius cornerRadius = default(CornerRadius))
#else
internal static void SetElevationInternal(this DependencyObject element, double elevation, Color shadowColor)
#endif
{
#if __ANDROID__
if (element is Android.Views.View view)
{
AndroidX.Core.View.ViewCompat.SetElevation(view, (float)Uno.UI.ViewHelper.LogicalToPhysicalPixels(elevation));
if(Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.P)
{
view.SetOutlineAmbientShadowColor(shadowColor);
view.SetOutlineSpotShadowColor(shadowColor);
}
}
#elif __IOS__ || __MACOS__
#if __MACOS__
if (element is AppKit.NSView view)
#else
if (element is UIKit.UIView view)
#endif
{
if (elevation > 0)
{
// Values for 1dp elevation according to https://material.io/guidelines/resources/shadows.html#shadows-illustrator
const float x = 0.25f;
const float y = 0.92f * 0.5f; // Looks more accurate than the recommended 0.92f.
const float blur = 0.5f;

#if __MACOS__
view.WantsLayer = true;
view.Shadow ??= new AppKit.NSShadow();
#endif
view.Layer.MasksToBounds = false;
view.Layer.ShadowOpacity = shadowColor.A / 255f;
#if __MACOS__
view.Layer.ShadowColor = AppKit.NSColor.FromRgb(shadowColor.R, shadowColor.G, shadowColor.B).CGColor;
#else
view.Layer.ShadowColor = UIKit.UIColor.FromRGB(shadowColor.R, shadowColor.G, shadowColor.B).CGColor;
#endif
view.Layer.ShadowRadius = (nfloat)(blur * elevation);
view.Layer.ShadowOffset = new CoreGraphics.CGSize(x * elevation, y * elevation);
view.Layer.ShadowPath = path;
}
else if(view.Layer != null)
{
view.Layer.ShadowOpacity = 0;
}
}
#elif __WASM__
if (element is UIElement uiElement)
{
if (elevation > 0)
{
// Values for 1dp elevation according to https://material.io/guidelines/resources/shadows.html#shadows-illustrator
const double x = 0.25d;
const double y = 0.92f * 0.5f; // Looks more accurate than the recommended 0.92f.
const double blur = 0.5f;
var color = Color.FromArgb((byte)(shadowColor.A * .35), shadowColor.R, shadowColor.G, shadowColor.B);

var str = $"{(x * elevation).ToStringInvariant()}px {(y * elevation).ToStringInvariant()}px {(blur * elevation).ToStringInvariant()}px {color.ToCssString()}";
uiElement.SetStyle("box-shadow", str);
uiElement.SetCssClasses("noclip");
}
else
{
uiElement.ResetStyle("box-shadow");
uiElement.UnsetCssClasses("noclip");
}
}
#elif __SKIA__
if (element is UIElement uiElement)
{
var visual = uiElement.Visual;

const float SHADOW_SIGMA_X_MODIFIER = 1f / 3.5f;
const float SHADOW_SIGMA_Y_MODIFIER = 1f / 3.5f;
float x = 0.3f;
float y = 0.92f * 0.5f;

var dx = (float)elevation * x;
var dy = (float)elevation * y;
var sigmaX = (float)elevation * SHADOW_SIGMA_X_MODIFIER;
var sigmaY = (float)elevation * SHADOW_SIGMA_Y_MODIFIER;
var shadow = new ShadowState(dx, dy, sigmaX, sigmaY, shadowColor);
visual.ShadowState = shadow;
}
#elif NETFX_CORE || NETCOREAPP
if (element is UIElement uiElement)
{
var compositor = ElementCompositionPreview.GetElementVisual(uiElement).Compositor;
var spriteVisual = compositor.CreateSpriteVisual();

var newSize = new Vector2(0, 0);
if (uiElement is FrameworkElement contentFE)
{
newSize = new Vector2((float)contentFE.ActualWidth, (float)contentFE.ActualHeight);
}

if (!(host is Canvas uiHost) || newSize == default)
{
return;
}

spriteVisual.Size = newSize;
if (elevation > 0)
{
// Values for 1dp elevation according to https://material.io/guidelines/resources/shadows.html#shadows-illustrator
const float x = 0.25f;
const float y = 0.92f * 0.5f; // Looks more accurate than the recommended 0.92f.
const float blur = 0.5f;

var shadow = compositor.CreateDropShadow();
shadow.Offset = new Vector3((float)elevation*x, (float)elevation*y, -(float)elevation);
shadow.BlurRadius = (float)(blur * elevation);

shadow.Mask = uiElement switch
{
// GetAlphaMask is only available for shapes, images, and textblocks
Shape shape => shape.GetAlphaMask(),
Image image => image.GetAlphaMask(),
TextBlock tb => tb.GetAlphaMask(),
_ => shadow.Mask
};

if (!cornerRadius.Equals(default))
{
var averageRadius =
(cornerRadius.TopLeft +
cornerRadius.TopRight +
cornerRadius.BottomLeft +
cornerRadius.BottomRight) / 4f;

// Create a rectangle with similar corner radius (average for now)
var rect = new Rectangle()
{
Fill = new SolidColorBrush(Colors.White),
Width = newSize.X,
Height = newSize.Y,
RadiusX = averageRadius,
RadiusY = averageRadius
};

uiHost.Children.Add(rect); // The rect need to be in th VisualTree for .GetAlphaMask() to work

shadow.Mask = rect.GetAlphaMask();

uiHost.Children.Remove(rect); // No need anymore, we can discard it.
}

shadow.Color = shadowColor;
shadow.Opacity = shadowColor.A/255f;
spriteVisual.Shadow = shadow;
}

ElementCompositionPreview.SetElementChildVisual(uiHost, spriteVisual);
}
#endif
}

#endregion

internal static Thickness GetPadding(this UIElement uiElement)
Expand Down
19 changes: 19 additions & 0 deletions src/Uno.UI/Helpers/Elevation/ElevationHelper.Android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Uno.UI.Helpers;

internal static class ElevationHelper
{
internal static void SetElevation(DependencyObject element, double elevation, Color shadowColor)
{
if (element is not Android.Views.View view)
{
return;
}

AndroidX.Core.View.ViewCompat.SetElevation(view, (float)Uno.UI.ViewHelper.LogicalToPhysicalPixels(elevation));
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.P)
{
view.SetOutlineAmbientShadowColor(shadowColor);
view.SetOutlineSpotShadowColor(shadowColor);
}
}
}
45 changes: 45 additions & 0 deletions src/Uno.UI/Helpers/Elevation/ElevationHelper.iOSmacOS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace Uno.UI.Helpers;

#if __IOS__
using _View = UIKit.UIView;
#else
using _View = AppKit.NSView;
#endif

internal static class ElevationHelper
{
internal static void SetElevation(DependencyObject element, double elevation, Color shadowColor, CGPath path = null)
{
if (element is not _View view)
{
return;
}

if (elevation > 0)
{
// Values for 1dp elevation according to https://material.io/guidelines/resources/shadows.html#shadows-illustrator
const float x = 0.25f;
const float y = 0.92f * 0.5f; // Looks more accurate than the recommended 0.92f.
const float blur = 0.5f;

#if __MACOS__
view.WantsLayer = true;
view.Shadow ??= new AppKit.NSShadow();
#endif
view.Layer.MasksToBounds = false;
view.Layer.ShadowOpacity = shadowColor.A / 255f;
#if __MACOS__
view.Layer.ShadowColor = AppKit.NSColor.FromRgb(shadowColor.R, shadowColor.G, shadowColor.B).CGColor;
#else
view.Layer.ShadowColor = UIKit.UIColor.FromRGB(shadowColor.R, shadowColor.G, shadowColor.B).CGColor;
#endif
view.Layer.ShadowRadius = (nfloat)(blur * elevation);
view.Layer.ShadowOffset = new CoreGraphics.CGSize(x * elevation, y * elevation);
view.Layer.ShadowPath = path;
}
else if (view.Layer != null)
{
view.Layer.ShadowOpacity = 0;
}
}
}
30 changes: 30 additions & 0 deletions src/Uno.UI/Helpers/Elevation/ElevationHelper.skia.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Uno.UI.Composition.Composition;
using Windows.UI;
using Windows.UI.Xaml;

namespace Uno.UI.Helpers;

internal static class ElevationHelper
{
internal static void SetElevation(DependencyObject element, double elevation, Color shadowColor)
{
if (element is not UIElement uiElement)
{
return;
}

var visual = uiElement.Visual;

const float SHADOW_SIGMA_X_MODIFIER = 1f / 3.5f;
const float SHADOW_SIGMA_Y_MODIFIER = 1f / 3.5f;
float x = 0.3f;
float y = 0.92f * 0.5f;

var dx = (float)elevation * x;
var dy = (float)elevation * y;
var sigmaX = (float)elevation * SHADOW_SIGMA_X_MODIFIER;
var sigmaY = (float)elevation * SHADOW_SIGMA_Y_MODIFIER;
var shadow = new ShadowState(dx, dy, sigmaX, sigmaY, shadowColor);
visual.ShadowState = shadow;
}
}
30 changes: 30 additions & 0 deletions src/Uno.UI/Helpers/Elevation/ElevationHelper.wasm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Uno.UI.Helpers;

internal static class ElevationHelper
{
internal static void SetElevation(DependencyObject element, double elevation, Color shadowColor)
{
if (element is UIElement uiElement)
{
return;
}

if (elevation > 0)
{
// Values for 1dp elevation according to https://material.io/guidelines/resources/shadows.html#shadows-illustrator
const double x = 0.25d;
const double y = 0.92f * 0.5f; // Looks more accurate than the recommended 0.92f.
const double blur = 0.5f;
var color = Color.FromArgb((byte)(shadowColor.A * .35), shadowColor.R, shadowColor.G, shadowColor.B);

var str = $"{(x * elevation).ToStringInvariant()}px {(y * elevation).ToStringInvariant()}px {(blur * elevation).ToStringInvariant()}px {color.ToCssString()}";
uiElement.SetStyle("box-shadow", str);
uiElement.SetCssClasses("noclip");
}
else
{
uiElement.ResetStyle("box-shadow");
uiElement.UnsetCssClasses("noclip");
}
}
}
Loading

0 comments on commit 1b2e8df

Please sign in to comment.