From bc0e0aac8e0a8f988507595d9c6a79a170c9d2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Fri, 29 Sep 2023 08:56:29 +0200 Subject: [PATCH] Implement gestures on Windows Spans --- src/Controls/src/Core/Label/Label.Windows.cs | 97 ++++++++++++++++++- .../Extensions/FormattedStringExtensions.cs | 52 ++++++++++ .../net-windows/PublicAPI.Unshipped.txt | 1 + .../tests/UITests/Tests/Issues/Issue3525.cs | 5 - .../tests/UITests/Tests/LabelUITests.cs | 5 - 5 files changed, 147 insertions(+), 13 deletions(-) diff --git a/src/Controls/src/Core/Label/Label.Windows.cs b/src/Controls/src/Core/Label/Label.Windows.cs index ed89c6aa56b5..3629240e386c 100644 --- a/src/Controls/src/Core/Label/Label.Windows.cs +++ b/src/Controls/src/Core/Label/Label.Windows.cs @@ -1,12 +1,44 @@ #nullable disable +using System.Collections.Generic; using Microsoft.Maui.Controls.Platform; +using Microsoft.UI.Xaml.Controls; namespace Microsoft.Maui.Controls { public partial class Label { - public static void MapDetectReadingOrderFromContent(LabelHandler handler, Label label) => MapDetectReadingOrderFromContent((ILabelHandler)handler, label); - public static void MapText(LabelHandler handler, Label label) => MapText((ILabelHandler)handler, label); + TextBlock _textBlock; + + private protected override void OnHandlerChangedCore() + { + base.OnHandlerChangedCore(); + + if (Handler is not null) + { + if (Handler is LabelHandler labelHandler && labelHandler.PlatformView is TextBlock textBlock) + { + _textBlock = textBlock; + _textBlock.SizeChanged += OnSizeChanged; + } + } + else + { + if (_textBlock is not null) + { + _textBlock.SizeChanged -= OnSizeChanged; + _textBlock = null; + } + } + } + + public static void MapDetectReadingOrderFromContent(LabelHandler handler, Label label) => + MapDetectReadingOrderFromContent((ILabelHandler)handler, label); + + public static void MapTextType(LabelHandler handler, Label label) => + MapTextType((ILabelHandler)handler, label); + + public static void MapText(LabelHandler handler, Label label) => + MapText((ILabelHandler)handler, label); public static void MapDetectReadingOrderFromContent(ILabelHandler handler, Label label) => Platform.TextBlockExtensions.UpdateDetectReadingOrderFromContent(handler.PlatformView, label); @@ -18,6 +50,65 @@ public static void MapLineBreakMode(ILabelHandler handler, Label label) => handler.PlatformView?.UpdateLineBreakMode(label); public static void MapMaxLines(ILabelHandler handler, Label label) => - handler.PlatformView?.UpdateMaxLines(label); + handler.PlatformView?.UpdateMaxLines(label); + + void OnSizeChanged(object sender, UI.Xaml.SizeChangedEventArgs e) + { + RecalculateSpanPositions(); + } + + void RecalculateSpanPositions() + { + if (Handler is LabelHandler labelHandler) + { + var platformView = labelHandler.PlatformView; + var virtualView = labelHandler.VirtualView as Label; + + if (platformView is null || virtualView is null) + return; + + IList inlineHeights = GetInlineHeights(); + platformView.RecalculateSpanPositions(virtualView, inlineHeights); + } + } + + IList GetInlineHeights() + { + IList inlineHeights = new List(); + + if (Handler is LabelHandler labelHandler) + { + var platformView = labelHandler.PlatformView; + var virtualView = labelHandler.VirtualView as Label; + + if (virtualView is not null) + { + FormattedString formatted = virtualView.FormattedText; + + if (formatted is not null) + { + var fontManager = virtualView.RequireFontManager(); + platformView.Inlines.Clear(); + + // Have to implement a measure here, otherwise inline.ContentStart and ContentEnd will be null, when used in RecalculatePositions + platformView.Measure(new global::Windows.Foundation.Size(double.MaxValue, double.MaxValue)); + + var heights = new List(); + for (var i = 0; i < formatted.Spans.Count; i++) + { + var span = formatted.Spans[i]; + + var run = span.ToRunAndColorsTuple(fontManager).Item1; + heights.Add(platformView.FindDefaultLineHeight(run)); + platformView.Inlines.Add(run); + } + + inlineHeights = heights; + } + } + } + + return inlineHeights; + } } } \ No newline at end of file diff --git a/src/Controls/src/Core/Platform/Windows/Extensions/FormattedStringExtensions.cs b/src/Controls/src/Core/Platform/Windows/Extensions/FormattedStringExtensions.cs index 5b29a13f1906..94aad0844cb6 100644 --- a/src/Controls/src/Core/Platform/Windows/Extensions/FormattedStringExtensions.cs +++ b/src/Controls/src/Core/Platform/Windows/Extensions/FormattedStringExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Graphics; using Microsoft.UI.Xaml.Controls; @@ -127,5 +128,56 @@ public static Tuple ToRunAndColorsTuple( return Tuple.Create(run, span.TextColor, span.BackgroundColor); } + + public static void RecalculateSpanPositions(this TextBlock control, Label element, IList inlineHeights) + { + if (element?.FormattedText?.Spans == null + || element.FormattedText.Spans.Count == 0) + return; + + var labelWidth = control.ActualWidth; + + if (labelWidth <= 0 || control.Height <= 0) + return; + + for (int i = 0; i < element.FormattedText.Spans.Count; i++) + { + var span = element.FormattedText.Spans[i]; + + var inline = control.Inlines.ElementAt(i); + + // TODO: Alternatives?. GetCharacterRect always return an empty Rect + var startRect = inline.ContentStart.GetCharacterRect(LogicalDirection.Forward); + var endRect = inline.ContentEnd.GetCharacterRect(LogicalDirection.Forward); + + var defaultLineHeight = inlineHeights[i]; + var yaxis = startRect.Top; + + var lineHeights = new List(); + + while (yaxis < endRect.Bottom) + { + double lineHeight; + + if (yaxis == startRect.Top) // First Line + { + lineHeight = startRect.Bottom - startRect.Top; + } + else if (yaxis != endRect.Top) // Middle Line(s) + { + lineHeight = defaultLineHeight; + } + else // Bottom Line + { + lineHeight = endRect.Bottom - endRect.Top; + } + + lineHeights.Add(lineHeight); + yaxis += lineHeight; + } + + ((ISpatialElement)span).Region = Region.FromLines(lineHeights.ToArray(), labelWidth, startRect.X, endRect.X + endRect.Width, startRect.Top).Inflate(10); + } + } } } \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt index fa1ca57b7941..91739065b68c 100644 --- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -66,6 +66,7 @@ Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommand.get -> S Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommand.set -> void Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommandParameter.get -> object! Microsoft.Maui.Controls.PointerGestureRecognizer.PointerReleasedCommandParameter.set -> void +static Microsoft.Maui.Controls.Platform.FormattedStringExtensions.RecalculateSpanPositions(this Microsoft.UI.Xaml.Controls.TextBlock! control, Microsoft.Maui.Controls.Label! element, System.Collections.Generic.IList! inlineHeights) -> void static readonly Microsoft.Maui.Controls.KeyboardAccelerator.KeyProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.KeyboardAccelerator.ModifiersProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.DragGestureRecognizer.CanDragProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue3525.cs b/src/Controls/tests/UITests/Tests/Issues/Issue3525.cs index 0430a77dfdbd..0a04adb6fa83 100644 --- a/src/Controls/tests/UITests/Tests/Issues/Issue3525.cs +++ b/src/Controls/tests/UITests/Tests/Issues/Issue3525.cs @@ -24,11 +24,6 @@ public void SpanRegionClicking() Assert.Ignore("Click (x, y) pointer type mouse is not implemented."); } - if (Device == TestDevice.Windows) - { - Assert.Ignore("This test is failing on Windows because the feature is not yet implemented: https://github.com/dotnet/maui/pull/17731"); - } - var label = App.WaitForElement(kLabelTestAutomationId); var location = label.GetRect(); diff --git a/src/Controls/tests/UITests/Tests/LabelUITests.cs b/src/Controls/tests/UITests/Tests/LabelUITests.cs index 5c401c041d7a..3c00cb850b3d 100644 --- a/src/Controls/tests/UITests/Tests/LabelUITests.cs +++ b/src/Controls/tests/UITests/Tests/LabelUITests.cs @@ -31,11 +31,6 @@ public void SpanTapped() Assert.Ignore("Click (x, y) pointer type mouse is not implemented."); } - if (Device == TestDevice.Windows) - { - Assert.Ignore("This test is failing on Windows because the feature is not yet implemented: https://github.com/dotnet/maui/issues/4734"); - } - var remote = new EventViewContainerRemote(UITestContext, Test.FormattedString.SpanTapped); remote.GoTo();