Skip to content

Commit

Permalink
feat(AcrylicBrush): First iOS implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund committed Aug 5, 2020
1 parent 4dd994f commit 98ebcb4
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 69 deletions.
59 changes: 20 additions & 39 deletions src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.iOSmacOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ internal void Clear()
}

private static IDisposable InnerCreateLayer(UIElement owner, CALayer parent, LayoutState state)
{
{
var area = state.Area;
var background = state.Background;
var borderThickness = state.BorderThickness;
Expand Down Expand Up @@ -159,16 +159,18 @@ private static IDisposable InnerCreateLayer(UIElement owner, CALayer parent, Lay
}
else if (background is AcrylicBrush acrylicBrush)
{
// TODO: react to AlwaysUseFallback changes
if (acrylicBrush.AlwaysUseFallback)
{
Brush.AssignAndObserveBrush(acrylicBrush, color => layer.FillColor = color)
.DisposeWith(disposables);
}
else
var fillMask = new CAShapeLayer()
{
Path = path,
Frame = area,
// We only use the fill color to create the mask area
FillColor = _Color.White.CGColor,
};
// We reduce the adjustedArea again so that the acrylic is inside the border (like in Windows)
adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset);

}
acrylicBrush.Subscribe(owner, area, adjustedArea, parent, sublayers, ref insertionIndex, fillMask)
.DisposeWith(disposables);
}
else
{
Expand Down Expand Up @@ -239,37 +241,16 @@ private static IDisposable InnerCreateLayer(UIElement owner, CALayer parent, Lay
}
else if (background is AcrylicBrush acrylicBrush)
{
// TODO: react to AlwaysUseFallback changes
if (acrylicBrush.AlwaysUseFallback)
{
Brush.AssignAndObserveBrush(acrylicBrush, c => parent.BackgroundColor = c)
.DisposeWith(disposables);

// This is required because changing the CornerRadius changes the background drawing
// implementation and we don't want a rectangular background behind a rounded background.
Disposable.Create(() => parent.BackgroundColor = null)
.DisposeWith(disposables);
}
else
{
var fullArea = new CGRect(
area.X + borderThickness.Left,
area.Y + borderThickness.Top,
area.Width - borderThickness.Left - borderThickness.Right,
area.Height - borderThickness.Top - borderThickness.Bottom);
var fullArea = new CGRect(
area.X + borderThickness.Left,
area.Y + borderThickness.Top,
area.Width - borderThickness.Left - borderThickness.Right,
area.Height - borderThickness.Top - borderThickness.Bottom);

var insideArea = new CGRect(CGPoint.Empty, fullArea.Size);
var insertionIndex = 0;
var insideArea = new CGRect(CGPoint.Empty, fullArea.Size);
var insertionIndex = 0;

acrylicBrush.CreateAcrylicBrushLayers(
owner,
fullArea,
insideArea,
parent,
sublayers,
ref insertionIndex,
fillMask: null);
}
acrylicBrush.Subscribe(owner, fullArea, insideArea, parent, sublayers, ref insertionIndex, fillMask: null);
}
else
{
Expand Down Expand Up @@ -458,7 +439,7 @@ private static void CreateGradientBrushLayers(CGRect fullArea, CGRect insideArea
sublayers.Add(gradientContainerLayer);
}



private class LayoutState : IEquatable<LayoutState>
{
Expand Down
175 changes: 148 additions & 27 deletions src/Uno.UI/UI/Xaml/Media/AcrylicBrush/AcrylicBrush.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using CoreAnimation;
using CoreGraphics;
using UIKit;
using Uno.Disposables;

namespace Windows.UI.Xaml.Media
{
public partial class AcrylicBrush
{
internal void CreateAcrylicBrushLayers(
/// <summary>
/// Subscribes to AcrylicBrush for a given UI element and applies it.
/// </summary>
/// <param name="uiElement">UI element.</param>
/// <returns>Disposable.</returns>
internal IDisposable Subscribe(
UIElement owner,
CGRect fullArea,
CGRect insideArea,
Expand All @@ -16,39 +23,153 @@ internal void CreateAcrylicBrushLayers(
ref int insertionIndex,
CAShapeLayer fillMask)
{
// This layer is the one we apply the mask on. It's the full size of the shape because the mask is as well.
var acrylicContainerLayer = new CALayer
var state = new AcrylicState(
owner,
fullArea,
insideArea,
layer,
sublayers,
insertionIndex++, // we always use a single layer for acrylic
fillMask);

var compositeDisposable = new CompositeDisposable(5);

this.RegisterDisposablePropertyChangedCallback(
AlwaysUseFallbackProperty,
(_, __) => Apply(state))
.DisposeWith(compositeDisposable);

this.RegisterDisposablePropertyChangedCallback(
TintColorProperty,
(_, __) => Apply(state))
.DisposeWith(compositeDisposable);

this.RegisterDisposablePropertyChangedCallback(
TintOpacityProperty,
(_, __) => Apply(state))
.DisposeWith(compositeDisposable);

this.RegisterDisposablePropertyChangedCallback(
OpacityProperty,
(_, __) => Apply(state))
.DisposeWith(compositeDisposable);

Apply(state);

Disposable.Create(() => state.ApplyDisposable.Disposable = null)
.DisposeWith(compositeDisposable);

return compositeDisposable;
}

/// <summary>
/// Applies the current state of Acrylic brush to a given UIElement
/// </summary>
/// <param name="uiElement">UIElement to set background brush to.</param>
private void Apply(AcrylicState acrylicState)
{
var compositeDisposable = new CompositeDisposable();

// Reset existing layers
acrylicState.ApplyDisposable.Disposable = compositeDisposable;

if (acrylicState.AcrylicContainerLayer == null)
{
Frame = fullArea,
Mask = fillMask,
BackgroundColor = UIColor.Clear.CGColor,
MasksToBounds = true,
};
// Initialize the container layer.
// This is done only once and the layer is reused if brush
// properties change.
acrylicState.AcrylicContainerLayer = new CALayer
{
Frame = acrylicState.FullArea,
Mask = acrylicState.FillMask,
BackgroundColor = UIColor.Clear.CGColor,
MasksToBounds = true,
};
acrylicState.Parent.InsertSublayer(acrylicState.AcrylicContainerLayer, acrylicState.InsertionIndex);
acrylicState.Sublayers.Add(acrylicState.AcrylicContainerLayer);

// The layer itself is removed automatically by the BorderLayoutRenderer
}

layer.InsertSublayer(acrylicContainerLayer, insertionIndex++);
var gradientFrame = new CGRect(new CGPoint(insideArea.X, insideArea.Y), insideArea.Size);
if (AlwaysUseFallback)
{
// Apply solid color only
var previousColor = acrylicState.AcrylicContainerLayer.BackgroundColor;
acrylicState.AcrylicContainerLayer.BackgroundColor = FallbackColorWithOpacity;

var acrylicLayer = new CALayer
Disposable.Create(() => acrylicState.AcrylicContainerLayer.BackgroundColor = previousColor)
.DisposeWith(compositeDisposable);
}
else
{
Frame = gradientFrame,
MasksToBounds = true,
Opacity = (float)TintOpacity,
BackgroundColor = TintColor
};
acrylicState.AcrylicContainerLayer.BackgroundColor = UIColor.Clear.CGColor;

var acrylicFrame = new CGRect(new CGPoint(acrylicState.InsideArea.X, acrylicState.InsideArea.Y), acrylicState.InsideArea.Size);

var acrylicLayer = new CALayer
{
Frame = acrylicFrame,
MasksToBounds = true,
Opacity = (float)TintOpacity,
BackgroundColor = TintColor
};

acrylicState.BlurView = new UIVisualEffectView()
{
ClipsToBounds = true,
BackgroundColor = UIColor.Clear,
Frame = acrylicFrame,
Effect = UIBlurEffect.FromStyle(UIBlurEffectStyle.Light)
};

acrylicState.Owner.InsertSubview(acrylicState.BlurView, 0);

acrylicState.AcrylicContainerLayer.AddSublayer(acrylicLayer);

Disposable.Create(() =>
{
acrylicState.AcrylicContainerLayer.Sublayers[0].RemoveFromSuperLayer();
acrylicState.BlurView.RemoveFromSuperview();
acrylicState.BlurView = null;
}).DisposeWith(compositeDisposable);
}
}

/// <summary>
/// Wraps the acrylic brush metadata for a single UI element.
/// </summary>
private class AcrylicState
{
public AcrylicState(UIElement owner, CGRect fullArea, CGRect insideArea, CALayer layer, List<CALayer> sublayers, int insertionIndex, CAShapeLayer fillMask)
{
Owner = owner;
FullArea = fullArea;
InsideArea = insideArea;
Parent = layer;
Sublayers = sublayers;
InsertionIndex = insertionIndex;
FillMask = fillMask;
}

public SerialDisposable ApplyDisposable { get; } = new SerialDisposable();

public CALayer AcrylicContainerLayer { get; set; }

public UIVisualEffectView BlurView { get; set; }

public UIElement Owner { get; }

public CGRect FullArea { get; }

public CGRect InsideArea { get; }

var blurView = new UIVisualEffectView()
{
ClipsToBounds = true,
BackgroundColor = UIColor.Clear
};
blurView.Frame = gradientFrame;
blurView.Effect = UIBlurEffect.FromStyle(UIBlurEffectStyle.Dark);
public CALayer Parent { get; }

owner.InsertSubview(blurView, 0);
public List<CALayer> Sublayers { get; }

acrylicContainerLayer.InsertSublayer(acrylicLayer, 0);
public int InsertionIndex { get; }

sublayers.Add(acrylicContainerLayer);
public CAShapeLayer FillMask { get; }
}
}
}
14 changes: 11 additions & 3 deletions src/Uno.UI/UI/Xaml/Media/AcrylicBrush/AcrylicBrush.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ public partial class AcrylicBrush

private static bool? _isBackdropFilterSupported = null;

/// <summary>
/// Subscribes to AcrylicBrush for a given UI element and applies it.
/// </summary>
/// <param name="uiElement">UI element.</param>
/// <returns>Disposable.</returns>
internal IDisposable Subscribe(UIElement uiElement)
{
var compositeDisposable = new CompositeDisposable(4);
var compositeDisposable = new CompositeDisposable(5);

this.RegisterDisposablePropertyChangedCallback(
AlwaysUseFallbackProperty,
Expand All @@ -39,13 +44,16 @@ internal IDisposable Subscribe(UIElement uiElement)
// Apply the current state of the brush
Apply(uiElement);

Disposable.Create(() => ResetStyle(uiElement))
.DisposeWith(compositeDisposable);

return compositeDisposable;
}

/// <summary>
/// Applies the current state of Acrylic brush to a given UIElement
/// Applies the current state of Acrylic brush to a given UI element
/// </summary>
/// <param name="uiElement">UIElement to set background brush to.</param>
/// <param name="uiElement">UI element to set background brush to.</param>
internal void Apply(UIElement uiElement)
{
var isBackdropSupported = IsBackdropFilterSupported();
Expand Down

0 comments on commit 98ebcb4

Please sign in to comment.