Skip to content

Commit

Permalink
[Android] Fix gestures in Label Spans (#14410)
Browse files Browse the repository at this point in the history
* Fix gestures in Label Spans on Android

* Fix merge issue

* Refactoring

* Added some more tests to the sample

* Make FromRectangles Region method public

---------

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
  • Loading branch information
jsuarezruiz and mattleibow authored May 2, 2023
1 parent 85625dd commit 2c301d7
Show file tree
Hide file tree
Showing 14 changed files with 361 additions and 249 deletions.
458 changes: 255 additions & 203 deletions src/Controls/samples/Controls.Sample/Pages/Controls/LabelPage.xaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
using Maui.Controls.Sample.ViewModels;
using System;
using Maui.Controls.Sample.ViewModels;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;

namespace Maui.Controls.Sample.Pages
{
public partial class LabelPage
{
readonly Color[] _colors = new Color[] { Colors.Red, Colors.Blue, Colors.Green, Colors.Yellow, Colors.Brown, Colors.Purple, Colors.Orange, Colors.Gray };
readonly Random _rand = new Random();

public LabelPage()
{
InitializeComponent();

BindingContext = new LabelViewModel();
}

void ClickGestureRecognizer_Clicked(object sender, System.EventArgs e)
void ClickGestureRecognizer_Clicked(object sender, EventArgs e)
{
var rnd = new System.Random();

if (sender is Span span)
span.TextColor = Color.FromRgb((byte)rnd.Next(0, 254), (byte)rnd.Next(0, 254), (byte)rnd.Next(0, 254));
SetRandomBackgroundColor(GestureSpan);
}

void ChangeFormattedString_Clicked(object sender, System.EventArgs e)
void ChangeFormattedString_Clicked(object sender, EventArgs e)
{
labelFormattedString.FormattedText = new FormattedString
{
Expand All @@ -30,9 +31,43 @@ void ChangeFormattedString_Clicked(object sender, System.EventArgs e)
new Span
{
Text = "Testing"
},
new Span
{
Text = "Bold",
FontAttributes = FontAttributes.Bold
}
}
};
}

void OnLink1Tapped(object sender, EventArgs e)
{
SetRandomBackgroundColor(Link1);
}

void OnLink2Tapped(object sender, EventArgs e)
{
SetRandomBackgroundColor(Link2);
}

void OnLink3Tapped(object sender, EventArgs e)
{
SetRandomBackgroundColor(Link3);
}

void SetRandomBackgroundColor(Span span)
{
var oldColor = span.BackgroundColor;

Color newColor = _colors[_rand.Next(_colors.Length)];

while (oldColor == newColor)
{
newColor = _colors[_rand.Next(_colors.Length)];
}

span.BackgroundColor = newColor;
}
}
}
2 changes: 1 addition & 1 deletion src/Controls/src/Core/HandlerImpl/Label/Label.Android.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#nullable disable
#nullable disable
using System;
using Android.Text;
using Microsoft.Maui.Controls.Platform;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,25 +133,26 @@ internal static SpannableString ToSpannableStringNewWay(

public static void RecalculateSpanPositions(this TextView textView, Label element, SpannableString spannableString, SizeRequest finalSize)
{
if (element?.FormattedText?.Spans == null || element.FormattedText.Spans.Count == 0)
if (element?.FormattedText?.Spans is null || element.FormattedText.Spans.Count == 0)
return;

var labelWidth = finalSize.Request.Width;

if (labelWidth <= 0 || finalSize.Request.Height <= 0)
return;

if (spannableString == null || spannableString.IsDisposed())
return;

var layout = textView.Layout;

if (layout == null)
return;

int next = 0;
int count = 0;
IList<int> totalLineHeights = new List<int>();

var padding = element.Padding;
var padLeft = (int)textView.Context.ToPixels(padding.Left);
var padTop = (int)textView.Context.ToPixels(padding.Top);

#pragma warning disable CA1416
var strlen = spannableString.Length();
Expand All @@ -169,7 +170,7 @@ public static void RecalculateSpanPositions(this TextView textView, Label elemen
continue;

// Find the next span
next = spannableString.NextSpanTransition(i, strlen, type);
next = spannableString.NextSpanTransition(i, spannableString.Length(), type);

// Get all spans in the range - Android can have overlapping spans
var spans = spannableString.GetSpans(i, next, type);
Expand All @@ -180,42 +181,47 @@ public static void RecalculateSpanPositions(this TextView textView, Label elemen
var startSpan = spans[0];
var endSpan = spans[spans.Length - 1];

var startSpanOffset = spannableString.GetSpanStart(startSpan);
var endSpanOffset = spannableString.GetSpanEnd(endSpan);

var thisLine = layout.GetLineForOffset(endSpanOffset);
var lineStart = layout.GetLineStart(thisLine);
var lineEnd = layout.GetLineEnd(thisLine);

// If this is true, endSpanOffset has the value for another line that belong to the next span and not it self.
// So it should be rearranged to value not pass the lineEnd.
if (endSpanOffset > (lineEnd - lineStart))
endSpanOffset = lineEnd;
var spanStartOffset = spannableString.GetSpanStart(startSpan);
var spanEndOffset = spannableString.GetSpanEnd(endSpan);

var startX = layout.GetPrimaryHorizontal(startSpanOffset);
var endX = layout.GetPrimaryHorizontal(endSpanOffset);
var spanStartLine = layout.GetLineForOffset(spanStartOffset);
var spanEndLine = layout.GetLineForOffset(spanEndOffset);

var startLine = layout.GetLineForOffset(startSpanOffset);
var endLine = layout.GetLineForOffset(endSpanOffset);

double[] lineHeights = new double[endLine - startLine + 1];

// Calculate all the different line heights
for (var lineCount = startLine; lineCount <= endLine; lineCount++)
// Go through all lines that are affected by the span and calculate a rectangle for each
List<Graphics.Rect> spanRectangles = new List<Graphics.Rect>();
for (var curLine = spanStartLine; curLine <= spanEndLine; curLine++)
{
var lineHeight = layout.GetLineBottom(lineCount) - layout.GetLineTop(lineCount);
lineHeights[lineCount - startLine] = lineHeight;

if (totalLineHeights.Count <= lineCount)
totalLineHeights.Add(lineHeight);
global::Android.Graphics.Rect bounds = new global::Android.Graphics.Rect();
layout.GetLineBounds(curLine, bounds);

var lineHeight = bounds.Height();
var lineStartOffset = layout.GetLineStart(curLine);
var lineVisibleEndOffset = layout.GetLineVisibleEnd(curLine);

var startOffset = (curLine == spanStartLine) ? spanStartOffset : lineStartOffset;
var spanStartX = (int)layout.GetPrimaryHorizontal(startOffset);

var endOffset = (curLine == spanEndLine) ? spanEndOffset : lineVisibleEndOffset;
var spanEndX = (int)layout.GetSecondaryHorizontal(endOffset);

var spanWidth = spanEndX - spanStartX;
var spanLeftX = spanStartX;

// If rtl is used, startX would be bigger than endX
if (spanStartX > spanEndX)
{
spanWidth = spanStartX - spanEndX;
spanLeftX = spanEndX;
}

if (spanWidth > 1)
{
var rectangle = new Graphics.Rect(spanLeftX + padLeft, bounds.Top + padTop, spanWidth, lineHeight);
spanRectangles.Add(rectangle);
}
}

var yaxis = 0.0;

for (var line = startLine; line > 0; line--)
yaxis += totalLineHeights[line];

((ISpatialElement)span).Region = Region.FromLines(lineHeights, labelWidth, startX, endX, yaxis).Inflate(10);
((ISpatialElement)span).Region = Region.FromRectangles(spanRectangles).Inflate(10);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,11 @@ void SetupGestures()
return;

var platformView = Control;

if (platformView == null)
return;

if (View.GestureRecognizers.Count == 0)
if (View.GestureController.CompositeGestureRecognizers.Count == 0)
{
platformView.Touch -= OnPlatformViewTouched;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ override Microsoft.Maui.Controls.SearchBar.IsEnabledCore.get -> bool
~override Microsoft.Maui.Controls.LayoutOptions.Equals(object obj) -> bool
~override Microsoft.Maui.Controls.Region.Equals(object obj) -> bool
~override Microsoft.Maui.Controls.Shapes.Matrix.Equals(object obj) -> bool
~static Microsoft.Maui.Controls.Region.FromRectangles(System.Collections.Generic.IEnumerable<Microsoft.Maui.Graphics.Rect> rectangles) -> Microsoft.Maui.Controls.Region
~static readonly Microsoft.Maui.Controls.WebView.UserAgentProperty -> Microsoft.Maui.Controls.BindableProperty
*REMOVED*~Microsoft.Maui.Controls.IMessagingCenter.Send<TSender, TArgs>(TSender sender, string message, TArgs args) -> void
*REMOVED*~Microsoft.Maui.Controls.IMessagingCenter.Send<TSender>(TSender sender, string message) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ override Microsoft.Maui.Controls.SearchBar.IsEnabledCore.get -> bool
~Microsoft.Maui.Controls.WebView.UserAgent.set -> void
~override Microsoft.Maui.Controls.Region.Equals(object obj) -> bool
~override Microsoft.Maui.Controls.Shapes.Matrix.Equals(object obj) -> bool
~static Microsoft.Maui.Controls.Region.FromRectangles(System.Collections.Generic.IEnumerable<Microsoft.Maui.Graphics.Rect> rectangles) -> Microsoft.Maui.Controls.Region
~static readonly Microsoft.Maui.Controls.WebView.UserAgentProperty -> Microsoft.Maui.Controls.BindableProperty
*REMOVED*~static Microsoft.Maui.Controls.MessagingCenter.Send<TSender, TArgs>(TSender sender, string message, TArgs args) -> void
*REMOVED*~static Microsoft.Maui.Controls.MessagingCenter.Send<TSender>(TSender sender, string message) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ override Microsoft.Maui.Controls.SearchBar.IsEnabledCore.get -> bool
~Microsoft.Maui.Controls.WebView.UserAgent.set -> void
~override Microsoft.Maui.Controls.Region.Equals(object obj) -> bool
~override Microsoft.Maui.Controls.Shapes.Matrix.Equals(object obj) -> bool
~static Microsoft.Maui.Controls.Region.FromRectangles(System.Collections.Generic.IEnumerable<Microsoft.Maui.Graphics.Rect> rectangles) -> Microsoft.Maui.Controls.Region
~static readonly Microsoft.Maui.Controls.WebView.UserAgentProperty -> Microsoft.Maui.Controls.BindableProperty
*REMOVED*~static Microsoft.Maui.Controls.MessagingCenter.Send<TSender, TArgs>(TSender sender, string message, TArgs args) -> void
*REMOVED*~static Microsoft.Maui.Controls.MessagingCenter.Send<TSender>(TSender sender, string message) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@ Microsoft.Maui.Controls.IValueConverter.ConvertBack(object? value, System.Type!
~static Microsoft.Maui.Controls.GridExtensions.Add(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int left, int right, int top, int bottom) -> void
~static Microsoft.Maui.Controls.GridExtensions.AddWithSpan(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int row = 0, int column = 0, int rowSpan = 1, int columnSpan = 1) -> void
Microsoft.Maui.Controls.Shapes.Shape.~Shape() -> void
override Microsoft.Maui.Controls.Shapes.Shape.OnBindingContextChanged() -> void
override Microsoft.Maui.Controls.Shapes.Shape.OnBindingContextChanged() -> void
~static Microsoft.Maui.Controls.Region.FromRectangles(System.Collections.Generic.IEnumerable<Microsoft.Maui.Graphics.Rect> rectangles) -> Microsoft.Maui.Controls.Region
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ override Microsoft.Maui.Controls.SearchBar.IsEnabledCore.get -> bool
~override Microsoft.Maui.Controls.LayoutOptions.Equals(object obj) -> bool
~override Microsoft.Maui.Controls.Region.Equals(object obj) -> bool
~override Microsoft.Maui.Controls.Shapes.Matrix.Equals(object obj) -> bool
~static Microsoft.Maui.Controls.Region.FromRectangles(System.Collections.Generic.IEnumerable<Microsoft.Maui.Graphics.Rect> rectangles) -> Microsoft.Maui.Controls.Region
~static readonly Microsoft.Maui.Controls.WebView.UserAgentProperty -> Microsoft.Maui.Controls.BindableProperty
*REMOVED*~static Microsoft.Maui.Controls.MessagingCenter.Send<TSender, TArgs>(TSender sender, string message, TArgs args) -> void
*REMOVED*~static Microsoft.Maui.Controls.MessagingCenter.Send<TSender>(TSender sender, string message) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ override Microsoft.Maui.Controls.SearchBar.IsEnabledCore.get -> bool
~override Microsoft.Maui.Controls.LayoutOptions.Equals(object obj) -> bool
~override Microsoft.Maui.Controls.Region.Equals(object obj) -> bool
~override Microsoft.Maui.Controls.Shapes.Matrix.Equals(object obj) -> bool
~static Microsoft.Maui.Controls.Region.FromRectangles(System.Collections.Generic.IEnumerable<Microsoft.Maui.Graphics.Rect> rectangles) -> Microsoft.Maui.Controls.Region
~static readonly Microsoft.Maui.Controls.WebView.UserAgentProperty -> Microsoft.Maui.Controls.BindableProperty
*REMOVED*~Microsoft.Maui.Controls.IMessagingCenter.Send<TSender, TArgs>(TSender sender, string message, TArgs args) -> void
*REMOVED*~Microsoft.Maui.Controls.IMessagingCenter.Send<TSender>(TSender sender, string message) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ override Microsoft.Maui.Controls.SearchBar.IsEnabledCore.get -> bool
~override Microsoft.Maui.Controls.LayoutOptions.Equals(object obj) -> bool
~override Microsoft.Maui.Controls.Region.Equals(object obj) -> bool
~override Microsoft.Maui.Controls.Shapes.Matrix.Equals(object obj) -> bool
~static Microsoft.Maui.Controls.Region.FromRectangles(System.Collections.Generic.IEnumerable<Microsoft.Maui.Graphics.Rect> rectangles) -> Microsoft.Maui.Controls.Region
~static readonly Microsoft.Maui.Controls.WebView.UserAgentProperty -> Microsoft.Maui.Controls.BindableProperty
*REMOVED*~static Microsoft.Maui.Controls.MessagingCenter.Send<TSender, TArgs>(TSender sender, string message, TArgs args) -> void
*REMOVED*~static Microsoft.Maui.Controls.MessagingCenter.Send<TSender>(TSender sender, string message) -> void
Expand Down
7 changes: 7 additions & 0 deletions src/Controls/src/Core/Region.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.Maui.Graphics;

namespace Microsoft.Maui.Controls
Expand All @@ -26,6 +27,12 @@ public struct Region : IEquatable<Region>
_inflation = inflation;
}

public static Region FromRectangles(IEnumerable<Rect> rectangles)
{
var list = rectangles.ToList();
return new Region(list);
}

/// <include file="../../docs/Microsoft.Maui.Controls/Region.xml" path="//Member[@MemberName='FromLines']/Docs/*" />
public static Region FromLines(double[] lineHeights, double maxWidth, double startX, double endX, double startY)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Core/tests/DeviceTests.Shared/Stubs/ContextStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class ContextStub : IMauiContext, IServiceProvider
#endif
#if ANDROID
Android.Content.Context _androidContext;
IFontManager _fontManager;
#endif

#if WINDOWS
Expand All @@ -37,6 +38,9 @@ public object GetService(Type serviceType)
if (serviceType == typeof(IAnimationManager))
return _manager ??= _services.GetRequiredService<IAnimationManager>();
#if ANDROID
if (serviceType == typeof(IFontManager))
return _fontManager ??= _services.GetRequiredService<IFontManager>();

if (serviceType == typeof(Android.Content.Context))
return MauiProgramDefaults.DefaultContext;

Expand Down

0 comments on commit 2c301d7

Please sign in to comment.