Skip to content

Commit

Permalink
Merge pull request unoplatform#15332 from unoplatform/dev/mazi/border…
Browse files Browse the repository at this point in the history
…layer-unstatic

refactor: Simplify `BorderLayerRenderer` on Skia
  • Loading branch information
MartinZikmund authored Feb 8, 2024
2 parents d33471c + 84f8575 commit 2ee46d4
Show file tree
Hide file tree
Showing 36 changed files with 443 additions and 250 deletions.
2 changes: 1 addition & 1 deletion src/Uno.UI.Runtime.Skia.Gtk/UI/Controls/GtkTextBoxView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Uno.UI.Runtime.Skia.Gtk.Extensions;
using static Microsoft.UI.Xaml.Shapes.BorderLayerRenderer;
using static Uno.UI.Xaml.Controls.BorderLayerRenderer;
using GtkWindow = Gtk.Window;
using Uno.UI.Runtime.Skia.Gtk.Helpers.Dpi;
using Windows.Graphics.Display;
Expand Down
1 change: 1 addition & 0 deletions src/Uno.UI/UI/Xaml/Controls/Border/Border.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Font = Android.Graphics.Typeface;
using Android.Graphics;
using Android.Views;
using Uno.UI.Xaml.Controls;

namespace Microsoft.UI.Xaml.Controls
{
Expand Down
17 changes: 17 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/Border/Border.IBorderInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Uno.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;

namespace Microsoft.UI.Xaml.Controls;

partial class Border : IBorderInfoProvider
{
Brush IBorderInfoProvider.Background => Background;

BackgroundSizing IBorderInfoProvider.BackgroundSizing => BackgroundSizing;

Brush IBorderInfoProvider.BorderBrush => BorderBrush;

Thickness IBorderInfoProvider.BorderThickness => BorderThickness;

CornerRadius IBorderInfoProvider.CornerRadius => CornerRadius;
}
2 changes: 2 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/Border/Border.iOSmacOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using Microsoft.UI.Xaml.Media;
using CoreGraphics;
using Uno.UI.Xaml.Media;
using Uno.UI.Xaml.Controls;

#if __IOS__
using UIKit;
using _Image = UIKit.UIImage;
Expand Down
19 changes: 5 additions & 14 deletions src/Uno.UI/UI/Xaml/Controls/Border/Border.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,19 @@
using System.Numerics;
using Windows.Foundation;
using Microsoft.UI.Xaml.Shapes;
using Uno.UI.Xaml.Controls;

namespace Microsoft.UI.Xaml.Controls
{
public partial class Border
{
private BorderLayerRenderer _borderRenderer = new BorderLayerRenderer();

public Border()
{
Loaded += (s, e) => UpdateBorder();
Unloaded += (s, e) => _borderRenderer.Clear();
LayoutUpdated += (s, e) => UpdateBorder();
BorderRenderer = new BorderLayerRenderer(this);
}

internal BorderLayerRenderer BorderRenderer { get; }

partial void OnChildChangedPartial(View previousValue, View newValue)
{
if (previousValue != null)
Expand All @@ -42,15 +41,7 @@ private void UpdateBorder()
{
if (Visual != null)
{
_borderRenderer.UpdateLayer(
this,
Background,
BackgroundSizing,
BorderThickness,
BorderBrush,
CornerRadius,
null
);
BorderRenderer.Update();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Rect = Windows.Foundation.Rect;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace Microsoft.UI.Xaml.Controls
namespace Uno.UI.Xaml.Controls
{
partial class BorderLayerRenderer
{
Expand Down
97 changes: 33 additions & 64 deletions src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.cs
Original file line number Diff line number Diff line change
@@ -1,84 +1,53 @@
using System;
using System.Collections.Generic;
using System.Text;
using Windows.Foundation;
#if __SKIA__
using System;
using Microsoft.UI.Xaml;
using Uno.UI;
using Uno.Disposables;

namespace Microsoft.UI.Xaml.Shapes;
namespace Uno.UI.Xaml.Controls;

/// <summary>
/// Provides Border and Background rendering capabilities to a UI element.
/// </summary>
internal partial class BorderLayerRenderer
{
/* The border is defined by the inner and outer rounded-rectangles.
* Each rounded rectangle is composed of 4x lines and 4x 90' arcs.
* ╭─────╮
* │A 2 B│
* │1 3│
* │D 4 C│
* ╰─────╯
* Three factors determine the border: BorderThickness, CornerRadius, and AvailableSize.
* What each part affects:
* - CornerRadius="1A2,2B3,3C4,4D1"
* - BorderThickness="D1A,A2B,B3C,C4D"
* - AvailableSize is used to constrain the radius.
*
* note: technically CornerRadius (together with AvailableSize) also affects the adjacent corner,
* as both points on the same axis will compete for what's available when both add up exceeds
* the available length. (see: h/vRatio in CalculateBorderEllipse)
*/
private readonly FrameworkElement _owner;
private readonly IBorderInfoProvider _borderInfoProvider;

public enum Corner { TopLeft, TopRight, BottomRight, BottomLeft }
private BorderLayerState _currentState;

public static Rect CalculateBorderEllipseBbox(Rect bbox, Corner corner, CornerRadius cr, Thickness bt, bool inner = false)
public BorderLayerRenderer(FrameworkElement owner)
{
var (cr0, hThickness, vThickness, hComplement, vComplement) = corner switch
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (owner is not IBorderInfoProvider borderInfoProvider)
{
Corner.TopLeft => (cr.TopLeft, bt.Left, bt.Top, cr.TopRight, cr.BottomLeft),
Corner.TopRight => (cr.TopRight, bt.Right, bt.Top, cr.TopLeft, cr.BottomRight),
Corner.BottomRight => (cr.BottomRight, bt.Right, bt.Bottom, cr.BottomLeft, cr.TopRight),
Corner.BottomLeft => (cr.BottomLeft, bt.Left, bt.Bottom, cr.BottomRight, cr.TopLeft),
throw new InvalidOperationException("BorderLayerRenderer requires an owner which implements IBorderInfoProvider");
}

_ => throw new ArgumentOutOfRangeException($"Invalid corner: {corner}"),
};
_borderInfoProvider = borderInfoProvider;

// there is still a corner to be painted, albeit not rounded.
if (cr0 == 0) return AlignCorner(bbox, corner, default);

// The ellipse can only grow up to twice the available length.
// This is further limited by the ratio between the corner-radius
// of that corner and the adjacent corner on the same line.
var hRatio = hComplement == 0 ? 1 : (cr0 / (cr0 + hComplement));
var vRatio = vComplement == 0 ? 1 : (cr0 / (cr0 + vComplement));

// if size is empty here, there is still a corner to be painted, just not rounded.
var size = new Size(
width: Math.Max(0, Math.Min(bbox.Width * 2 * hRatio, cr0 * 2 + (inner ? -hThickness : hThickness))),
height: Math.Max(0, Math.Min(bbox.Height * 2 * vRatio, cr0 * 2 + (inner ? -vThickness : vThickness)))
);
var result = AlignCorner(bbox, corner, size);

return result;
_owner.Loaded += (s, e) => Update();
_owner.Unloaded += (s, e) => Clear();
_owner.LayoutUpdated += (s, e) => Update();
}

/// <summary>
/// Arrange a size on the bounding-<paramref name="bbox"/> so that the borders neighboring
/// to the <paramref name="corner"/> are overlapped.
/// Updates the border.
/// </summary>
/// <remarks>The resulting rect can be outside of the bounding-box.</remarks>
private static Rect AlignCorner(Rect bbox, Corner corner, Size size)
internal void Update()
{
// note: the ellipse can project outside the bounding-box
// because only a quarter needs to be constrained within.
var location = (Point)(corner switch
if (_owner.IsLoaded)
{
Corner.TopLeft => new(bbox.Left, bbox.Top),
Corner.TopRight => new(bbox.Left + bbox.Width - size.Width, bbox.Top),
Corner.BottomRight => new(bbox.Left + bbox.Width - size.Width, bbox.Top + bbox.Height - size.Height),
Corner.BottomLeft => new(bbox.Left, bbox.Top + bbox.Height - size.Height),
UpdatePlatform();
}
}

_ => throw new ArgumentOutOfRangeException($"Invalid corner: {corner}"),
});
/// <summary>
/// Removes added layers and subscriptions.
/// </summary>
internal void Clear() => ClearPlatform();

return new(location, size);
}
partial void UpdatePlatform();

partial void ClearPlatform();
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Uno.UI.Extensions;
using Uno.UI.Xaml.Media;
using ObjCRuntime;
using Microsoft.UI.Xaml;

#if __IOS__
using UIKit;
Expand All @@ -31,7 +32,7 @@

using RadialGradientBrush = Microsoft/* UWP don't rename */.UI.Xaml.Media.RadialGradientBrush;

namespace Microsoft.UI.Xaml.Shapes
namespace Uno.UI.Xaml.Controls
{
partial class BorderLayerRenderer
{
Expand Down Expand Up @@ -670,5 +671,78 @@ private record LayoutState(
{
// internal CGPath BoundsPath { get; set; }
}

/* The border is defined by the inner and outer rounded-rectangles.
* Each rounded rectangle is composed of 4x lines and 4x 90' arcs.
* ╭─────╮
* │A 2 B│
* │1 3│
* │D 4 C│
* ╰─────╯
* Three factors determine the border: BorderThickness, CornerRadius, and AvailableSize.
* What each part affects:
* - CornerRadius="1A2,2B3,3C4,4D1"
* - BorderThickness="D1A,A2B,B3C,C4D"
* - AvailableSize is used to constrain the radius.
*
* note: technically CornerRadius (together with AvailableSize) also affects the adjacent corner,
* as both points on the same axis will compete for what's available when both add up exceeds
* the available length. (see: h/vRatio in CalculateBorderEllipse)
*/

public enum Corner { TopLeft, TopRight, BottomRight, BottomLeft }

public static Rect CalculateBorderEllipseBbox(Rect bbox, Corner corner, CornerRadius cr, Thickness bt, bool inner = false)
{
var (cr0, hThickness, vThickness, hComplement, vComplement) = corner switch
{
Corner.TopLeft => (cr.TopLeft, bt.Left, bt.Top, cr.TopRight, cr.BottomLeft),
Corner.TopRight => (cr.TopRight, bt.Right, bt.Top, cr.TopLeft, cr.BottomRight),
Corner.BottomRight => (cr.BottomRight, bt.Right, bt.Bottom, cr.BottomLeft, cr.TopRight),
Corner.BottomLeft => (cr.BottomLeft, bt.Left, bt.Bottom, cr.BottomRight, cr.TopLeft),

_ => throw new ArgumentOutOfRangeException($"Invalid corner: {corner}"),
};

// there is still a corner to be painted, albeit not rounded.
if (cr0 == 0) return AlignCorner(bbox, corner, default);

// The ellipse can only grow up to twice the available length.
// This is further limited by the ratio between the corner-radius
// of that corner and the adjacent corner on the same line.
var hRatio = hComplement == 0 ? 1 : (cr0 / (cr0 + hComplement));
var vRatio = vComplement == 0 ? 1 : (cr0 / (cr0 + vComplement));

// if size is empty here, there is still a corner to be painted, just not rounded.
var size = new Size(
width: Math.Max(0, Math.Min(bbox.Width * 2 * hRatio, cr0 * 2 + (inner ? -hThickness : hThickness))),
height: Math.Max(0, Math.Min(bbox.Height * 2 * vRatio, cr0 * 2 + (inner ? -vThickness : vThickness)))
);
var result = AlignCorner(bbox, corner, size);

return result;
}

/// <summary>
/// Arrange a size on the bounding-<paramref name="bbox"/> so that the borders neighboring
/// to the <paramref name="corner"/> are overlapped.
/// </summary>
/// <remarks>The resulting rect can be outside of the bounding-box.</remarks>
private static Rect AlignCorner(Rect bbox, Corner corner, Size size)
{
// note: the ellipse can project outside the bounding-box
// because only a quarter needs to be constrained within.
var location = (Point)(corner switch
{
Corner.TopLeft => new(bbox.Left, bbox.Top),
Corner.TopRight => new(bbox.Left + bbox.Width - size.Width, bbox.Top),
Corner.BottomRight => new(bbox.Left + bbox.Width - size.Width, bbox.Top + bbox.Height - size.Height),
Corner.BottomLeft => new(bbox.Left, bbox.Top + bbox.Height - size.Height),

_ => throw new ArgumentOutOfRangeException($"Invalid corner: {corner}"),
});

return new(location, size);
}
}
}
30 changes: 15 additions & 15 deletions src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.reference.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;

namespace Microsoft.UI.Xaml.Shapes
namespace Uno.UI.Xaml.Controls;

partial class BorderLayerRenderer
{
partial class BorderLayerRenderer
{
public void UpdateLayer(
FrameworkElement owner,
Brush background,
BackgroundSizing backgroundSizing,
Thickness borderThickness,
Brush borderBrush,
CornerRadius cornerRadius,
object backgroundImage
)
{ }
}
public void UpdateLayer(
FrameworkElement owner,
Brush background,
BackgroundSizing backgroundSizing,
Thickness borderThickness,
Brush borderBrush,
CornerRadius cornerRadius,
object backgroundImage
)
{ }
}
Loading

0 comments on commit 2ee46d4

Please sign in to comment.