diff --git a/src/Controls/src/Core/Platform/Windows/Extensions/ButtonExtensions.cs b/src/Controls/src/Core/Platform/Windows/Extensions/ButtonExtensions.cs index b08f6afe8c73..8950d1617aa4 100644 --- a/src/Controls/src/Core/Platform/Windows/Extensions/ButtonExtensions.cs +++ b/src/Controls/src/Core/Platform/Windows/Extensions/ButtonExtensions.cs @@ -32,6 +32,8 @@ public static void UpdateContentLayout(this UI.Xaml.Controls.Button mauiButton, content.LayoutImageLeft(spacing); break; } + + content.InvalidateMeasure(); } public static void UpdateLineBreakMode(this Microsoft.UI.Xaml.Controls.Button platformButton, Button button) diff --git a/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.cs b/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.cs index 8d75c38f5ab2..c871ae1b78fa 100644 --- a/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.cs @@ -326,7 +326,47 @@ await AttachAndRun(grid, (handler) => }); } - /* Commented out of for now due to inconsistent platform behavior + [Fact] + [Category(TestCategory.Button, TestCategory.FlexLayout)] + public async Task ButtonWithImageInFlexLayoutInGridDoesNotCycle() + { + EnsureHandlerCreated((builder) => + { + builder.ConfigureMauiHandlers(handler => + { + handler.AddHandler(typeof(Button), typeof(ButtonHandler)); + handler.AddHandler(typeof(Layout), typeof(LayoutHandler)); + }); + }); + + await ButtonWithImageInFlexLayoutInGridDoesNotCycleCore(); + // Cycle does not occur on first run + await ButtonWithImageInFlexLayoutInGridDoesNotCycleCore(); + } + + async Task ButtonWithImageInFlexLayoutInGridDoesNotCycleCore() + { + var grid = new Grid() { MaximumWidthRequest = 150 }; + grid.AddRowDefinition(new RowDefinition(GridLength.Auto)); + + var flexLayout = new FlexLayout() { Wrap = Layouts.FlexWrap.Wrap }; + grid.Add(flexLayout); + + for (int i = 0; i < 2; i++) + { + var button = new Button { ImageSource = "black.png" }; + flexLayout.Add(button); + } + + await InvokeOnMainThreadAsync(async () => + { + // If this can be attached to the hierarchy and make it through a layout + // without crashing, then we're good. + await AttachAndRun(grid, (handler) => { }); + }); + } + + /* Commented out of for now due to inconsistent platform behavior [Fact("Ensures grid rows renders the correct size - Issue 15330")] public async Task Issue15330() { @@ -401,7 +441,7 @@ public async Task Issue15330() Assert.Equal(bitmap.Height / 3 * 2, cyanBlob.MinRow, 2d); }*/ - [Fact] + [Fact] public async Task DependentLayoutBindingsResolve() { EnsureHandlerCreated((builder) => diff --git a/src/Controls/tests/UITests/snapshots/windows/Issue21513Test.png b/src/Controls/tests/UITests/snapshots/windows/Issue21513Test.png index 32f6179c5624..4a7abdf9afd1 100644 Binary files a/src/Controls/tests/UITests/snapshots/windows/Issue21513Test.png and b/src/Controls/tests/UITests/snapshots/windows/Issue21513Test.png differ diff --git a/src/Core/src/Platform/Windows/ButtonExtensions.cs b/src/Core/src/Platform/Windows/ButtonExtensions.cs index 819e2b01d3a2..c4e037137569 100644 --- a/src/Core/src/Platform/Windows/ButtonExtensions.cs +++ b/src/Core/src/Platform/Windows/ButtonExtensions.cs @@ -160,9 +160,6 @@ public static void UpdateImageSource(this Button platformButton, WImageSource? n { if (platformButton.GetContent() is WImage nativeImage) { - // Stretch to fill - nativeImage.Stretch = UI.Xaml.Media.Stretch.Uniform; - // If we're a CanvasImageSource (font image source), we need to explicitly set the image height // to the desired size of the font, otherwise it will be stretched to the available space if (nativeImageSource is CanvasImageSource canvas) diff --git a/src/Core/src/Platform/Windows/MauiButton.cs b/src/Core/src/Platform/Windows/MauiButton.cs index f1fdffcfb388..c905ecec3656 100644 --- a/src/Core/src/Platform/Windows/MauiButton.cs +++ b/src/Core/src/Platform/Windows/MauiButton.cs @@ -1,8 +1,10 @@ -using Microsoft.UI.Xaml; +using System; +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Automation.Peers; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; -using Windows.Security.Credentials.UI; +using WRect = global::Windows.Foundation.Rect; +using WSize = global::Windows.Foundation.Size; using WThickness = Microsoft.UI.Xaml.Thickness; namespace Microsoft.Maui.Platform @@ -23,28 +25,26 @@ protected override AutomationPeer OnCreateAutomationPeer() } } - internal class DefaultMauiButtonContent : Grid + internal class DefaultMauiButtonContent : MauiPanel { readonly Image _image; readonly TextBlock _textBlock; + double _spacing; + bool _isHorizontalLayout; + bool _imageOnBottomOrRight; + public DefaultMauiButtonContent() { - RowDefinitions.Add(new RowDefinition { Height = UI.Xaml.GridLength.Auto }); - RowDefinitions.Add(new RowDefinition { Height = UI.Xaml.GridLength.Auto }); - - ColumnDefinitions.Add(new ColumnDefinition { Width = UI.Xaml.GridLength.Auto }); - ColumnDefinitions.Add(new ColumnDefinition { Width = UI.Xaml.GridLength.Auto }); - HorizontalAlignment = HorizontalAlignment.Center; VerticalAlignment = VerticalAlignment.Center; Margin = new WThickness(0); _image = new Image { - VerticalAlignment = VerticalAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Center, - Stretch = Stretch.None, + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch, + Stretch = Stretch.Uniform, Margin = new WThickness(0), Visibility = UI.Xaml.Visibility.Collapsed, }; @@ -63,97 +63,219 @@ public DefaultMauiButtonContent() LayoutImageLeft(0); } - public void LayoutImageLeft(double spacing) + protected override WSize MeasureOverride(WSize availableSize) { - SetupHorizontalLayout(spacing); + double measuredHeight = 0; + double measuredWidth = 0; + double spacing = 0.0; + + // Always measure the image first, and use the remaining space for the text + if (_image.Source != null && + _image.Visibility == UI.Xaml.Visibility.Visible) + { + _image.Measure(availableSize); + measuredWidth = _image.DesiredSize.Width; + measuredHeight = _image.DesiredSize.Height; + } - Grid.SetColumn(_image, 0); - Grid.SetColumn(_textBlock, 1); + if (!string.IsNullOrEmpty(_textBlock.Text) && + _textBlock.Visibility == UI.Xaml.Visibility.Visible) + { + // Only add spacing if we have valid text + spacing = _spacing; + + if (_isHorizontalLayout) + { + var availableWidth = Math.Max(0, availableSize.Width - measuredWidth - spacing); + _textBlock.Measure(new WSize(availableWidth, availableSize.Height)); + + measuredWidth += _textBlock.DesiredSize.Width; + measuredHeight = Math.Max(measuredHeight, _textBlock.DesiredSize.Height); + } + else // Vertical + { + var availableHeight = Math.Max(0, availableSize.Height - measuredHeight - spacing); + _textBlock.Measure(new WSize(availableSize.Width, availableHeight)); + + measuredWidth = Math.Max(measuredWidth, _textBlock.DesiredSize.Width); + measuredHeight += _textBlock.DesiredSize.Height; + } + } - ColumnDefinitions[0].Width = new UI.Xaml.GridLength(1, UI.Xaml.GridUnitType.Auto); - ColumnDefinitions[1].Width = new UI.Xaml.GridLength(1, UI.Xaml.GridUnitType.Star); + // Only add spacing if we have room + if (_isHorizontalLayout) + { + measuredWidth = Math.Min(measuredWidth + spacing, availableSize.Width); + } + else // Vertical + { + measuredHeight = Math.Min(measuredHeight + spacing, availableSize.Height); + } + + if (!double.IsInfinity(availableSize.Width) && + HorizontalAlignment == HorizontalAlignment.Stretch) + { + measuredWidth = Math.Max(measuredWidth, availableSize.Width); + } + + if (!double.IsInfinity(availableSize.Height) && + VerticalAlignment == VerticalAlignment.Stretch) + { + measuredHeight = Math.Max(measuredHeight, availableSize.Height); + } + return new WSize(measuredWidth, measuredHeight); } - public void LayoutImageRight(double spacing) + protected override WSize ArrangeOverride(WSize finalSize) { - SetupHorizontalLayout(spacing); - - Grid.SetColumn(_image, 1); - Grid.SetColumn(_textBlock, 0); + if (_imageOnBottomOrRight) + { + ArrangeBottomAndRight(finalSize); + } + else + { + ArrangeLeftAndTop(finalSize); + } - ColumnDefinitions[0].Width = new UI.Xaml.GridLength(1, UI.Xaml.GridUnitType.Star); - ColumnDefinitions[1].Width = new UI.Xaml.GridLength(1, UI.Xaml.GridUnitType.Auto); + return new WSize(finalSize.Width, finalSize.Height); } - public void LayoutImageTop(double spacing) + private void ArrangeLeftAndTop(WSize finalSize) { - SetupVerticalLayout(spacing); + var x = 0.0; + var y = 0.0; + + var spacing = _spacing; + if (string.IsNullOrEmpty(_textBlock.Text)) + { + spacing = 0; + } - Grid.SetRow(_image, 0); - Grid.SetRow(_textBlock, 1); + if (_image.Visibility == UI.Xaml.Visibility.Visible) + { + var (newX, newY) = ArrangePrimaryElement(_image, x, y, spacing, finalSize); + x = newX; + y = newY; + } - RowDefinitions[0].Height = new UI.Xaml.GridLength(0, UI.Xaml.GridUnitType.Auto); - RowDefinitions[1].Height = new UI.Xaml.GridLength(1, UI.Xaml.GridUnitType.Star); + if (!string.IsNullOrEmpty(_textBlock.Text) && + _textBlock.Visibility == UI.Xaml.Visibility.Visible) + { + ArrangeSecondaryElement(_textBlock, x, y, finalSize); + } } - public void LayoutImageBottom(double spacing) + private void ArrangeBottomAndRight(WSize finalSize) { - SetupVerticalLayout(spacing); + var x = 0.0; + var y = 0.0; - Grid.SetRow(_image, 1); - Grid.SetRow(_textBlock, 0); + if (!string.IsNullOrEmpty(_textBlock.Text) && + _textBlock.Visibility == UI.Xaml.Visibility.Visible) + { + var (newX, newY) = ArrangePrimaryElement(_textBlock, x, y, _spacing, finalSize); + x = newX; + y = newY; + } - RowDefinitions[0].Height = new UI.Xaml.GridLength(1, UI.Xaml.GridUnitType.Star); - RowDefinitions[1].Height = new UI.Xaml.GridLength(0, UI.Xaml.GridUnitType.Auto); + if (_image.Visibility == UI.Xaml.Visibility.Visible) + { + ArrangeSecondaryElement(_image, x, y, finalSize); + } } - double AdjustSpacing(double spacing) + /// + /// Arrange and center primary element (image or text) and return the new x or y position + /// based on the element's size and if we're horizontal or vertical + /// + /// + /// + /// + /// + /// + /// + private (double newX, double newY) ArrangePrimaryElement(FrameworkElement element, double x, double y, double spacing, WSize finalSize) { - if (_image.Visibility == UI.Xaml.Visibility.Collapsed - || _textBlock.Visibility == UI.Xaml.Visibility.Collapsed) + if (_isHorizontalLayout) { - return 0; + var centeredY = Math.Max(0, (finalSize.Height / 2.0) - (element.DesiredSize.Height / 2.0)); + + element.Arrange(new WRect(0, centeredY, + element.DesiredSize.Width, element.DesiredSize.Height)); + + return (x + element.DesiredSize.Width + spacing, 0); } + else // Vertical + { + var centeredX = Math.Max(0, (finalSize.Width / 2.0) - (element.DesiredSize.Width / 2.0)); - return spacing; + element.Arrange(new WRect(centeredX, 0, + element.DesiredSize.Width, element.DesiredSize.Height)); + + return (0, y + element.DesiredSize.Height + spacing); + } } - void SetupHorizontalLayout(double spacing) + private void ArrangeSecondaryElement(FrameworkElement element, double x, double y, WSize finalSize) { - RowSpacing = 0; - ColumnSpacing = AdjustSpacing(spacing); + if (_isHorizontalLayout) + { + y = Math.Max(0, (finalSize.Height / 2.0) - (element.DesiredSize.Height / 2.0)); + } + else + { + x = Math.Max(0, (finalSize.Width / 2.0) - (element.DesiredSize.Width / 2.0)); + } - RowDefinitions[0].Height = new UI.Xaml.GridLength(1, UI.Xaml.GridUnitType.Star); - RowDefinitions[1].Height = new UI.Xaml.GridLength(1, UI.Xaml.GridUnitType.Star); + element.Arrange(new WRect(x, y, + element.DesiredSize.Width, element.DesiredSize.Height)); + } - Grid.SetRow(_image, 0); - Grid.SetRowSpan(_image, 2); - Grid.SetColumnSpan(_image, 1); + public void LayoutImageLeft(double spacing) + { + _imageOnBottomOrRight = false; + SetupHorizontalLayout(spacing); + } - Grid.SetRow(_textBlock, 0); - Grid.SetRowSpan(_textBlock, 2); - Grid.SetColumnSpan(_textBlock, 1); + public void LayoutImageRight(double spacing) + { + _imageOnBottomOrRight = true; + SetupHorizontalLayout(spacing); + } + public void LayoutImageTop(double spacing) + { + _imageOnBottomOrRight = false; + SetupVerticalLayout(spacing); } - void SetupVerticalLayout(double spacing) + public void LayoutImageBottom(double spacing) { - ColumnSpacing = 0; - RowSpacing = AdjustSpacing(spacing); + _imageOnBottomOrRight = true; + SetupVerticalLayout(spacing); + } - RowDefinitions[0].Height = UI.Xaml.GridLength.Auto; - RowDefinitions[1].Height = UI.Xaml.GridLength.Auto; + double AdjustSpacing(double spacing) + { + if (_image.Visibility == UI.Xaml.Visibility.Collapsed + || _textBlock.Visibility == UI.Xaml.Visibility.Collapsed) + { + return 0; + } - ColumnDefinitions[0].Width = new UI.Xaml.GridLength(1, UI.Xaml.GridUnitType.Star); - ColumnDefinitions[1].Width = new UI.Xaml.GridLength(1, UI.Xaml.GridUnitType.Star); + return spacing; + } - Grid.SetRowSpan(_image, 1); - Grid.SetColumn(_image, 0); - Grid.SetColumnSpan(_image, 2); + void SetupHorizontalLayout(double spacing) + { + _isHorizontalLayout = true; + _spacing = AdjustSpacing(spacing); + } - Grid.SetRowSpan(_textBlock, 1); - Grid.SetColumn(_textBlock, 0); - Grid.SetColumnSpan(_textBlock, 2); + void SetupVerticalLayout(double spacing) + { + _isHorizontalLayout = false; + _spacing = AdjustSpacing(spacing); } } } \ No newline at end of file