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

Added fixes for Margin, Padding and Thickness properties with UseLayo… #7867

Merged
merged 8 commits into from
Apr 21, 2022
59 changes: 56 additions & 3 deletions src/Avalonia.Base/Layout/LayoutHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,44 @@ public static Size MeasureChild(ILayoutable? control, Size availableSize, Thickn

public static Size ArrangeChild(ILayoutable? child, Size availableSize, Thickness padding, Thickness borderThickness)
{
return ArrangeChild(child, availableSize, padding + borderThickness);
if (IsParentLayoutRounded(child, out double scale))
{
padding = RoundLayoutThickness(padding, scale, scale);
borderThickness = RoundLayoutThickness(borderThickness, scale, scale);
}

return ArrangeChildInternal(child, availableSize, padding + borderThickness);
}

public static Size ArrangeChild(ILayoutable? child, Size availableSize, Thickness padding)
{
if(IsParentLayoutRounded(child, out double scale))
padding = RoundLayoutThickness(padding, scale, scale);

return ArrangeChildInternal(child, availableSize, padding);
}

private static Size ArrangeChildInternal(ILayoutable? child, Size availableSize, Thickness padding)
{
child?.Arrange(new Rect(availableSize).Deflate(padding));

return availableSize;
}

private static bool IsParentLayoutRounded(ILayoutable? child, out double scale)
{
var layoutableParent = (ILayoutable?)child?.GetVisualParent();

if (layoutableParent == null || !((Layoutable)layoutableParent).UseLayoutRounding)
{
scale = 1.0;
return false;
}

scale = GetLayoutScale(layoutableParent);
return true;
}

/// <summary>
/// Invalidates measure for given control and all visual children recursively.
/// </summary>
Expand Down Expand Up @@ -126,6 +154,32 @@ public static Size RoundLayoutSize(Size size, double dpiScaleX, double dpiScaleY
return new Size(RoundLayoutValue(size.Width, dpiScaleX), RoundLayoutValue(size.Height, dpiScaleY));
}

/// <summary>
/// Rounds a thickness to integer values for layout purposes, compensating for high DPI screen
/// coordinates.
/// </summary>
/// <param name="thickness">Input thickness.</param>
/// <param name="dpiScaleX">DPI along x-dimension.</param>
/// <param name="dpiScaleY">DPI along y-dimension.</param>
/// <returns>Value of thickness that will be rounded under screen DPI.</returns>
/// <remarks>
/// This is a layout helper method. It takes DPI into account and also does not return
/// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper
/// associated with the UseLayoutRounding property and should not be used as a general rounding
/// utility.
/// </remarks>
public static Thickness RoundLayoutThickness(Thickness thickness, double dpiScaleX, double dpiScaleY)
{
return new Thickness(
RoundLayoutValue(thickness.Left, dpiScaleX),
RoundLayoutValue(thickness.Top, dpiScaleY),
RoundLayoutValue(thickness.Right, dpiScaleX),
RoundLayoutValue(thickness.Bottom, dpiScaleY)
);
}



/// <summary>
/// Calculates the value to be used for layout rounding at high DPI.
/// </summary>
Expand Down Expand Up @@ -163,8 +217,7 @@ public static double RoundLayoutValue(double value, double dpiScale)

return newValue;
}



/// <summary>
/// Calculates the min and max height for a control. Ported from WPF.
/// </summary>
Expand Down
14 changes: 12 additions & 2 deletions src/Avalonia.Base/Layout/Layoutable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -643,17 +643,27 @@ protected virtual void ArrangeCore(Rect finalRect)
{
if (IsVisible)
{
var useLayoutRounding = UseLayoutRounding;
var scale = LayoutHelper.GetLayoutScale(this);

var margin = Margin;
var originX = finalRect.X + margin.Left;
var originY = finalRect.Y + margin.Top;

// Margin has to be treated separately because the layout rounding function is not linear
// f(a + b) != f(a) + f(b)
// If the margin isn't pre-rounded some sizes will be offset by 1 pixel in certain scales.
if (useLayoutRounding)
{
margin = LayoutHelper.RoundLayoutThickness(margin, scale, scale);
}

var availableSizeMinusMargins = new Size(
Math.Max(0, finalRect.Width - margin.Left - margin.Right),
Math.Max(0, finalRect.Height - margin.Top - margin.Bottom));
var horizontalAlignment = HorizontalAlignment;
var verticalAlignment = VerticalAlignment;
var size = availableSizeMinusMargins;
var scale = LayoutHelper.GetLayoutScale(this);
var useLayoutRounding = UseLayoutRounding;

if (horizontalAlignment != HorizontalAlignment.Stretch)
{
Expand Down
48 changes: 47 additions & 1 deletion src/Avalonia.Controls/Border.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using Avalonia.Collections;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Utilities;
using Avalonia.VisualTree;

namespace Avalonia.Controls
Expand Down Expand Up @@ -69,6 +71,8 @@ public partial class Border : Decorator, IVisualWithRoundRectClip
AvaloniaProperty.Register<Border, PenLineJoin>(nameof(BorderLineJoin), PenLineJoin.Miter);

private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
private Thickness? _layoutThickness;
private double _scale;

/// <summary>
/// Initializes static members of the <see cref="Border"/> class.
Expand All @@ -88,6 +92,18 @@ static Border()
AffectsMeasure<Border>(BorderThicknessProperty);
}

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
switch (change.Property.Name)
{
case nameof(UseLayoutRounding):
case nameof(BorderThickness):
_layoutThickness = null;
break;
}
}

/// <summary>
/// Gets or sets a brush with which to paint the background.
/// </summary>
Expand Down Expand Up @@ -169,13 +185,43 @@ public BoxShadows BoxShadow
set => SetValue(BoxShadowProperty, value);
}

private Thickness LayoutThickness
{
get
{
VerifyScale();

if (_layoutThickness == null)
{
var borderThickness = BorderThickness;

if (UseLayoutRounding)
borderThickness = LayoutHelper.RoundLayoutThickness(BorderThickness, _scale, _scale);

_layoutThickness = borderThickness;
}

return _layoutThickness.Value;
}
}

private void VerifyScale()
{
var currentScale = LayoutHelper.GetLayoutScale(this);
if (MathUtilities.AreClose(currentScale, _scale))
return;

_scale = currentScale;
_layoutThickness = null;
}

/// <summary>
/// Renders the control.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
{
_borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush,
_borderRenderHelper.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush,
BoxShadow, BorderDashOffset, BorderLineCap, BorderLineJoin, BorderDashArray);
}

Expand Down
55 changes: 51 additions & 4 deletions src/Avalonia.Controls/Presenters/ContentPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Utilities;

namespace Avalonia.Controls.Presenters
{
Expand Down Expand Up @@ -415,6 +416,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
case nameof(TemplatedParent):
TemplatedParentChanged(change);
break;
case nameof(UseLayoutRounding):
case nameof(BorderThickness):
_layoutThickness = null;
break;
}
}

Expand Down Expand Up @@ -495,10 +500,43 @@ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e
InvalidateMeasure();
}

private Thickness? _layoutThickness;
private double _scale;

private Thickness LayoutThickness
{
get
{
VerifyScale();

if (_layoutThickness == null)
{
var borderThickness = BorderThickness;

if (UseLayoutRounding)
borderThickness = LayoutHelper.RoundLayoutThickness(BorderThickness, _scale, _scale);

_layoutThickness = borderThickness;
}

return _layoutThickness.Value;
}
}

private void VerifyScale()
{
var currentScale = LayoutHelper.GetLayoutScale(this);
if (MathUtilities.AreClose(currentScale, _scale))
return;

_scale = currentScale;
_layoutThickness = null;
}

/// <inheritdoc/>
public override void Render(DrawingContext context)
{
_borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush,
_borderRenderer.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush,
BoxShadow);
}

Expand Down Expand Up @@ -566,13 +604,22 @@ internal Size ArrangeOverrideImpl(Size finalSize, Vector offset)
{
if (Child == null) return finalSize;

var padding = Padding + BorderThickness;
var useLayoutRounding = UseLayoutRounding;
var scale = LayoutHelper.GetLayoutScale(this);
var padding = Padding;
var borderThickness = BorderThickness;

if (useLayoutRounding)
{
padding = LayoutHelper.RoundLayoutThickness(padding, scale, scale);
borderThickness = LayoutHelper.RoundLayoutThickness(borderThickness, scale, scale);
}

padding += borderThickness;
var horizontalContentAlignment = HorizontalContentAlignment;
var verticalContentAlignment = VerticalContentAlignment;
var useLayoutRounding = UseLayoutRounding;
var availableSize = finalSize;
var sizeForChild = availableSize;
var scale = LayoutHelper.GetLayoutScale(this);
var originX = offset.X;
var originY = offset.Y;

Expand Down