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

Support Min/Max Height/Width on IView #2265

Merged
merged 21 commits into from
Aug 27, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ protected override void OnLayout(bool changed, int l, int t, int r, int b)
(l, t, r, b) = Context.ToPixels(ipc.ContainerArea);
}

pageViewGroup.Measure(r - l, b - t);
var mode = MeasureSpecMode.Exactly;
var widthSpec = mode.MakeMeasureSpec(r - l);
var heightSpec = mode.MakeMeasureSpec(b - t);

pageViewGroup.Measure(widthSpec, heightSpec);
pageViewGroup.Layout(l, t, r, b);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Compatibility/Core/src/Android/VisualElementTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public void UpdateLayout()
formsViewGroup.MeasureAndLayout(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.Exactly), x, y, x + width, y + height);
Performance.Stop(reference, "MeasureAndLayout");
}
else if (aview is LayoutViewGroup && width == 0 && height == 0)
else if ((aview is LayoutViewGroup || aview is PageViewGroup) && width == 0 && height == 0)
{
// Nothing to do here; just chill.
}
Expand Down
7 changes: 5 additions & 2 deletions src/Controls/src/Core/HandlerImpl/ScrollView.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ protected override Size MeasureOverride(double widthConstraint, double heightCon
// The value from ComputeDesiredSize won't account for any margins on the Content; we'll need to do that manually
// And we'll use ResolveConstraints to make sure we're sticking within and explicit Height/Width values or externally
// imposed constraints
var desiredWidth = ResolveConstraints(widthConstraint, Width, defaultSize.Width + contentMargin.HorizontalThickness);
var desiredHeight = ResolveConstraints(heightConstraint, Height, defaultSize.Height + contentMargin.VerticalThickness);
var width = (this as IView).Width;
var height = (this as IView).Height;

var desiredWidth = ResolveConstraints(widthConstraint, width, defaultSize.Width + contentMargin.HorizontalThickness);
var desiredHeight = ResolveConstraints(heightConstraint, height, defaultSize.Height + contentMargin.VerticalThickness);

DesiredSize = new Size(desiredWidth, desiredHeight);
return DesiredSize;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Maui.Graphics;
using System;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Layouts;

namespace Microsoft.Maui.Controls
Expand Down Expand Up @@ -118,8 +119,99 @@ Semantics IView.Semantics
internal Semantics SetupSemantics() =>
_semantics ??= new Semantics();

double IView.Width => WidthRequest;
double IView.Height => HeightRequest;
static void ValidatePositive(double value, string name)
{
if (value < 0)
{
throw new InvalidOperationException($"{name} cannot be less than zero.");
}
}

double IView.Width
{
get
{
if (!IsSet(WidthRequestProperty))
{
return Primitives.Dimension.Unset;
}

// Access once up front to avoid multiple GetValue calls
var value = WidthRequest;
ValidatePositive(value, nameof(IView.Width));
return value;
}
}

double IView.Height
{
get
{
if (!IsSet(HeightRequestProperty))
{
return Primitives.Dimension.Unset;
}

// Access once up front to avoid multiple GetValue calls
var value = HeightRequest;
ValidatePositive(value, nameof(IView.Height));
return value;
}
}

double IView.MinimumWidth
{
get
{
if (!IsSet(MinimumWidthRequestProperty))
{
return Primitives.Dimension.Minimum;
}

// Access once up front to avoid multiple GetValue calls
var value = MinimumWidthRequest;
ValidatePositive(value, nameof(IView.MinimumWidth));
return value;
}
}

double IView.MinimumHeight
{
get
{
if (!IsSet(MinimumHeightRequestProperty))
{
return Primitives.Dimension.Minimum;
}

// Access once up front to avoid multiple GetValue calls
var value = MinimumHeightRequest;
ValidatePositive(value, nameof(IView.MinimumHeight));
return value;
}
}

double IView.MaximumWidth
{
get
{
// Access once up front to avoid multiple GetValue calls
var value = MaximumWidthRequest;
ValidatePositive(value, nameof(IView.MaximumWidth));
return value;
}
}

double IView.MaximumHeight
{
get
{
// Access once up front to avoid multiple GetValue calls
var value = MaximumHeightRequest;
ValidatePositive(value, nameof(IView.MaximumHeight));
return value;
}
}

Thickness IView.Margin => Thickness.Zero;
}
Expand Down
28 changes: 24 additions & 4 deletions src/Controls/src/Core/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,17 @@ void InvalidateGradientBrushRequested(object sender, EventArgs e)
public static readonly BindableProperty TriggersProperty = TriggersPropertyKey.BindableProperty;


public static readonly BindableProperty WidthRequestProperty = BindableProperty.Create("WidthRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);
public static readonly BindableProperty WidthRequestProperty = BindableProperty.Create(nameof(WidthRequest), typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);

public static readonly BindableProperty HeightRequestProperty = BindableProperty.Create("HeightRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);
public static readonly BindableProperty HeightRequestProperty = BindableProperty.Create(nameof(HeightRequest), typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);

public static readonly BindableProperty MinimumWidthRequestProperty = BindableProperty.Create("MinimumWidthRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);
public static readonly BindableProperty MinimumWidthRequestProperty = BindableProperty.Create(nameof(MinimumWidthRequest), typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);

public static readonly BindableProperty MinimumHeightRequestProperty = BindableProperty.Create("MinimumHeightRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);
public static readonly BindableProperty MinimumHeightRequestProperty = BindableProperty.Create(nameof(MinimumHeightRequest), typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);

public static readonly BindableProperty MaximumWidthRequestProperty = BindableProperty.Create(nameof(MaximumWidthRequest), typeof(double), typeof(VisualElement), double.PositiveInfinity, propertyChanged: OnRequestChanged);

public static readonly BindableProperty MaximumHeightRequestProperty = BindableProperty.Create(nameof(MaximumHeightRequest), typeof(double), typeof(VisualElement), double.PositiveInfinity, propertyChanged: OnRequestChanged);

[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindablePropertyKey IsFocusedPropertyKey = BindableProperty.CreateReadOnly("IsFocused",
Expand Down Expand Up @@ -436,6 +440,18 @@ public double MinimumWidthRequest
set { SetValue(MinimumWidthRequestProperty, value); }
}

public double MaximumHeightRequest
{
get { return (double)GetValue(MaximumHeightRequestProperty); }
set { SetValue(MaximumHeightRequestProperty, value); }
}

public double MaximumWidthRequest
{
get { return (double)GetValue(MaximumWidthRequestProperty); }
set { SetValue(MaximumWidthRequestProperty, value); }
}

public double Opacity
{
get { return (double)GetValue(OpacityProperty); }
Expand Down Expand Up @@ -1038,6 +1054,10 @@ static void OnRequestChanged(BindableObject bindable, object oldvalue, object ne
{
fe.Handler?.UpdateValue(nameof(IView.Width));
fe.Handler?.UpdateValue(nameof(IView.Height));
fe.Handler?.UpdateValue(nameof(IView.MinimumHeight));
fe.Handler?.UpdateValue(nameof(IView.MinimumWidth));
fe.Handler?.UpdateValue(nameof(IView.MaximumHeight));
fe.Handler?.UpdateValue(nameof(IView.MaximumWidth));
}

((VisualElement)bindable).InvalidateMeasureInternal(InvalidationTrigger.SizeRequestChanged);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void GridInsideStackLayout()
var stackLayout = new StackLayout() { IsPlatformEnabled = true };
var grid = new Grid() { IsPlatformEnabled = true, HeightRequest = 50 };
var label = new Label() { IsPlatformEnabled = true };
var expectedSize = new Size(50, 50);
var expectedSize = new Size(100, 50);

var view = Substitute.For<IViewHandler>();
view.GetDesiredSize(default, default).ReturnsForAnyArgs(expectedSize);
Expand All @@ -51,7 +51,7 @@ public void GridInsideStackLayout()
grid.Children.Add(label);
contentPage.Content = stackLayout;

var rect = new Rectangle(0, 0, 50, 100);
var rect = new Rectangle(Point.Zero, expectedSize);
(contentPage as IView).Measure(expectedSize.Width, expectedSize.Height);
(contentPage as IView).Arrange(rect);

Expand Down
26 changes: 23 additions & 3 deletions src/Core/src/Core/IView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,40 @@ public interface IView : IElement, ITransform
Paint? Background { get; }

/// <summary>
/// Gets the bounds of the View.
/// Gets the bounds of the View within its container.
/// </summary>
Rectangle Frame { get; set; }

/// <summary>
/// Gets the specified width of this View.
/// Gets the specified width of the IView.
/// </summary>
double Width { get; }

/// <summary>
/// Gets the specified height of this View.
/// Gets the specified minimum width constraint of the IView, between zero and double.PositiveInfinity.
/// </summary>
double MinimumWidth { get; }

/// <summary>
/// Gets the specified maximum width constraint of the IView, between zero and double.PositiveInfinity.
/// </summary>
double MaximumWidth { get; }

/// <summary>
/// Gets the specified height of the IView.
/// </summary>
double Height { get; }

/// <summary>
/// Gets the specified minimum height constraint of the IView, between zero and double.PositiveInfinity.
/// </summary>
double MinimumHeight { get; }

/// <summary>
/// Gets the specified maximum height constraint of the IView, between zero and double.PositiveInfinity.
/// </summary>
double MaximumHeight { get; }

/// <summary>
/// The Margin represents the distance between an view and its adjacent views.
/// </summary>
Expand Down
24 changes: 24 additions & 0 deletions src/Core/src/Handlers/View/ViewHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public abstract partial class ViewHandler : ElementHandler, IViewHandler
[nameof(IView.FlowDirection)] = MapFlowDirection,
[nameof(IView.Width)] = MapWidth,
[nameof(IView.Height)] = MapHeight,
[nameof(IView.MinimumHeight)] = MapMinimumHeight,
[nameof(IView.MaximumHeight)] = MapMaximumHeight,
[nameof(IView.MinimumWidth)] = MapMinimumWidth,
[nameof(IView.MaximumWidth)] = MapMaximumWidth,
[nameof(IView.IsEnabled)] = MapIsEnabled,
[nameof(IView.Opacity)] = MapOpacity,
[nameof(IView.Semantics)] = MapSemantics,
Expand Down Expand Up @@ -138,6 +142,26 @@ public static void MapHeight(ViewHandler handler, IView view)
((NativeView?)handler.NativeView)?.UpdateHeight(view);
}

public static void MapMinimumHeight(ViewHandler handler, IView view)
{
((NativeView?)handler.NativeView)?.UpdateMinimumHeight(view);
}

public static void MapMaximumHeight(ViewHandler handler, IView view)
{
((NativeView?)handler.NativeView)?.UpdateMaximumHeight(view);
}

public static void MapMinimumWidth(ViewHandler handler, IView view)
{
((NativeView?)handler.NativeView)?.UpdateMinimumWidth(view);
}

public static void MapMaximumWidth(ViewHandler handler, IView view)
{
((NativeView?)handler.NativeView)?.UpdateMaximumWidth(view);
}

public static void MapIsEnabled(ViewHandler handler, IView view)
{
((NativeView?)handler.NativeView)?.UpdateIsEnabled(view);
Expand Down
14 changes: 10 additions & 4 deletions src/Core/src/Handlers/View/ViewHandlerOfT.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Android.Content;
using Android.Views;
using Microsoft.Maui.Graphics;
using static Microsoft.Maui.Primitives.Dimension;

namespace Microsoft.Maui.Handlers
{
Expand Down Expand Up @@ -55,25 +56,30 @@ public override Size GetDesiredSize(double widthConstraint, double heightConstra
}

// Create a spec to handle the native measure
var widthSpec = CreateMeasureSpec(widthConstraint, VirtualView.Width);
var heightSpec = CreateMeasureSpec(heightConstraint, VirtualView.Height);
var widthSpec = CreateMeasureSpec(widthConstraint, VirtualView.Width, VirtualView.MaximumWidth);
var heightSpec = CreateMeasureSpec(heightConstraint, VirtualView.Height, VirtualView.MaximumHeight);

nativeView.Measure(widthSpec, heightSpec);

// Convert back to xplat sizes for the return value
return Context.FromPixels(nativeView.MeasuredWidth, nativeView.MeasuredHeight);
}

int CreateMeasureSpec(double constraint, double explicitSize)
int CreateMeasureSpec(double constraint, double explicitSize, double maximumSize)
{
var mode = MeasureSpecMode.AtMost;

if (explicitSize >= 0)
if (IsExplicitSet(explicitSize))
{
// We have a set value (i.e., a Width or Height)
mode = MeasureSpecMode.Exactly;
constraint = explicitSize;
}
else if (IsMaximumSet(maximumSize))
{
mode = MeasureSpecMode.AtMost;
constraint = maximumSize;
}
else if (double.IsInfinity(constraint))
{
// We've got infinite space; we'll leave the size up to the native control
Expand Down
39 changes: 32 additions & 7 deletions src/Core/src/Handlers/View/ViewHandlerOfT.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Maui.Graphics;
using UIKit;
using static Microsoft.Maui.Primitives.Dimension;

namespace Microsoft.Maui.Handlers
{
Expand Down Expand Up @@ -46,11 +47,6 @@ public override Size GetDesiredSize(double widthConstraint, double heightConstra
return new Size(widthConstraint, heightConstraint);
}

var explicitWidth = VirtualView.Width;
var explicitHeight = VirtualView.Height;
var hasExplicitWidth = explicitWidth >= 0;
var hasExplicitHeight = explicitHeight >= 0;

var sizeThatFits = nativeView.SizeThatFits(new CoreGraphics.CGSize((float)widthConstraint, (float)heightConstraint));

var size = new Size(
Expand All @@ -63,8 +59,37 @@ public override Size GetDesiredSize(double widthConstraint, double heightConstra
size = new Size(nativeView.Frame.Width, nativeView.Frame.Height);
}

return new Size(hasExplicitWidth ? explicitWidth : size.Width,
hasExplicitHeight ? explicitHeight : size.Height);
var finalWidth = ResolveConstraints(size.Width, VirtualView.Width, VirtualView.MinimumWidth, VirtualView.MaximumWidth);
var finalHeight = ResolveConstraints(size.Height, VirtualView.Height, VirtualView.MinimumHeight, VirtualView.MaximumHeight);

return new Size(finalWidth, finalHeight);
}

double ResolveConstraints(double measured, double exact, double min, double max)
{
var resolved = measured;

if (IsExplicitSet(exact))
{
// If an exact value has been specified, try to use that
resolved = exact;
}

if (resolved > max)
{
// Apply the max value constraint (if any)
// If the exact value is in conflict with the max value, the max value should win
resolved = max;
}

if (resolved < min)
{
// Apply the min value constraint (if any)
// If the exact or max value is in conflict with the min value, the min value should win
resolved = min;
}

return resolved;
}

protected override void SetupContainer()
Expand Down
Loading