Browse Source

Merge branch 'master' into disableSetProcessName-feature

pull/9085/head
Tako 3 years ago
committed by GitHub
parent
commit
05ee302cce
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      samples/ControlCatalog/MainView.xaml
  2. 5
      samples/ControlCatalog/MainView.xaml.cs
  3. 66
      samples/ControlCatalog/Pages/AdornerLayerPage.xaml
  4. 48
      samples/ControlCatalog/Pages/AdornerLayerPage.xaml.cs
  5. 12
      samples/ControlCatalog/Pages/ScreenPage.cs
  6. 5
      samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
  7. 1
      samples/Sandbox/MainWindow.axaml
  8. 106
      src/Avalonia.Base/Media/FormattedText.cs
  9. 13
      src/Avalonia.Base/Media/Geometry.cs
  10. 11
      src/Avalonia.Base/Media/GlyphRun.cs
  11. 253
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  12. 2
      src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs
  13. 14
      src/Avalonia.Controls/Documents/InlineCollection.cs
  14. 111
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  15. 34
      src/Avalonia.Controls/Window.cs
  16. 83
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

3
samples/ControlCatalog/MainView.xaml

@ -19,6 +19,9 @@
<TabItem Header="Acrylic">
<pages:AcrylicPage />
</TabItem>
<TabItem Header="AdornerLayer">
<pages:AdornerLayerPage />
</TabItem>
<TabItem Header="AutoCompleteBox">
<pages:AutoCompleteBoxPage />
</TabItem>

5
samples/ControlCatalog/MainView.xaml.cs

@ -2,10 +2,10 @@ using System;
using System.Collections;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.Themes.Fluent;
using ControlCatalog.Models;
using ControlCatalog.Pages;
@ -20,7 +20,7 @@ namespace ControlCatalog
var sideBar = this.Get<TabControl>("Sidebar");
if (AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo().IsDesktop == true)
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
{
var tabItems = (sideBar.Items as IList);
tabItems?.Add(new TabItem()
@ -28,7 +28,6 @@ namespace ControlCatalog
Header = "Screens",
Content = new ScreenPage()
});
}
var themes = this.Get<ComboBox>("Themes");

66
samples/ControlCatalog/Pages/AdornerLayerPage.xaml

@ -0,0 +1,66 @@
<UserControl x:Class="ControlCatalog.Pages.AdornerLayerPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="800"
d:DesignWidth="400"
mc:Ignorable="d">
<DockPanel>
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto" Margin="16" DockPanel.Dock="Top">
<TextBlock Grid.Column="0" Grid.Row="0">Rotation</TextBlock>
<Slider Name="rotation" Maximum="360" Grid.Column="1" Grid.Row="0"/>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" DockPanel.Dock="Top">
<Button Name="AddAdorner"
Click="AddAdorner_OnClick"
Content="Add Adorner"
Margin="6" />
<Button Name="RemoveAdorner"
Click="RemoveAdorner_OnClick"
Content="Remove Adorner"
Margin="6" />
</StackPanel>
<Grid ColumnDefinitions="24,Auto,24"
RowDefinitions="24,Auto,24"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border Background="{DynamicResource SystemAccentColor}" Grid.Column="1" Grid.Row="0"/>
<Border Background="{DynamicResource SystemAccentColor}" Grid.Column="0" Grid.Row="1"/>
<Border Background="{DynamicResource SystemAccentColor}" Grid.Column="2" Grid.Row="1"/>
<Border Background="{DynamicResource SystemAccentColor}" Grid.Column="1" Grid.Row="2"/>
<LayoutTransformControl Name="layoutTransform" Grid.Column="1" Grid.Row="1">
<LayoutTransformControl.LayoutTransform>
<RotateTransform Angle="{Binding #rotation.Value}"/>
</LayoutTransformControl.LayoutTransform>
<Button Name="AdornerButton"
Content="Adorned Button"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
VerticalContentAlignment="Center" VerticalAlignment="Stretch"
Width="200" Height="42">
<AdornerLayer.Adorner>
<Canvas x:Name="AdornerCanvas"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Cyan"
IsHitTestVisible="False"
Opacity="0.3"
IsVisible="True">
<Line StartPoint="-10000,0" EndPoint="10000,0" Stroke="Cyan" StrokeThickness="1" />
<Line StartPoint="-10000,42" EndPoint="10000,42" Stroke="Cyan" StrokeThickness="1" />
<Line StartPoint="0,-10000" EndPoint="0,10000" Stroke="Cyan" StrokeThickness="1" />
<Line StartPoint="200,-10000" EndPoint="200,10000" Stroke="Cyan" StrokeThickness="1" />
</Canvas>
</AdornerLayer.Adorner>
</Button>
</LayoutTransformControl>
</Grid>
</DockPanel>
</UserControl>

48
samples/ControlCatalog/Pages/AdornerLayerPage.xaml.cs

@ -0,0 +1,48 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class AdornerLayerPage : UserControl
{
private Control? _adorner;
public AdornerLayerPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void RemoveAdorner_OnClick(object? sender, RoutedEventArgs e)
{
var adornerButton = this.FindControl<Button>("AdornerButton");
if (adornerButton is { })
{
var adorner = AdornerLayer.GetAdorner(adornerButton);
if (adorner is { })
{
_adorner = adorner;
}
AdornerLayer.SetAdorner(adornerButton, null);
}
}
private void AddAdorner_OnClick(object? sender, RoutedEventArgs e)
{
var adornerButton = this.FindControl<Button>("AdornerButton");
if (adornerButton is { })
{
if (_adorner is { })
{
AdornerLayer.SetAdorner(adornerButton, _adorner);
}
}
}
}
}

12
samples/ControlCatalog/Pages/ScreenPage.cs

@ -2,10 +2,10 @@ using System;
using System.Globalization;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
namespace ControlCatalog.Pages
{
@ -18,8 +18,10 @@ namespace ControlCatalog.Pages
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
Window w = (Window)VisualRoot!;
w.PositionChanged += (sender, args) => InvalidateVisual();
if(VisualRoot is Window w)
{
w.PositionChanged += (_, _) => InvalidateVisual();
}
}
public override void Render(DrawingContext context)
@ -27,7 +29,7 @@ namespace ControlCatalog.Pages
base.Render(context);
if (!(VisualRoot is Window w))
{
return;
return;
}
var screens = w.Screens.All;
var scaling = ((IRenderRoot)w).RenderScaling;
@ -40,7 +42,7 @@ namespace ControlCatalog.Pages
if (screen.Bounds.X / 10f < _leftMost)
{
_leftMost = screen.Bounds.X / 10f;
InvalidateVisual();
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
return;
}

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

11
src/Avalonia.Base/Media/GlyphRun.cs

@ -789,14 +789,15 @@ namespace Avalonia.Media
var clusterLength = 1;
while (i - 1 >= 0)
var j = i;
while (j - 1 >= 0)
{
var nextCluster = GlyphClusters[i - 1];
var nextCluster = GlyphClusters[--j];
if (currentCluster == nextCluster)
{
clusterLength++;
i--;
clusterLength++;
continue;
}
@ -811,7 +812,7 @@ namespace Avalonia.Media
trailingWhitespaceLength += clusterLength;
glyphCount++;
glyphCount += clusterLength;
}
}

253
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -128,7 +128,7 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList[0];
if(collapsingProperties is null)
if (collapsingProperties is null)
{
return this;
}
@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
{
var currentRun = _textRuns[i];
if(currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
{
var rightToLeftIndex = i;
currentPosition += currentRun.TextSourceLength;
@ -213,14 +213,14 @@ namespace Avalonia.Media.TextFormatting
for (var j = i; i <= rightToLeftIndex; j++)
{
if(j > _textRuns.Count - 1)
if (j > _textRuns.Count - 1)
{
break;
}
currentRun = _textRuns[j];
if(currentDistance + currentRun.Size.Width <= distance)
if (currentDistance + currentRun.Size.Width <= distance)
{
currentDistance += currentRun.Size.Width;
currentPosition -= currentRun.TextSourceLength;
@ -266,10 +266,6 @@ namespace Avalonia.Media.TextFormatting
{
offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
}
//else
//{
// offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length);
//}
characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
@ -326,11 +322,11 @@ namespace Avalonia.Media.TextFormatting
continue;
}
break;
}
if(i > index)
if (i > index)
{
while (i >= index)
{
@ -354,7 +350,7 @@ namespace Avalonia.Media.TextFormatting
}
}
if (currentPosition + currentRun.TextSourceLength >= characterIndex &&
if (currentPosition + currentRun.TextSourceLength >= characterIndex &&
TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _))
{
return Math.Max(0, currentDistance + distance);
@ -534,6 +530,8 @@ namespace Avalonia.Media.TextFormatting
double currentWidth = 0;
var currentRect = Rect.Empty;
TextRunBounds lastRunBounds = default;
for (var index = 0; index < TextRuns.Count; index++)
{
if (TextRuns[index] is not DrawableTextRun currentRun)
@ -543,53 +541,93 @@ namespace Avalonia.Media.TextFormatting
var characterLength = 0;
var endX = startX;
var runWidth = 0.0;
TextRunBounds? currentRunBounds = null;
var currentShapedRun = currentRun as ShapedTextCharacters;
TextRunBounds currentRunBounds;
double combinedWidth;
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
}
if (currentShapedRun != null && !currentShapedRun.ShapedBuffer.IsLeftToRight)
{
var rightToLeftIndex = index;
startX += currentShapedRun.Size.Width;
var rightToLeftWidth = currentShapedRun.Size.Width;
while (rightToLeftIndex + 1 <= _textRuns.Count - 1)
while (rightToLeftIndex + 1 <= _textRuns.Count - 1 && _textRuns[rightToLeftIndex + 1] is ShapedTextCharacters nextShapedRun)
{
var nextShapedRun = _textRuns[rightToLeftIndex + 1] as ShapedTextCharacters;
if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight)
{
break;
}
startX += nextShapedRun.Size.Width;
rightToLeftIndex++;
rightToLeftWidth += nextShapedRun.Size.Width;
if (currentPosition + nextShapedRun.TextSourceLength > firstTextSourceIndex + textLength)
{
break;
}
currentShapedRun = nextShapedRun;
}
if (TryGetTextRunBoundsRightToLeft(startX, firstTextSourceIndex, characterIndex, rightToLeftIndex, ref currentPosition, ref remainingLength, out currentRunBounds))
startX = startX + rightToLeftWidth;
currentRunBounds = GetRightToLeftTextRunBounds(currentShapedRun, startX, firstTextSourceIndex, characterIndex, currentPosition, remainingLength);
remainingLength -= currentRunBounds.Length;
currentPosition = currentRunBounds.TextSourceCharacterIndex + currentRunBounds.Length;
endX = currentRunBounds.Rectangle.Right;
startX = currentRunBounds.Rectangle.Left;
var rightToLeftRunBounds = new List<TextRunBounds> { currentRunBounds };
for (int i = rightToLeftIndex - 1; i >= index; i--)
{
startX = currentRunBounds!.Rectangle.Left;
endX = currentRunBounds.Rectangle.Right;
currentShapedRun = TextRuns[i] as ShapedTextCharacters;
if(currentShapedRun == null)
{
continue;
}
runWidth = currentRunBounds.Rectangle.Width;
currentRunBounds = GetRightToLeftTextRunBounds(currentShapedRun, startX, firstTextSourceIndex, characterIndex, currentPosition, remainingLength);
rightToLeftRunBounds.Insert(0, currentRunBounds);
remainingLength -= currentRunBounds.Length;
startX = currentRunBounds.Rectangle.Left;
currentPosition += currentRunBounds.Length;
}
combinedWidth = endX - startX;
currentRect = new Rect(startX, 0, combinedWidth, Height);
currentDirection = FlowDirection.RightToLeft;
if (!MathUtilities.IsZero(combinedWidth))
{
result.Add(new TextBounds(currentRect, currentDirection, rightToLeftRunBounds));
}
startX = endX;
}
else
{
if (currentShapedRun != null)
{
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
continue;
}
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
currentPosition += offset;
@ -665,43 +703,46 @@ namespace Avalonia.Media.TextFormatting
characterLength = NewLineLength;
}
runWidth = endX - startX;
currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
combinedWidth = endX - startX;
currentRunBounds = new TextRunBounds(new Rect(startX, 0, combinedWidth, Height), currentPosition, characterLength, currentRun);
currentPosition += characterLength;
remainingLength -= characterLength;
}
if (currentRunBounds != null && !MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
startX = endX;
if (currentRunBounds.TextRun != null && !MathUtilities.IsZero(combinedWidth) || NewLineLength > 0)
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
if (result.Count > 0 && lastDirection == currentDirection && MathUtilities.AreClose(currentRect.Left, lastRunBounds.Rectangle.Right))
{
currentRect = currentRect.WithWidth(currentWidth + combinedWidth);
var textBounds = result[result.Count - 1];
var textBounds = result[result.Count - 1];
textBounds.Rectangle = currentRect;
textBounds.Rectangle = currentRect;
textBounds.TextRunBounds.Add(currentRunBounds!);
}
else
{
currentRect = currentRunBounds!.Rectangle;
textBounds.TextRunBounds.Add(currentRunBounds);
}
else
{
currentRect = currentRunBounds.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
}
}
lastRunBounds = currentRunBounds;
}
currentWidth += runWidth;
currentWidth += combinedWidth;
if (remainingLength <= 0 || currentPosition >= characterIndex)
{
break;
}
startX = endX;
lastDirection = currentDirection;
}
@ -856,105 +897,45 @@ namespace Avalonia.Media.TextFormatting
return result;
}
private bool TryGetTextRunBoundsRightToLeft(double startX, int firstTextSourceIndex, int characterIndex, int runIndex, ref int currentPosition, ref int remainingLength, out TextRunBounds? textRunBounds)
private TextRunBounds GetRightToLeftTextRunBounds(ShapedTextCharacters currentRun, double endX, int firstTextSourceIndex, int characterIndex, int currentPosition, int remainingLength)
{
textRunBounds = null;
var startX = endX;
for (var index = runIndex; index >= 0; index--)
{
if (TextRuns[index] is not DrawableTextRun currentRun)
{
continue;
}
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
{
startX -= currentRun.Size.Width;
currentPosition += offset;
currentPosition += currentRun.TextSourceLength;
var startIndex = currentRun.Text.Start + offset;
continue;
}
double startOffset;
double endOffset;
var characterLength = 0;
var endX = startX;
if (currentRun is ShapedTextCharacters currentShapedRun)
{
var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
currentPosition += offset;
var startIndex = currentRun.Text.Start + offset;
double startOffset;
double endOffset;
if (currentShapedRun.ShapedBuffer.IsLeftToRight)
{
if (currentPosition < startIndex)
{
startOffset = endOffset = 0;
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
}
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
startX -= currentRun.Size.Width - startOffset;
endX -= currentRun.Size.Width - endOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
endOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength);
}
else
{
if (currentPosition + currentRun.TextSourceLength <= characterIndex)
{
endX -= currentRun.Size.Width;
}
startOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
if (currentPosition < firstTextSourceIndex)
{
startX -= currentRun.Size.Width;
startX -= currentRun.Size.Width - startOffset;
endX -= currentRun.Size.Width - endOffset;
characterLength = currentRun.TextSourceLength;
}
}
var endHit = currentRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
if (endX < startX)
{
(endX, startX) = (startX, endX);
}
var characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength);
//Lines that only contain a linebreak need to be covered here
if (characterLength == 0)
{
characterLength = NewLineLength;
}
var runWidth = endX - startX;
remainingLength -= characterLength;
currentPosition += characterLength;
textRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if (endX < startX)
{
(endX, startX) = (startX, endX);
}
return true;
//Lines that only contain a linebreak need to be covered here
if (characterLength == 0)
{
characterLength = NewLineLength;
}
return false;
var runWidth = endX - startX;
return new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
}
public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength)
@ -1536,7 +1517,7 @@ namespace Avalonia.Media.TextFormatting
var textAlignment = _paragraphProperties.TextAlignment;
var paragraphFlowDirection = _paragraphProperties.FlowDirection;
if(textAlignment == TextAlignment.Justify)
if (textAlignment == TextAlignment.Justify)
{
textAlignment = TextAlignment.Start;
}

2
src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs

@ -3,7 +3,7 @@
/// <summary>
/// The bounding rectangle of text run
/// </summary>
public sealed class TextRunBounds
public readonly struct TextRunBounds
{
/// <summary>
/// Constructing TextRunBounds

14
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -111,7 +111,7 @@ namespace Avalonia.Controls.Documents
private void AddText(string text)
{
if(Parent is RichTextBlock textBlock && !textBlock.HasComplexContent)
if (Parent is RichTextBlock textBlock && !textBlock.HasComplexContent)
{
textBlock._text += text;
}
@ -156,7 +156,17 @@ namespace Avalonia.Controls.Documents
{
foreach (var child in this)
{
((ISetLogicalParent)child).SetParent(parent);
var oldParent = child.Parent;
if (oldParent != parent)
{
if (oldParent != null)
{
((ISetLogicalParent)child).SetParent(null);
}
((ISetLogicalParent)child).SetParent(parent);
}
}
}

111
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -27,12 +27,22 @@ namespace Avalonia.Controls.Primitives
public static readonly AttachedProperty<bool> IsClipEnabledProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, bool>("IsClipEnabled", true);
/// <summary>
/// Allows for getting and setting of the adorner for control.
/// </summary>
public static readonly AttachedProperty<Control?> AdornerProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Control?>("Adorner");
private static readonly AttachedProperty<AdornedElementInfo> s_adornedElementInfoProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo>("AdornedElementInfo");
private static readonly AttachedProperty<AdornerLayer?> s_savedAdornerLayerProperty =
AvaloniaProperty.RegisterAttached<Visual, Visual, AdornerLayer?>("SavedAdornerLayer");
static AdornerLayer()
{
AdornedElementProperty.Changed.Subscribe(AdornedElementChanged);
AdornerProperty.Changed.Subscribe(AdornerChanged);
}
public AdornerLayer()
@ -65,6 +75,107 @@ namespace Avalonia.Controls.Primitives
adorner.SetValue(IsClipEnabledProperty, isClipEnabled);
}
public static Control? GetAdorner(Visual visual)
{
return visual.GetValue(AdornerProperty);
}
public static void SetAdorner(Visual visual, Control? adorner)
{
visual.SetValue(AdornerProperty, adorner);
}
private static void AdornerChanged(AvaloniaPropertyChangedEventArgs<Control?> e)
{
if (e.Sender is Visual visual)
{
var oldAdorner = e.OldValue.GetValueOrDefault();
var newAdorner = e.NewValue.GetValueOrDefault();
if (Equals(oldAdorner, newAdorner))
{
return;
}
if (oldAdorner is { })
{
visual.AttachedToVisualTree -= VisualOnAttachedToVisualTree;
visual.DetachedFromVisualTree -= VisualOnDetachedFromVisualTree;
Detach(visual, oldAdorner);
}
if (newAdorner is { })
{
visual.AttachedToVisualTree += VisualOnAttachedToVisualTree;
visual.DetachedFromVisualTree += VisualOnDetachedFromVisualTree;
Attach(visual, newAdorner);
}
}
}
private static void VisualOnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
if (sender is Visual visual)
{
var adorner = GetAdorner(visual);
if (adorner is { })
{
Attach(visual, adorner);
}
}
}
private static void VisualOnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
if (sender is Visual visual)
{
var adorner = GetAdorner(visual);
if (adorner is { })
{
Detach(visual, adorner);
}
}
}
private static void Attach(Visual visual, Control adorner)
{
var layer = AdornerLayer.GetAdornerLayer(visual);
AddVisualAdorner(visual, adorner, layer);
visual.SetValue(s_savedAdornerLayerProperty, layer);
}
private static void Detach(Visual visual, Control adorner)
{
var layer = visual.GetValue(s_savedAdornerLayerProperty);
RemoveVisualAdorner(visual, adorner, layer);
visual.ClearValue(s_savedAdornerLayerProperty);
}
private static void AddVisualAdorner(Visual visual, Control? adorner, AdornerLayer? layer)
{
if (adorner is null || layer == null || layer.Children.Contains(adorner))
{
return;
}
AdornerLayer.SetAdornedElement(adorner, visual);
AdornerLayer.SetIsClipEnabled(adorner, false);
((ISetLogicalParent) adorner).SetParent(visual);
layer.Children.Add(adorner);
}
private static void RemoveVisualAdorner(Visual visual, Control? adorner, AdornerLayer? layer)
{
if (adorner is null || layer is null || !layer.Children.Contains(adorner))
{
return;
}
layer.Children.Remove(adorner);
((ISetLogicalParent) adorner).SetParent(null);
}
protected override Size MeasureOverride(Size availableSize)
{
foreach (var child in Children)

34
src/Avalonia.Controls/Window.cs

@ -668,7 +668,7 @@ namespace Avalonia.Controls
Owner = parent;
parent?.AddChild(this, false);
SetWindowStartupLocation(Owner?.PlatformImpl);
SetWindowStartupLocation(parent?.PlatformImpl);
PlatformImpl?.Show(ShowActivated, false);
Renderer?.Start();
@ -830,10 +830,11 @@ namespace Avalonia.Controls
var startupLocation = WindowStartupLocation;
if (startupLocation == WindowStartupLocation.CenterOwner &&
Owner is Window ownerWindow &&
ownerWindow.WindowState == WindowState.Minimized)
(owner is null ||
(Owner is Window ownerWindow && ownerWindow.WindowState == WindowState.Minimized))
)
{
// If startup location is CenterOwner, but owner is minimized then fall back
// If startup location is CenterOwner, but owner is null or minimized then fall back
// to CenterScreen. This behavior is consistent with WPF.
startupLocation = WindowStartupLocation.CenterScreen;
}
@ -851,31 +852,24 @@ namespace Avalonia.Controls
if (owner is not null)
{
screen = Screens.ScreenFromWindow(owner);
screen ??= Screens.ScreenFromPoint(owner.Position);
screen = Screens.ScreenFromWindow(owner)
?? Screens.ScreenFromPoint(owner.Position);
}
if (screen is null)
{
screen = Screens.ScreenFromPoint(Position);
}
screen ??= Screens.ScreenFromPoint(Position);
if (screen != null)
if (screen is not null)
{
Position = screen.WorkingArea.CenterRect(rect).Position;
}
}
else if (startupLocation == WindowStartupLocation.CenterOwner)
{
if (owner != null)
{
var ownerSize = owner.FrameSize ?? owner.ClientSize;
var ownerRect = new PixelRect(
owner.Position,
PixelSize.FromSize(ownerSize, scaling));
Position = ownerRect.CenterRect(rect).Position;
}
var ownerSize = owner!.FrameSize ?? owner.ClientSize;
var ownerRect = new PixelRect(
owner.Position,
PixelSize.FromSize(ownerSize, scaling));
Position = ownerRect.CenterRect(rect).Position;
}
}

83
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -597,21 +597,82 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textBounds = textLine.GetTextBounds(0, 20);
Assert.Equal(1, textBounds.Count);
Assert.Equal(2, textBounds.Count);
Assert.Equal(144.0234375, textBounds[0].Rectangle.Width);
Assert.Equal(144.0234375, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 30);
Assert.Equal(1, textBounds.Count);
Assert.Equal(3, textBounds.Count);
Assert.Equal(216.03515625, textBounds[0].Rectangle.Width);
Assert.Equal(216.03515625, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 40);
Assert.Equal(1, textBounds.Count);
Assert.Equal(4, textBounds.Count);
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
}
}
[Fact]
public void Should_GetTextRange()
{
var text = "שדגככעיחדגכAישדגשדגחייטYDASYWIWחיחלדשSAטויליHUHIUHUIDWKLאא'ק'קחליק/'וקןגגגלךשף'/קפוכדגכשדגשיח'/קטאגשד";
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new SingleBufferTextSource(text, defaultProperties);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var textRuns = textLine.TextRuns.Cast<ShapedTextCharacters>().ToList();
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds[0].Rectangle.Width);
var lineWidth = textLine.WidthIncludingTrailingWhitespace;
var textBounds = textLine.GetTextBounds(0, text.Length);
TextBounds lastBounds = null;
var runBounds = textBounds.SelectMany(x => x.TextRunBounds).ToList();
Assert.Equal(textRuns.Count, runBounds.Count);
for (var i = 0; i < textRuns.Count; i++)
{
var run = textRuns[i];
var bounds = runBounds[i];
Assert.Equal(run.Text.Start, bounds.TextSourceCharacterIndex);
Assert.Equal(run, bounds.TextRun);
Assert.Equal(run.Size.Width, bounds.Rectangle.Width);
}
for (var i = 0; i < textBounds.Count; i++)
{
var currentBounds = textBounds[i];
if (lastBounds != null)
{
Assert.Equal(lastBounds.Rectangle.Right, currentBounds.Rectangle.Left);
}
var sumOfRunWidth = currentBounds.TextRunBounds.Sum(x => x.Rectangle.Width);
Assert.Equal(sumOfRunWidth, currentBounds.Rectangle.Width);
lastBounds = currentBounds;
}
var sumOfBoundsWidth = textBounds.Sum(x => x.Rectangle.Width);
Assert.Equal(lineWidth, sumOfBoundsWidth);
}
}
@ -779,7 +840,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textBounds = textLine.GetTextBounds(0, text.Length * 3 + 3);
Assert.Equal(1, textBounds.Count);
Assert.Equal(6, textBounds.Count);
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 1);
@ -789,8 +850,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textBounds = textLine.GetTextBounds(0, firstRun.Text.Length + 1);
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds[0].Rectangle.Width);
Assert.Equal(2, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(1, firstRun.Text.Length);
@ -799,8 +860,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
textBounds = textLine.GetTextBounds(1, firstRun.Text.Length + 1);
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds[0].Rectangle.Width);
Assert.Equal(2, textBounds.Count);
Assert.Equal(firstRun.Size.Width + 14, textBounds.Sum(x => x.Rectangle.Width));
}
}

Loading…
Cancel
Save