Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
Fix 4143: improved Span region calculation (#13348) fixes #4143 fixes #…
Browse files Browse the repository at this point in the history
…6992 fixes #11650 fixes #7655 fixes #11657 fixes #10520 fixes #4829

* improved span region calculation on android

* Remove unused code

* Nit: clean up comments

* Wrap in ScrollView for small screen compatibility

Co-authored-by: Tim Dittmar <Tim.Dittmar@Exxeta.com>
Co-authored-by: Rachel Kang <rachel.j.kang@gmail.com>
Co-authored-by: Rachel Kang <rachelkang@microsoft.com>
  • Loading branch information
4 people committed Jul 7, 2021
1 parent 6dfd32d commit ca19c01
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8" ?>
<controls:TestContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xamarin.Forms.Controls"
xmlns:system="clr-namespace:System;assembly=mscorlib"
x:Name="This"
x:Class="Xamarin.Forms.Controls.Issues.Issue4143">

<ScrollView>
<StackLayout>
<Label
Text="This test is passed if all tappable spans (blue and underlined texts) react to touches by changing its background color. Only the touched span should react and not others."/>

<Label
Margin="10"
HorizontalOptions="Center"
TextColor="Black"
BackgroundColor="CadetBlue">
<Label.FormattedText>
<FormattedString>
<Span Text="Two clickable spans in one line:&#10;" />
<Span
x:Name="Link1"
TextDecorations="Underline"
Text="Link1"
TextColor="Blue">
<Span.GestureRecognizers>
<TapGestureRecognizer Tapped="OnLink1Tapped"/>
</Span.GestureRecognizers>
</Span>
<Span Text=" " />
<Span
x:Name="Link2"
Text="Link2&#10;"
TextDecorations="Underline"
TextColor="Blue">
<Span.GestureRecognizers>
<TapGestureRecognizer Tapped="OnLink2Tapped" />
</Span.GestureRecognizers>
</Span>

<Span Text="Multiline tappable span:&#10;" />
<Span
x:Name="Link3"
TextDecorations="Underline"
Text="Link3_1&#10;Link3_2"
TextColor="Blue">
<Span.GestureRecognizers>
<TapGestureRecognizer Tapped="OnLink3Tapped" />
</Span.GestureRecognizers>
</Span>
</FormattedString>
</Label.FormattedText>
</Label>

<Label
Margin="10"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Padding="10,20"
BackgroundColor="CadetBlue">

<Label.FormattedText>
<FormattedString>
<Span Text="Clickable Span in a Label with padding:&#10;" />
<Span
x:Name="Link4"
Text="Link4"
TextDecorations="Underline"
TextColor="Blue">
<Span.GestureRecognizers>
<TapGestureRecognizer Tapped="OnLink4Tapped"/>
</Span.GestureRecognizers>
</Span>
</FormattedString>
</Label.FormattedText>
</Label>

<Label
Margin = "10"
FlowDirection="RightToLeft"
TextColor="Black"
BackgroundColor="CadetBlue">

<Label.FormattedText>
<FormattedString>
<Span Text="لكن لا بد أن أوضح لك أن كل هذه الأفكار المغلوطة حول استنكار النشوة وتمجيد الألم نشأت بالفعل، وسأعرض لك التفاصيل لتكتشف حقيقة وأساس تلك السعادة البشرية، فلا أحد يرفض أو يكره أو يتجنب الشعور بالسعادة، ولكن بفضل هؤلاء الأشخاص الذين لا يدركون بأن السعادة لا بد أن نستشعرها بصورة أكثر عقلانية ومنطقية فيعرضهم هذا لمواجهة الظروف الأليمة، وأكرر بأنه لا يوجد من يرغب في الحب ونيل المنال ويتلذذ بالآلام، الألم هو الألم ولكن نتيجة لظروف ما قد تكمن السعاده فيما نتحمله من كد وأسي." />
<Span Text="{x:Static system:Environment.NewLine}"/>
<Span
x:Name="Link5"
Text="و سأعرض مثال حي لهذا، من منا لم يتحمل جهد بدني شاق إلا من أجل الحصول على ميزة أو فائدة؟ ولكن من لديه الحق أن ينتقد شخص ما أراد أن يشعر بالسعادة التي لا تشوبها عواقب أليمة أو آخر أراد أن يتجنب الألم الذي ربما تنجم عنه بعض المتعة ؟ "
TextDecorations="Underline"
TextColor="Blue">
<Span.GestureRecognizers>
<TapGestureRecognizer Tapped="OnLink5Tapped" />
</Span.GestureRecognizers>
</Span>
<Span Text="علي الجانب الآخر نشجب ونستنكر هؤلاء الرجال المفتونون بنشوة اللحظة الهائمون في رغباتهم فلا يدركون ما يعقبها من الألم والأسي المحتم، واللوم كذلك يشمل هؤلاء الذين أخفقوا في واجباتهم نتيجة لضعف إرادتهم فيتساوي مع هؤلاء الذين يتجنبون وينأون عن تحمل الكدح والألم ." />
</FormattedString>
</Label.FormattedText>
</Label>

</StackLayout>
</ScrollView>
</controls:TestContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;

#if UITEST
using Xamarin.UITest;
using NUnit.Framework;
#endif

namespace Xamarin.Forms.Controls.Issues
{
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 4143, "Span inaccuracies", PlatformAffected.Android)]
public partial class Issue4143 : TestContentPage
{
Color[] _colors = new Color[]{ Color.Red, Color.Blue, Color.Green, Color.Yellow, Color.Brown, Color.Purple, Color.Orange, Color.Gray };
Random _rand = new Random();

protected override void Init()
{
#if APP
InitializeComponent();
#endif
}

#if APP
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 OnLink4Tapped(object sender, EventArgs e)
{
SetRandomBackgroundColor(Link4);
}
void OnLink5Tapped(object sender, EventArgs e)
{
SetRandomBackgroundColor(Link5);
}
#endif

void SetRandomBackgroundColor(Span span)
{
var oldColor = span.BackgroundColor;
Color newColor;
do
{
newColor = _colors[_rand.Next(_colors.Length)];
} while(oldColor == newColor);

span.BackgroundColor = newColor;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,9 @@
<DependentUpon>Issue5268.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Issue4143.xaml.cs">
<DependentUpon>Issue4143.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Issue6713.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue6705.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LegacyComponents\NonAppCompatSwitch.cs" />
Expand Down Expand Up @@ -2714,6 +2717,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Issue4143.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Issue13437.xaml">
Expand Down
7 changes: 7 additions & 0 deletions Xamarin.Forms.Core/Region.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace Xamarin.Forms
{
Expand All @@ -21,6 +22,12 @@ public struct Region
{
_inflation = inflation;
}

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

public static Region FromLines(double[] lineHeights, double maxWidth, double startX, double endX, double startY)
{
Expand Down
76 changes: 41 additions & 35 deletions Xamarin.Forms.Platform.Android/Extensions/TextViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,11 @@ public static void RecalculateSpanPositions(this TextView textView, Label elemen

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

for (int i = 0; i < spannableString.Length(); i = next)
{
var type = Java.Lang.Class.FromType(typeof(Java.Lang.Object));
Expand All @@ -108,47 +111,50 @@ public static void RecalculateSpanPositions(this TextView textView, Label elemen

// get all spans in the range - Android can have overlapping spans
var spans = spannableString.GetSpans(i, next, type);

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 startX = layout.GetPrimaryHorizontal(startSpanOffset);
var endX = layout.GetPrimaryHorizontal(endSpanOffset);

var startLine = layout.GetLineForOffset(startSpanOffset);
var endLine = layout.GetLineForOffset(endSpanOffset);
var spanStartOffset = spannableString.GetSpanStart(startSpan);
var spanEndOffset = spannableString.GetSpanEnd(endSpan);

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

// calculate all the different line heights
for (var lineCount = startLine; lineCount <= endLine; lineCount++)
var spanStartLine = layout.GetLineForOffset(spanStartOffset);
var spanEndLine = layout.GetLineForOffset(spanEndOffset);

// go through all lines that are affected by the span and calculate a rectangle for each
var spanRectangles = new List<Rectangle>();
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 Rectangle(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

0 comments on commit ca19c01

Please sign in to comment.