Skip to content

Commit

Permalink
Implement gestures on Windows Spans (#17731)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsuarezruiz committed Feb 21, 2024
1 parent 8d517af commit 813c7c9
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 37 deletions.
42 changes: 40 additions & 2 deletions src/Controls/src/Core/Label/Label.Windows.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
#nullable disable
using System.Collections.Generic;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Graphics;
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);
public static void MapDetectReadingOrderFromContent(LabelHandler handler, Label label) =>
MapDetectReadingOrderFromContent((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);
Expand All @@ -19,5 +25,37 @@ public partial class Label

public static void MapMaxLines(ILabelHandler handler, Label label) =>
handler.PlatformView?.UpdateMaxLines(label);

protected override Size ArrangeOverride(Rect bounds)
{
var size = base.ArrangeOverride(bounds);

RecalculateSpanPositions();

return size;
}

void RecalculateSpanPositions()
{
if (Handler is not ILabelHandler labelHandler)
{
return;
}

var platformView = labelHandler.PlatformView;
var virtualView = labelHandler.VirtualView as Label;
if (platformView is null || virtualView is null)
{
return;
}

var formatted = virtualView.FormattedText;
if (formatted is null)
{
return;
}

platformView.RecalculateSpanPositions(formatted);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Graphics;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;

namespace Microsoft.Maui.Controls.Platform
{
public static class FormattedStringExtensions
{
static double GetMeasuredLineHeight(DependencyObject obj) =>
(double)obj.GetValue(MeasuredLineHeightProperty);

static void SetMeasuredLineHeight(DependencyObject obj, double value) =>
obj.SetValue(MeasuredLineHeightProperty, value);

static readonly DependencyProperty MeasuredLineHeightProperty = DependencyProperty.RegisterAttached(
"MeasuredLineHeight", typeof(double), typeof(FormattedStringExtensions), new PropertyMetadata(0));

public static void UpdateInlines(this TextBlock textBlock, Label label)
=> UpdateInlines(
textBlock,
Expand All @@ -31,44 +42,47 @@ public static void UpdateInlines(this TextBlock textBlock, Label label)
TextTransform defaultTextTransform = TextTransform.Default)
{
textBlock.Inlines.Clear();

// Have to implement a measure here, otherwise inline.ContentStart and ContentEnd will be null, when used in RecalculatePositions
textBlock.Measure(new global::Windows.Foundation.Size(double.MaxValue, double.MaxValue));

var runAndColorTuples = formattedString.ToRunAndColorsTuples(fontManager, defaultLineHeight, defaultHorizontalAlignment, defaultFont, defaultColor, defaultTextTransform);
var runs = formattedString.ToRunAndColorsTuples(
fontManager,
defaultLineHeight,
defaultHorizontalAlignment,
defaultFont,
defaultColor,
defaultTextTransform).ToArray();

var heights = new List<double>();
int currentTextIndex = 0;
foreach (var runAndColorTuple in runAndColorTuples)
var lineHeights = new List<double>(runs.Length);
foreach (var (run, _, _) in runs)
{
Run run = runAndColorTuple.Item1;
Color textColor = runAndColorTuple.Item2;
Color background = runAndColorTuple.Item3;
heights.Add(textBlock.FindDefaultLineHeight(run));
textBlock.Inlines.Add(run);
int length = run.Text.Length;
lineHeights.Add(textBlock.FindDefaultLineHeight(run));
}

if (background != null || textColor != null)
{
TextHighlighter textHighlighter = new TextHighlighter { Ranges = { new TextRange(currentTextIndex, length) } };
var currentTextIndex = 0;
for (var i = 0; i < runs.Length; i++)
{
var (run, textColor, background) = runs[i];
var runTextLength = run.Text.Length;

if (background != null)
{
textHighlighter.Background = background.ToPlatform();
}
else
{
textHighlighter.Background = Colors.Transparent.ToPlatform();
}
SetMeasuredLineHeight(run, lineHeights[i]);

if (textColor != null)
textBlock.Inlines.Add(run);

if (background is not null || textColor is not null)
{
var textHighlighter = new TextHighlighter
{
textHighlighter.Foreground = textColor.ToPlatform();
}
Ranges = { new TextRange(currentTextIndex, runTextLength) },
Background = (background ?? Colors.Transparent).ToPlatform(),
Foreground = textColor?.ToPlatform(),
};

textBlock.TextHighlighters.Add(textHighlighter);
}

currentTextIndex += length;
currentTextIndex += runTextLength;
}
}

Expand Down Expand Up @@ -127,5 +141,61 @@ public static void UpdateInlines(this TextBlock textBlock, Label label)

return Tuple.Create(run, span.TextColor, span.BackgroundColor);
}

internal static void RecalculateSpanPositions(this TextBlock control, FormattedString formatted)
{
var spans = formatted.Spans;
var labelWidth = control.ActualWidth;
if (spans is null || spans.Count == 0 || labelWidth <= 0 || control.Height <= 0)
{
return;
}

for (int i = 0; i < spans.Count; i++)
{
var span = spans[i];

var inline = control.Inlines.ElementAt(i);

var startRect = inline.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var endRect = inline.ContentEnd.GetCharacterRect(LogicalDirection.Forward);

var defaultLineHeight = GetMeasuredLineHeight(inline);
var yaxis = startRect.Top;

var lineHeights = new List<double>();

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;
}

var region = Region.FromLines(
lineHeights.ToArray(),
labelWidth,
startRect.X,
endRect.X + endRect.Width,
startRect.Top);
((ISpatialElement)span).Region = region.Inflate(10);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Microsoft.Maui.Controls.InputView.FontSize.get -> double
Microsoft.Maui.Controls.InputView.FontSize.set -> void
Microsoft.Maui.Controls.InputView.SelectionLength.get -> int
Microsoft.Maui.Controls.InputView.SelectionLength.set -> void
override Microsoft.Maui.Controls.Label.ArrangeOverride(Microsoft.Maui.Graphics.Rect bounds) -> Microsoft.Maui.Graphics.Size
static Microsoft.Maui.Controls.Handlers.ShellItemHandler.MapTitle(Microsoft.Maui.Controls.Handlers.ShellItemHandler! handler, Microsoft.Maui.Controls.ShellItem! item) -> void
static Microsoft.Maui.Controls.Handlers.ShellSectionHandler.MapTitle(Microsoft.Maui.Controls.Handlers.ShellSectionHandler! handler, Microsoft.Maui.Controls.ShellSection! item) -> void
Microsoft.Maui.Controls.Handlers.Compatibility.ViewRenderer<TElement, TNativeView>.ViewRenderer(Microsoft.Maui.IPropertyMapper! mapper, Microsoft.Maui.CommandMapper? commandMapper = null) -> void
Expand Down
5 changes: 0 additions & 5 deletions src/Controls/tests/UITests/Tests/Issues/Issue3525.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
5 changes: 0 additions & 5 deletions src/Controls/tests/UITests/Tests/LabelUITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down

0 comments on commit 813c7c9

Please sign in to comment.