Browse Source

Implement FormattedText.BuildHighlightGeometry

pull/8671/head
Benedikt Stebner 4 years ago
parent
commit
116fd47439
  1. 5
      samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
  2. 1
      samples/Sandbox/MainWindow.axaml
  3. 106
      src/Avalonia.Base/Media/FormattedText.cs
  4. 13
      src/Avalonia.Base/Media/Geometry.cs

5
samples/RenderDemo/Pages/FormattedTextPage.axaml.cs

@ -3,6 +3,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Immutable;
namespace RenderDemo.Pages
{
@ -59,6 +60,10 @@ namespace RenderDemo.Pages
var geometry = formattedText.BuildGeometry(new Point(10 + formattedText.Width + 10, 0));
context.DrawGeometry(gradient, null, geometry);
var highlightGeometry = formattedText.BuildHighlightGeometry(new Point(10 + formattedText.Width + 10, 0));
context.DrawGeometry(null, new ImmutablePen(gradient.ToImmutable(), 2), highlightGeometry);
}
}
}

1
samples/Sandbox/MainWindow.axaml

@ -1,5 +1,4 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class="Sandbox.MainWindow">
<TextBox />
</Window>

106
src/Avalonia.Base/Media/FormattedText.cs

@ -1,8 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using Avalonia.Controls;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
@ -654,14 +656,16 @@ namespace Avalonia.Media
// line break before _currentLine, needed in case we have to reformat it with collapsing symbol
private TextLineBreak? _previousLineBreak;
private int _position;
private int _length;
internal LineEnumerator(FormattedText text)
{
_previousHeight = 0;
Length = 0;
_length = 0;
_previousLineBreak = null;
Position = 0;
_position = 0;
_lineCount = 0;
_totalHeight = 0;
Current = null;
@ -678,9 +682,17 @@ namespace Avalonia.Media
_nextLine = null;
}
private int Position { get; set; }
public int Position
{
get => _position;
private set => _position = value;
}
private int Length { get; set; }
public int Length
{
get => _length;
private set => _length = value;
}
/// <summary>
/// Gets the current text line in the collection
@ -1292,6 +1304,92 @@ namespace Avalonia.Media
return accumulatedGeometry;
}
/// <summary>
/// Builds a highlight geometry object.
/// </summary>
/// <param name="origin">The origin of the highlight region</param>
/// <returns>Geometry that surrounds the text.</returns>
public Geometry? BuildHighlightGeometry(Point origin)
{
return BuildHighlightGeometry(origin, 0, _text.Length);
}
/// <summary>
/// Builds a highlight geometry object for a given character range.
/// </summary>
/// <param name="origin">The origin of the highlight region.</param>
/// <param name="startIndex">The start index of initial character the bounds should be obtained for.</param>
/// <param name="count">The number of characters the bounds should be obtained for.</param>
/// <returns>Geometry that surrounds the specified character range.</returns>
public Geometry? BuildHighlightGeometry(Point origin, int startIndex, int count)
{
ValidateRange(startIndex, count);
Geometry? accumulatedBounds = null;
using (var enumerator = GetEnumerator())
{
var lineOrigin = origin;
while (enumerator.MoveNext())
{
var currentLine = enumerator.Current!;
int x0 = Math.Max(enumerator.Position, startIndex);
int x1 = Math.Min(enumerator.Position + enumerator.Length, startIndex + count);
// check if this line is intersects with the specified character range
if (x0 < x1)
{
var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0);
if (highlightBounds != null)
{
foreach (var bound in highlightBounds)
{
var rect = bound.Rectangle;
if (FlowDirection == FlowDirection.RightToLeft)
{
// Convert logical units (which extend leftward from the right edge
// of the paragraph) to physical units.
//
// Note that since rect is in logical units, rect.Right corresponds to
// the visual *left* edge of the rectangle in the RTL case. Specifically,
// is the distance leftward from the right edge of the formatting rectangle
// whose width is the paragraph width passed to FormatLine.
//
rect = rect.WithX(enumerator.CurrentParagraphWidth - rect.Right);
}
rect = new Rect(new Point(rect.X + lineOrigin.X, rect.Y + lineOrigin.Y), rect.Size);
RectangleGeometry rectangleGeometry = new RectangleGeometry(rect);
if (accumulatedBounds == null)
{
accumulatedBounds = rectangleGeometry;
}
else
{
accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union);
}
}
}
}
AdvanceLineOrigin(ref lineOrigin, currentLine);
}
}
if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsEmpty)
{
return null;
}
return accumulatedBounds;
}
/// <summary>
/// Draws the text object
/// </summary>

13
src/Avalonia.Base/Media/Geometry.cs

@ -185,5 +185,18 @@ namespace Avalonia.Media
var control = e.Sender as Geometry;
control?.InvalidateGeometry();
}
/// <summary>
/// Combines the two geometries using the specified <see cref="GeometryCombineMode"/> and applies the specified transform to the resulting geometry.
/// </summary>
/// <param name="geometry1">The first geometry to combine.</param>
/// <param name="geometry2">The second geometry to combine.</param>
/// <param name="combineMode">One of the enumeration values that specifies how the geometries are combined.</param>
/// <param name="transform">A transformation to apply to the combined geometry, or <c>null</c>.</param>
/// <returns></returns>
public static Geometry Combine(Geometry geometry1, RectangleGeometry geometry2, GeometryCombineMode combineMode, Transform? transform = null)
{
return new CombinedGeometry(combineMode, geometry1, geometry2, transform);
}
}
}

Loading…
Cancel
Save