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

Compressed layout is back (maybe?) #24758

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion src/Controls/src/Core/Layout/Layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.Maui.Controls
/// Base class for layouts that allow you to arrange and group UI controls in your application.
/// </summary>
[ContentProperty(nameof(Children))]
public abstract partial class Layout : View, Maui.ILayout, IList<IView>, IBindableLayout, IPaddingElement, IVisualTreeElement, ISafeAreaView, IInputTransparentContainerElement
public abstract partial class Layout : View, Maui.ILayout, IList<IView>, IBindableLayout, IPaddingElement, IVisualTreeElement, ISafeAreaView, IInputTransparentContainerElement, ICompressedLayout
{
protected ILayoutManager _layoutManager;

Expand Down Expand Up @@ -79,6 +79,8 @@ public IView this[int index]
OnUpdate(index, value, old);
}
}

bool ICompressedLayout.IsHeadless => CompressedLayout.GetIsHeadless(this);

/// <summary>Bindable property for <see cref="IsClippedToBounds"/>.</summary>
public static readonly BindableProperty IsClippedToBoundsProperty =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ void SetupGestureManager()
if (GesturePlatformManager != null)
return;

#if PLATFORM
GesturePlatformManager = handler is IPlatformViewHandler ? new GesturePlatformManager(handler) : null;
#else
GesturePlatformManager = new GesturePlatformManager(handler);
#endif

_handler = handler;
_containerView = handler.ContainerView;
_platformView = handler.PlatformView;
Expand Down
33 changes: 33 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue24532.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue24532"
Title="Issue24532">

<Grid Padding="24" RowDefinitions="Auto,*" RowSpacing="8">
<Button x:Name="StartButton" Clicked="ButtonClicked" Text="Start" AutomationId="StartButton" />

<ScrollView Grid.Row="1">
<VerticalStackLayout x:Name="BindableContainer" BindableLayout.ItemsSource="{Binding Models}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid RowDefinitions="Auto,Auto,Auto" CompressedLayout.IsHeadless="True">
<Label Text="{Binding Header}" />
<Label Text="{Binding Content}" Grid.Row="1" />
<VerticalStackLayout Grid.Row="2" BindableLayout.ItemsSource="{Binding SubModels}" CompressedLayout.IsHeadless="True">
<BindableLayout.ItemTemplate>
<DataTemplate>
<VerticalStackLayout CompressedLayout.IsHeadless="True">
<Label Text="{Binding Header}" />
<Label Text="{Binding Content}" AutomationId="{Binding AutomationId}" />
</VerticalStackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</VerticalStackLayout>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</VerticalStackLayout>
</ScrollView>
</Grid>
</ContentPage>
144 changes: 144 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue24532.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

#if __IOS__ || MACCATALYST
using PlatformView = UIKit.UIView;
#elif __ANDROID__
using PlatformView = Android.Views.View;
#elif WINDOWS
using PlatformView = Microsoft.UI.Xaml.FrameworkElement;
#elif TIZEN
using PlatformView = Tizen.NUI.BaseComponents.View;
#elif (NETSTANDARD || !PLATFORM)
using PlatformView = System.Object;
#endif

namespace Maui.Controls.Sample.Issues;

[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 24532, "Rendering performance", PlatformAffected.All)]
public partial class Issue24532 : ContentPage
{
public List<Model> Models { get; set; }

public Issue24532()
{
Models = GenerateOneItem("Test0");
BindingContext = this;
InitializeComponent();
}

private async void ButtonClicked(object sender, EventArgs e)
{
var stopwatch = new Stopwatch();

var platformView = (PlatformView)BindableContainer.Handler!.PlatformView;

var capturedTimes = new List<long[]>();

var test1Models = GenerateItems("Test1", 70);
var test2Models = GenerateItems("Test2", 40);
var resetModel = GenerateOneItem("Test0");

for (var i = 0; i < 5; i++)
{
await Task.Delay(100);

Models = test1Models;
stopwatch.Restart();
OnPropertyChanged(nameof(Models));
await WaitForAutomationId(platformView, "Test1");
stopwatch.Stop();
var t1 = stopwatch.ElapsedMilliseconds;

await Task.Delay(100);

Models = test2Models;
stopwatch.Restart();
OnPropertyChanged(nameof(Models));
await WaitForAutomationId(platformView, "Test2");
stopwatch.Stop();
var t2 = stopwatch.ElapsedMilliseconds;

await Task.Delay(100);

stopwatch.Restart();
BindableContainer.Clear();
Models = resetModel;
OnPropertyChanged(nameof(Models));
await WaitForAutomationId(platformView, "Test0");
stopwatch.Stop();
var t3 = stopwatch.ElapsedMilliseconds;

capturedTimes.Add([t1, t2, t3]);
}

StartButton.Text =
$"{capturedTimes.Average(t => t[0])},{capturedTimes.Average(t => t[1])},{capturedTimes.Average(t => t[2])}";
}

async Task WaitForAutomationId(PlatformView platformView, string automationId)
{
var found = false;

while (!found)
{
await Task.Delay(20);
#if IOS
int length;
while ((length = platformView.Subviews.Length) > 0) { platformView = platformView.Subviews[length - 1]; }
found = platformView.AccessibilityIdentifier == automationId;
#elif ANDROID
int length;
while ((length = (platformView as Android.Views.ViewGroup)?.ChildCount ?? 0) > 0) { platformView = ((Android.Views.ViewGroup)platformView!).GetChildAt(length - 1); }
found = (platformView as Android.Widget.TextView)?.Text == automationId;
#else
found = true;
#endif
}
}

static List<Model> GenerateItems(string automationId, int count)
{
return
[
..Enumerable.Range(0, count).Select(i => new Model { Content = $"Content {i}", Header = $"Header {i}" }),
..GenerateOneItem(automationId)
];
}

static List<Model> GenerateOneItem(string automationId)
{
return
[
new Model
{
Content = automationId,
Header = automationId,
SubModels =
[
new SubModel { Content = automationId, Header = automationId, AutomationId = automationId }
]
}
];
}

public class Model : SubModel
{
public SubModel[] SubModels { get; set; } = Enumerable.Range(0, 10).Select(i => new SubModel
{
Content = $"SubContent {i}", Header = $"SubHeader {i}"
}).ToArray();
}

public class SubModel
{
public string Header { get; set; }
public string Content { get; set; }
public string AutomationId { get; set; }
}
}
26 changes: 26 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue99999.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue99999"
Title="Issue99999">

<Grid Padding="24" RowDefinitions="Auto,Auto" ColumnDefinitions="*,*,*,*,*" RowSpacing="16" ColumnSpacing="8">
<Entry x:Name="IndexEntry" Keyboard="Numeric" Placeholder="Index" />
<Button Text="Ins out" Clicked="OnInsOut" Grid.Row="0" Grid.Column="1" />
<Button Text="Rm out" Clicked="OnRmOut" Grid.Row="0" Grid.Column="2" />
<Button Text="Ins in" Clicked="OnInsIn" Grid.Row="0" Grid.Column="3" />
<Button Text="Rm in" Clicked="OnRmIn" Grid.Row="0" Grid.Column="4" />

<VerticalStackLayout x:Name="OuterLayout" Spacing="16" Grid.Row="1" Grid.ColumnSpan="5">
<Label Text="Text0" BackgroundColor="LightBlue" HeightRequest="20" />
<Label Text="Text1" BackgroundColor="LightBlue" HeightRequest="20" />
<VerticalStackLayout x:Name="InnerLayout" Padding="12" Spacing="8" CompressedLayout.IsHeadless="True">
<Label Text="Text2" BackgroundColor="LightGreen" HeightRequest="20" />
<Label Text="Text3" BackgroundColor="LightGreen" HeightRequest="20" />
<Label Text="Text4" BackgroundColor="LightGreen" HeightRequest="20" />
</VerticalStackLayout>
<Label Text="Text5" BackgroundColor="LightBlue" HeightRequest="20" />
<Label Text="Text6" BackgroundColor="LightBlue" HeightRequest="20" />
</VerticalStackLayout>
</Grid>
</ContentPage>
43 changes: 43 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue99999.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Maui.Controls.Sample.Issues;

[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 99999, "CompressedLayout is not working", PlatformAffected.All)]

public partial class Issue99999 : ContentPage
{
int _count = 6;

public Issue99999()
{
InitializeComponent();
}

private void OnInsOut(object sender, EventArgs e)
{
var index = int.Parse(IndexEntry.Text);
OuterLayout.Insert(index, new Label { Text = $"Text{++_count}" });
}

private void OnRmOut(object sender, EventArgs e)
{
var index = int.Parse(IndexEntry.Text);
OuterLayout.RemoveAt(index);
}

private void OnInsIn(object sender, EventArgs e)
{
var index = int.Parse(IndexEntry.Text);
InnerLayout.Insert(index, new Label { Text = $"Text{++_count}" });
}

private void OnRmIn(object sender, EventArgs e)
{
var index = int.Parse(IndexEntry.Text);
InnerLayout.RemoveAt(index);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#if ANDROID || IOS || MACCATALYST
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue24532 : _IssuesUITest
{
public Issue24532(TestDevice device) : base(device) { }

public override string Issue => "Rendering performance";

[Test]
[Category(UITestCategories.Performance)]
public async Task RenderingPerformance()
{
var button = App.WaitForElement("StartButton");
App.Tap("StartButton");

string? text;
while (true)
{
text = button.GetText();
if (string.IsNullOrEmpty(text) || text == "Start")
{
await Task.Delay(1000);
continue;
}

break;
}

var times = text.Split(',');
Console.WriteLine(@$"First render: {times[0]}ms");
Console.WriteLine(@$"Binding context changed: {times[1]}ms");
Console.WriteLine(@$"Clear: {times[2]}ms");
}
}
}
#endif
17 changes: 17 additions & 0 deletions src/Core/src/Core/ICompressedLayout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Microsoft.Maui
{

/// <summary>
/// Provides information about whether the ILayout is compressed.
/// </summary>
/// <remarks>
/// A compressed layout is not included in the platform tree.
/// </remarks>
internal interface ICompressedLayout
{
/// <summary>
/// Specifies whether the ILayout is compressed.
/// </summary>
bool IsHeadless { get; }
}
}
Loading