Browse Source

Merge branch 'master' into fix/ProgressBarShouldListenToTrackSize

pull/8498/head
Max Katz 4 years ago
committed by GitHub
parent
commit
deb104a789
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 44
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  2. 2
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  3. 109
      src/Avalonia.Base/Media/GlyphRun.cs
  4. 2
      src/Avalonia.Base/Media/TextDecoration.cs
  5. 51
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  6. 433
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  7. 9
      src/Avalonia.Controls/Documents/LineBreak.cs
  8. 119
      src/Avalonia.Controls/RichTextBlock.cs
  9. 7
      src/Avalonia.Controls/TextBlock.cs
  10. 285
      src/Avalonia.Controls/TextBox.cs
  11. 4
      src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs
  12. 1
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  13. 14
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  14. 2
      src/Avalonia.OpenGL/Egl/EglContext.cs
  15. 4
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  16. 225
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  17. 44
      src/Avalonia.OpenGL/GlBasicInfoInterface.cs
  18. 103
      src/Avalonia.OpenGL/GlEntryPointAttribute.cs
  19. 494
      src/Avalonia.OpenGL/GlInterface.cs
  20. 79
      src/Avalonia.OpenGL/GlInterfaceBase.cs
  21. 10
      src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml
  22. 1
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  23. 1
      src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
  24. 14
      src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml
  25. 2
      src/Avalonia.X11/Avalonia.X11.csproj
  26. 111
      src/Avalonia.X11/Glx/Glx.cs
  27. 2
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
  28. 2
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  29. 24
      src/Shared/SourceGeneratorAttributes.cs
  30. 28
      src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs
  31. 2
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  32. 11
      src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
  33. 52
      src/iOS/Avalonia.iOS/LayerFbo.cs
  34. 338
      src/tools/DevGenerators/GetProcAddressInitialization.cs
  35. 31
      src/tools/DevGenerators/Helpers.cs
  36. BIN
      tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf
  37. 4
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  38. 8
      tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs
  39. 129
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  40. 17
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

44
samples/ControlCatalog/Pages/OpenGlPage.xaml.cs

@ -90,7 +90,6 @@ namespace ControlCatalog.Pages
private int _vertexBufferObject;
private int _indexBufferObject;
private int _vertexArrayObject;
private GlExtrasInterface _glExt;
private string GetShader(bool fragment, string shader)
{
@ -258,7 +257,6 @@ namespace ControlCatalog.Pages
protected unsafe override void OnOpenGlInit(GlInterface GL, int fb)
{
CheckError(GL);
_glExt = new GlExtrasInterface(GL);
Info = $"Renderer: {GL.GetString(GL_RENDERER)} Version: {GL.GetString(GL_VERSION)}";
@ -298,8 +296,8 @@ namespace ControlCatalog.Pages
GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata),
GL_STATIC_DRAW);
CheckError(GL);
_vertexArrayObject = _glExt.GenVertexArray();
_glExt.BindVertexArray(_vertexArrayObject);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
CheckError(GL);
GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT,
0, vertexSize, IntPtr.Zero);
@ -316,12 +314,13 @@ namespace ControlCatalog.Pages
// Unbind everything
GL.BindBuffer(GL_ARRAY_BUFFER, 0);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
_glExt.BindVertexArray(0);
GL.BindVertexArray(0);
GL.UseProgram(0);
// Delete all resources.
GL.DeleteBuffers(2, new[] { _vertexBufferObject, _indexBufferObject });
_glExt.DeleteVertexArrays(1, new[] { _vertexArrayObject });
GL.DeleteBuffer(_vertexBufferObject);
GL.DeleteBuffer(_indexBufferObject);
GL.DeleteVertexArray(_vertexArrayObject);
GL.DeleteProgram(_shaderProgram);
GL.DeleteShader(_fragmentShader);
GL.DeleteShader(_vertexShader);
@ -338,7 +337,7 @@ namespace ControlCatalog.Pages
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject);
_glExt.BindVertexArray(_vertexArrayObject);
GL.BindVertexArray(_vertexArrayObject);
GL.UseProgram(_shaderProgram);
CheckError(GL);
var projection =
@ -369,34 +368,5 @@ namespace ControlCatalog.Pages
if (_disco > 0.01)
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
}
class GlExtrasInterface : GlInterfaceBase<GlInterface.GlContextInfo>
{
public GlExtrasInterface(GlInterface gl) : base(gl.GetProcAddress, gl.ContextInfo)
{
}
public delegate void GlDeleteVertexArrays(int count, int[] buffers);
[GlMinVersionEntryPoint("glDeleteVertexArrays", 3,0)]
[GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")]
public GlDeleteVertexArrays DeleteVertexArrays { get; }
public delegate void GlBindVertexArray(int array);
[GlMinVersionEntryPoint("glBindVertexArray", 3,0)]
[GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")]
public GlBindVertexArray BindVertexArray { get; }
public delegate void GlGenVertexArrays(int n, int[] rv);
[GlMinVersionEntryPoint("glGenVertexArrays",3,0)]
[GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")]
public GlGenVertexArrays GenVertexArrays { get; }
public int GenVertexArray()
{
var rv = new int[1];
GenVertexArrays(1, rv);
return rv[0];
}
}
}
}

2
samples/ControlCatalog/Pages/TextBlockPage.xaml

@ -118,7 +118,7 @@
</StackPanel>
</Border>
<Border>
<RichTextBlock Margin="10" TextWrapping="Wrap">
<RichTextBlock SelectionBrush="LightBlue" IsTextSelectionEnabled="True" Margin="10" TextWrapping="Wrap">
This <Span FontWeight="Bold">is</Span> a
<Span Background="Silver" Foreground="Maroon">TextBlock</Span>
with <Span TextDecorations="Underline">several</Span>

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

@ -445,7 +445,7 @@ namespace Avalonia.Media
/// </returns>
public int FindGlyphIndex(int characterIndex)
{
if (GlyphClusters == null)
if (GlyphClusters == null || GlyphClusters.Count == 0)
{
return characterIndex;
}
@ -614,17 +614,29 @@ namespace Avalonia.Media
private GlyphRunMetrics CreateGlyphRunMetrics()
{
var firstCluster = 0;
var lastCluster = Characters.Length - 1;
if (!IsLeftToRight)
{
var cluster = firstCluster;
firstCluster = lastCluster;
lastCluster = cluster;
}
if (GlyphClusters != null && GlyphClusters.Count > 0)
{
var firstCluster = GlyphClusters[0];
firstCluster = GlyphClusters[0];
lastCluster = GlyphClusters[GlyphClusters.Count - 1];
_offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster);
}
var isReversed = firstCluster > lastCluster;
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
var widthIncludingTrailingWhitespace = 0d;
var trailingWhitespaceLength = GetTrailingWhitespaceLength(out var newLineLength, out var glyphCount);
var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount);
for (var index = 0; index < GlyphIndices.Count; index++)
{
@ -635,16 +647,16 @@ namespace Avalonia.Media
var width = widthIncludingTrailingWhitespace;
if (IsLeftToRight)
if (isReversed)
{
for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++)
for (var index = 0; index < glyphCount; index++)
{
width -= GetGlyphAdvance(index, out _);
}
}
else
{
for (var index = 0; index < glyphCount; index++)
for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++)
{
width -= GetGlyphAdvance(index, out _);
}
@ -654,16 +666,15 @@ namespace Avalonia.Media
height);
}
private int GetTrailingWhitespaceLength(out int newLineLength, out int glyphCount)
{
glyphCount = 0;
newLineLength = 0;
if (Characters.IsEmpty)
private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount)
{
if (isReversed)
{
return 0;
return GetTralingWhitespaceLengthRightToLeft(out newLineLength, out glyphCount);
}
glyphCount = 0;
newLineLength = 0;
var trailingWhitespaceLength = 0;
if (GlyphClusters == null)
@ -732,6 +743,78 @@ namespace Avalonia.Media
return trailingWhitespaceLength;
}
private int GetTralingWhitespaceLengthRightToLeft(out int newLineLength, out int glyphCount)
{
glyphCount = 0;
newLineLength = 0;
var trailingWhitespaceLength = 0;
if (GlyphClusters == null)
{
for (var i = 0; i < Characters.Length;)
{
var codepoint = Codepoint.ReadAt(_characters, i, out var count);
if (!codepoint.IsWhiteSpace)
{
break;
}
if (codepoint.IsBreakChar)
{
newLineLength++;
}
trailingWhitespaceLength++;
i += count;
glyphCount++;
}
}
else
{
for (var i = 0; i < GlyphClusters.Count; i++)
{
var currentCluster = GlyphClusters[i];
var characterIndex = Math.Max(0, currentCluster - _characters.BufferOffset);
var codepoint = Codepoint.ReadAt(_characters, characterIndex, out _);
if (!codepoint.IsWhiteSpace)
{
break;
}
var clusterLength = 1;
while (i - 1 >= 0)
{
var nextCluster = GlyphClusters[i - 1];
if (currentCluster == nextCluster)
{
clusterLength++;
i--;
continue;
}
break;
}
if (codepoint.IsBreakChar)
{
newLineLength += clusterLength;
}
trailingWhitespaceLength += clusterLength;
glyphCount++;
}
}
return trailingWhitespaceLength;
}
private void Set<T>(ref T field, T value)
{
_glyphRunImpl?.Dispose();

2
src/Avalonia.Base/Media/TextDecoration.cs

@ -209,7 +209,7 @@ namespace Avalonia.Media
var pen = new Pen(Stroke ?? defaultBrush, thickness,
new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap);
drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Size.Width, 0));
drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Metrics.Width, 0));
}
}
}

51
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -63,7 +63,7 @@ namespace Avalonia.Media.TextFormatting
MaxHeight = maxHeight;
MaxLines = maxLines;
MaxLines = maxLines;
TextLines = CreateTextLines();
}
@ -80,7 +80,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="maxLines">The maximum number of text lines.</param>
public TextLayout(
ITextSource textSource,
TextParagraphProperties paragraphProperties,
TextParagraphProperties paragraphProperties,
TextTrimming? textTrimming = null,
double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity,
@ -178,24 +178,18 @@ namespace Avalonia.Media.TextFormatting
return new Rect();
}
if (textPosition < 0 || textPosition >= _textSourceLength)
if (textPosition < 0)
{
var lastLine = TextLines[TextLines.Count - 1];
var lineX = lastLine.Width;
var lineY = Bounds.Bottom - lastLine.Height;
return new Rect(lineX, lineY, 0, lastLine.Height);
textPosition = _textSourceLength;
}
var currentY = 0.0;
foreach (var textLine in TextLines)
{
var end = textLine.FirstTextSourceIndex + textLine.Length - 1;
var end = textLine.FirstTextSourceIndex + textLine.Length;
if (end < textPosition)
if (end <= textPosition && end < _textSourceLength)
{
currentY += textLine.Height;
@ -224,7 +218,7 @@ namespace Avalonia.Media.TextFormatting
}
var result = new List<Rect>(TextLines.Count);
var currentY = 0d;
foreach (var textLine in TextLines)
@ -239,7 +233,7 @@ namespace Avalonia.Media.TextFormatting
var textBounds = textLine.GetTextBounds(start, length);
if(textBounds.Count > 0)
if (textBounds.Count > 0)
{
foreach (var bounds in textBounds)
{
@ -262,7 +256,7 @@ namespace Avalonia.Media.TextFormatting
}
}
if(textLine.FirstTextSourceIndex + textLine.Length >= start + length)
if (textLine.FirstTextSourceIndex + textLine.Length >= start + length)
{
break;
}
@ -305,7 +299,7 @@ namespace Avalonia.Media.TextFormatting
return GetHitTestResult(currentLine, characterHit, point);
}
public int GetLineIndexFromCharacterIndex(int charIndex, bool trailingEdge)
{
if (charIndex < 0)
@ -327,7 +321,7 @@ namespace Avalonia.Media.TextFormatting
continue;
}
if (charIndex >= textLine.FirstTextSourceIndex &&
if (charIndex >= textLine.FirstTextSourceIndex &&
charIndex <= textLine.FirstTextSourceIndex + textLine.Length - (trailingEdge ? 0 : 1))
{
return index;
@ -398,7 +392,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="left">The current left.</param>
/// <param name="width">The current width.</param>
/// <param name="height">The current height.</param>
private static void UpdateBounds(TextLine textLine,ref double left, ref double width, ref double height)
private static void UpdateBounds(TextLine textLine, ref double left, ref double width, ref double height)
{
var lineWidth = textLine.WidthIncludingTrailingWhitespace;
@ -421,7 +415,7 @@ namespace Avalonia.Media.TextFormatting
{
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);
Bounds = new Rect(0,0,0, textLine.Height);
Bounds = new Rect(0, 0, 0, textLine.Height);
return new List<TextLine> { textLine };
}
@ -439,9 +433,9 @@ namespace Avalonia.Media.TextFormatting
var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth,
_paragraphProperties, previousLine?.TextLineBreak);
if(textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
if (textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph)
{
if(previousLine != null && previousLine.NewLineLength > 0)
if (previousLine != null && previousLine.NewLineLength > 0)
{
var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, _paragraphProperties);
@ -454,7 +448,7 @@ namespace Avalonia.Media.TextFormatting
}
_textSourceLength += textLine.Length;
//Fulfill max height constraint
if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight)
{
@ -485,12 +479,17 @@ namespace Avalonia.Media.TextFormatting
//Fulfill max lines constraint
if (MaxLines > 0 && textLines.Count >= MaxLines)
{
if(textLine.TextLineBreak is TextLineBreak lineBreak && lineBreak.RemainingRuns != null)
{
textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width));
}
break;
}
}
//Make sure the TextLayout always contains at least on empty line
if(textLines.Count == 0)
if (textLines.Count == 0)
{
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties);
@ -501,7 +500,7 @@ namespace Avalonia.Media.TextFormatting
Bounds = new Rect(left, 0, width, height);
if(_paragraphProperties.TextAlignment == TextAlignment.Justify)
if (_paragraphProperties.TextAlignment == TextAlignment.Justify)
{
var whitespaceWidth = 0d;
@ -509,7 +508,7 @@ namespace Avalonia.Media.TextFormatting
{
var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace;
if(lineWhitespaceWidth > whitespaceWidth)
if (lineWhitespaceWidth > whitespaceWidth)
{
whitespaceWidth = lineWhitespaceWidth;
}
@ -517,7 +516,7 @@ namespace Avalonia.Media.TextFormatting
var justificationWidth = width - whitespaceWidth;
if(justificationWidth > 0)
if (justificationWidth > 0)
{
var justificationProperties = new InterWordJustification(justificationWidth);

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

@ -166,58 +166,74 @@ namespace Avalonia.Media.TextFormatting
if (distance <= 0)
{
// hit happens before the line, return the first position
var firstRun = _textRuns[0];
if (firstRun is ShapedTextCharacters shapedTextCharacters)
{
return shapedTextCharacters.GlyphRun.GetCharacterHitFromDistance(distance, out _);
}
return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0);
}
return _resolvedFlowDirection == FlowDirection.LeftToRight ?
new CharacterHit(FirstTextSourceIndex) :
new CharacterHit(FirstTextSourceIndex + Length);
if (distance > WidthIncludingTrailingWhitespace)
{
var lastRun = _textRuns[_textRuns.Count - 1];
return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.TextSourceLength, lastRun.Size.Width);
}
// process hit that happens within the line
var characterHit = new CharacterHit();
var currentPosition = FirstTextSourceIndex;
var currentDistance = 0.0;
foreach (var currentRun in _textRuns)
{
switch (currentRun)
if (currentDistance + currentRun.Size.Width < distance)
{
case ShapedTextCharacters shapedRun:
{
characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
currentDistance += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
var offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
continue;
}
characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance);
break;
}
default:
break;
}
return characterHit;
}
private static CharacterHit GetRunCharacterHit(DrawableTextRun run, int currentPosition, double distance)
{
CharacterHit characterHit;
switch (run)
{
case ShapedTextCharacters shapedRun:
{
characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
var offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
if (!shapedRun.GlyphRun.IsLeftToRight)
{
if (distance < currentRun.Size.Width / 2)
{
characterHit = new CharacterHit(currentPosition);
}
else
{
characterHit = new CharacterHit(currentPosition, currentRun.TextSourceLength);
}
break;
offset = Math.Max(0, offset - shapedRun.Text.End);
}
}
if (distance <= currentRun.Size.Width)
{
break;
}
characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
distance -= currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
break;
}
default:
{
if (distance < run.Size.Width / 2)
{
characterHit = new CharacterHit(currentPosition);
}
else
{
characterHit = new CharacterHit(currentPosition, run.TextSourceLength);
}
break;
}
}
return characterHit;
@ -226,136 +242,122 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/>
public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
{
var isTrailingHit = characterHit.TrailingLength > 0;
var flowDirection = _paragraphProperties.FlowDirection;
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var currentDistance = Start;
var currentPosition = FirstTextSourceIndex;
var remainingLength = characterIndex - FirstTextSourceIndex;
GlyphRun? lastRun = null;
var currentDistance = Start;
for (var index = 0; index < _textRuns.Count; index++)
if (flowDirection == FlowDirection.LeftToRight)
{
for (var index = 0; index < _textRuns.Count; index++)
{
var currentRun = _textRuns[index];
if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength,
flowDirection, out var distance, out _))
{
return currentDistance + distance;
}
//No hit hit found so we add the full width
currentDistance += currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
remainingLength -= currentRun.TextSourceLength;
}
}
else
{
var textRun = _textRuns[index];
currentDistance += WidthIncludingTrailingWhitespace;
switch (textRun)
for (var index = _textRuns.Count - 1; index >= 0; index--)
{
case ShapedTextCharacters shapedTextCharacters:
{
var currentRun = shapedTextCharacters.GlyphRun;
var currentRun = _textRuns[index];
if (lastRun != null)
{
if (!lastRun.IsLeftToRight && currentRun.IsLeftToRight &&
currentRun.Characters.Start == characterHit.FirstCharacterIndex &&
characterHit.TrailingLength == 0)
{
return currentDistance;
}
}
if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength,
flowDirection, out var distance, out var currentGlyphRun))
{
if (currentGlyphRun != null)
{
distance = currentGlyphRun.Size.Width - distance;
}
//Look for a hit in within the current run
if (currentPosition + remainingLength <= currentPosition + textRun.Text.Length)
{
characterHit = new CharacterHit(textRun.Text.Start + remainingLength);
return currentDistance - distance;
}
var distance = currentRun.GetDistanceFromCharacterHit(characterHit);
//No hit hit found so we add the full width
currentDistance -= currentRun.Size.Width;
currentPosition += currentRun.TextSourceLength;
remainingLength -= currentRun.TextSourceLength;
}
}
return currentDistance + distance;
}
return currentDistance;
}
//Look at the left and right edge of the current run
if (currentRun.IsLeftToRight)
{
if (_resolvedFlowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight))
{
if (characterIndex <= currentPosition)
{
return currentDistance;
}
}
else
{
if (characterIndex == currentPosition)
{
return currentDistance;
}
}
private static bool TryGetDistanceFromCharacterHit(
DrawableTextRun currentRun,
CharacterHit characterHit,
int currentPosition,
int remainingLength,
FlowDirection flowDirection,
out double distance,
out GlyphRun? currentGlyphRun)
{
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var isTrailingHit = characterHit.TrailingLength > 0;
if (characterIndex == currentPosition + textRun.Text.Length && isTrailingHit)
{
return currentDistance + currentRun.Size.Width;
}
}
else
{
if (characterIndex == currentPosition)
{
return currentDistance + currentRun.Size.Width;
}
distance = 0;
currentGlyphRun = null;
var nextRun = index + 1 < _textRuns.Count ?
_textRuns[index + 1] as ShapedTextCharacters :
null;
switch (currentRun)
{
case ShapedTextCharacters shapedTextCharacters:
{
currentGlyphRun = shapedTextCharacters.GlyphRun;
if (nextRun != null)
{
if (nextRun.ShapedBuffer.IsLeftToRight)
{
if (characterIndex == currentPosition + textRun.Text.Length)
{
return currentDistance;
}
}
else
{
if (currentPosition + nextRun.Text.Length == characterIndex)
{
return currentDistance;
}
}
}
else
{
if (characterIndex > currentPosition + textRun.Text.Length)
{
return currentDistance;
}
}
}
if (currentPosition + remainingLength <= currentPosition + currentRun.Text.Length)
{
characterHit = new CharacterHit(currentRun.Text.Start + remainingLength);
lastRun = currentRun;
distance = currentGlyphRun.GetDistanceFromCharacterHit(characterHit);
break;
return true;
}
default:
if (currentPosition + remainingLength == currentPosition + currentRun.Text.Length && isTrailingHit)
{
if (characterIndex == currentPosition)
if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft)
{
return currentDistance;
distance = currentGlyphRun.Size.Width;
}
if (characterIndex == currentPosition + textRun.TextSourceLength)
{
return currentDistance + textRun.Size.Width;
}
return true;
}
break;
break;
}
default:
{
if (characterIndex == currentPosition)
{
return true;
}
}
//No hit hit found so we add the full width
currentDistance += textRun.Size.Width;
currentPosition += textRun.TextSourceLength;
remainingLength -= textRun.TextSourceLength;
if (characterIndex == currentPosition + currentRun.TextSourceLength)
{
distance = currentRun.Size.Width;
if (remainingLength <= 0)
{
break;
}
return true;
}
break;
}
}
return currentDistance;
return false;
}
/// <inheritdoc/>
@ -460,20 +462,33 @@ namespace Avalonia.Media.TextFormatting
var startIndex = currentRun.Text.Start + offset;
var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex + remainingLength) :
new CharacterHit(startIndex));
double startOffset;
double endOffset;
endX += endOffset;
if (currentShapedRun.ShapedBuffer.IsLeftToRight)
{
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex) :
new CharacterHit(startIndex + remainingLength));
if (currentPosition < startIndex)
{
startOffset = endOffset;
}
else
{
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
}
startX += startOffset;
endX += endOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
@ -504,47 +519,40 @@ namespace Avalonia.Media.TextFormatting
}
//Lines that only contain a linebreak need to be covered here
if(characterLength == 0)
if (characterLength == 0)
{
characterLength = NewLineLength;
}
var runwidth = endX - startX;
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runwidth, Height), currentPosition, characterLength, currentRun);
var runWidth = endX - startX;
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{
currentRect = currentRect.WithWidth(currentWidth + runwidth);
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
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 }));
}
}
currentWidth += runwidth;
currentWidth += runWidth;
currentPosition += characterLength;
if (currentDirection == FlowDirection.LeftToRight)
{
if (currentPosition > characterIndex)
{
break;
}
}
else
if (currentPosition > characterIndex)
{
if (currentPosition <= firstTextSourceIndex)
{
break;
}
break;
}
startX = endX;
@ -571,7 +579,7 @@ namespace Avalonia.Media.TextFormatting
var currentPosition = FirstTextSourceIndex;
var remainingLength = textLength;
var startX = Start + WidthIncludingTrailingWhitespace;
var startX = WidthIncludingTrailingWhitespace;
double currentWidth = 0;
var currentRect = Rect.Empty;
@ -582,7 +590,7 @@ namespace Avalonia.Media.TextFormatting
continue;
}
if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
if (currentPosition + currentRun.TextSourceLength < firstTextSourceIndex)
{
startX -= currentRun.Size.Width;
@ -601,20 +609,31 @@ namespace Avalonia.Media.TextFormatting
currentPosition += offset;
var startIndex = currentRun.Text.Start + offset;
double startOffset;
double endOffset;
var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex + remainingLength) :
new CharacterHit(startIndex));
if (currentShapedRun.ShapedBuffer.IsLeftToRight)
{
if (currentPosition < startIndex)
{
startOffset = endOffset = 0;
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
endX += endOffset - currentShapedRun.Size.Width;
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
}
}
else
{
endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
currentShapedRun.ShapedBuffer.IsLeftToRight ?
new CharacterHit(startIndex) :
new CharacterHit(startIndex + remainingLength));
startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
startX += startOffset - currentShapedRun.Size.Width;
startX -= currentRun.Size.Width - startOffset;
endX -= currentRun.Size.Width - endOffset;
var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
@ -652,41 +671,35 @@ namespace Avalonia.Media.TextFormatting
}
var runWidth = endX - startX;
var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
if(!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX))
{
currentRect = currentRect.WithWidth(currentWidth + runWidth);
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 }));
}
}
currentWidth += runWidth;
currentPosition += characterLength;
if (currentDirection == FlowDirection.LeftToRight)
{
if (currentPosition > characterIndex)
{
break;
}
}
else
if (currentPosition > characterIndex)
{
if (currentPosition <= firstTextSourceIndex)
{
break;
}
break;
}
lastDirection = currentDirection;
@ -698,6 +711,8 @@ namespace Avalonia.Media.TextFormatting
}
}
result.Reverse();
return result;
}
@ -1302,8 +1317,14 @@ namespace Avalonia.Media.TextFormatting
switch (textAlignment)
{
case TextAlignment.Center:
return Math.Max(0, (_paragraphWidth - width) / 2);
var start = (_paragraphWidth - width) / 2;
if(paragraphFlowDirection == FlowDirection.RightToLeft)
{
start -= (widthIncludingTrailingWhitespace - width);
}
return Math.Max(0, start);
case TextAlignment.Right:
return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace);

9
src/Avalonia.Controls/Documents/LineBreak.cs

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.LogicalTree;
using Avalonia.Media.TextFormatting;
using Avalonia.Metadata;
@ -22,7 +21,13 @@ namespace Avalonia.Controls.Documents
internal override void BuildTextRun(IList<TextRun> textRuns)
{
textRuns.Add(new TextEndOfLine());
var text = Environment.NewLine.AsMemory();
var textRunProperties = CreateTextRunProperties();
var textCharacters = new TextCharacters(text, textRunProperties);
textRuns.Add(textCharacters);
}
internal override void AppendText(StringBuilder stringBuilder)

119
src/Avalonia.Controls/RichTextBlock.cs

@ -41,9 +41,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
AvaloniaProperty.Register<RichTextBlock, IBrush?>(nameof(SelectionBrush), Brushes.Blue);
public static readonly StyledProperty<IBrush?> SelectionForegroundBrushProperty =
AvaloniaProperty.Register<RichTextBlock, IBrush?>(nameof(SelectionForegroundBrush));
/// <summary>
/// Defines the <see cref="Inlines"/> property.
/// </summary>
@ -63,12 +60,13 @@ namespace Avalonia.Controls
private bool _canCopy;
private int _selectionStart;
private int _selectionEnd;
private int _wordSelectionStart = -1;
static RichTextBlock()
{
FocusableProperty.OverrideDefaultValue(typeof(RichTextBlock), true);
AffectsRender<RichTextBlock>(SelectionStartProperty, SelectionEndProperty, SelectionForegroundBrushProperty, SelectionBrushProperty);
AffectsRender<RichTextBlock>(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty, IsTextSelectionEnabledProperty);
}
public RichTextBlock()
@ -89,15 +87,6 @@ namespace Avalonia.Controls
set => SetValue(SelectionBrushProperty, value);
}
/// <summary>
/// Gets or sets a value that defines the brush used for selected text.
/// </summary>
public IBrush? SelectionForegroundBrush
{
get => GetValue(SelectionForegroundBrushProperty);
set => SetValue(SelectionForegroundBrushProperty, value);
}
/// <summary>
/// Gets or sets a character index for the beginning of the current selection.
/// </summary>
@ -200,7 +189,7 @@ namespace Avalonia.Controls
}
}
public override void Render(DrawingContext context)
protected override void RenderTextLayout(DrawingContext context, Point origin)
{
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
@ -215,13 +204,16 @@ namespace Avalonia.Controls
var rects = TextLayout.HitTestTextRange(start, length);
foreach (var rect in rects)
using (context.PushPostTransform(Matrix.CreateTranslation(origin)))
{
context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1));
foreach (var rect in rects)
{
context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1));
}
}
}
base.Render(context);
base.RenderTextLayout(context, origin);
}
/// <summary>
@ -297,8 +289,9 @@ namespace Avalonia.Controls
/// <returns>A <see cref="TextLayout"/> object.</returns>
protected override TextLayout CreateTextLayout(string? text)
{
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var defaultProperties = new GenericTextRunProperties(
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
typeface,
FontSize,
TextDecorations,
Foreground);
@ -345,6 +338,8 @@ namespace Avalonia.Controls
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
var handled = false;
var modifiers = e.KeyModifiers;
var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>();
@ -363,6 +358,8 @@ namespace Avalonia.Controls
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
if (!IsTextSelectionEnabled)
{
return;
@ -373,7 +370,9 @@ namespace Avalonia.Controls
if (text != null && clickInfo.Properties.IsLeftButtonPressed)
{
var point = e.GetPosition(this);
var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
@ -382,8 +381,6 @@ namespace Avalonia.Controls
var hit = TextLayout.HitTestPoint(point);
var index = hit.TextPosition;
SelectionStart = SelectionEnd = index;
#pragma warning disable CS0618 // Type or member is obsolete
switch (e.ClickCount)
#pragma warning restore CS0618 // Type or member is obsolete
@ -391,12 +388,34 @@ namespace Avalonia.Controls
case 1:
if (clickToSelect)
{
SelectionStart = Math.Min(oldIndex, index);
SelectionEnd = Math.Max(oldIndex, index);
if (_wordSelectionStart >= 0)
{
var previousWord = StringUtils.PreviousWord(text, index);
if (index > _wordSelectionStart)
{
SelectionEnd = StringUtils.NextWord(text, index);
}
if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
{
SelectionStart = previousWord;
}
}
else
{
SelectionStart = Math.Min(oldIndex, index);
SelectionEnd = Math.Max(oldIndex, index);
}
}
else
{
SelectionStart = SelectionEnd = index;
if (_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
{
SelectionStart = SelectionEnd = index;
_wordSelectionStart = -1;
}
}
break;
@ -406,9 +425,13 @@ namespace Avalonia.Controls
SelectionStart = StringUtils.PreviousWord(text, index);
}
_wordSelectionStart = SelectionStart;
SelectionEnd = StringUtils.NextWord(text, index);
break;
case 3:
_wordSelectionStart = -1;
SelectAll();
break;
}
@ -420,6 +443,8 @@ namespace Avalonia.Controls
protected override void OnPointerMoved(PointerEventArgs e)
{
base.OnPointerMoved(e);
if (!IsTextSelectionEnabled)
{
return;
@ -428,20 +453,49 @@ namespace Avalonia.Controls
// selection should not change during pointer move if the user right clicks
if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
var point = e.GetPosition(this);
var text = Text;
var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
point = new Point(
MathUtilities.Clamp(point.X, 0, Math.Max(Bounds.Width - 1, 0)),
MathUtilities.Clamp(point.Y, 0, Math.Max(Bounds.Height - 1, 0)));
MathUtilities.Clamp(point.X, 0, Math.Max(TextLayout.Bounds.Width, 0)),
MathUtilities.Clamp(point.Y, 0, Math.Max(TextLayout.Bounds.Width, 0)));
var hit = TextLayout.HitTestPoint(point);
var textPosition = hit.TextPosition;
if (text != null && _wordSelectionStart >= 0)
{
var distance = textPosition - _wordSelectionStart;
if (distance <= 0)
{
SelectionStart = StringUtils.PreviousWord(text, textPosition);
}
if (distance >= 0)
{
if (SelectionStart != _wordSelectionStart)
{
SelectionStart = _wordSelectionStart;
}
SelectionEnd = StringUtils.NextWord(text, textPosition);
}
}
else
{
SelectionEnd = textPosition;
}
SelectionEnd = hit.TextPosition;
}
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (!IsTextSelectionEnabled)
{
return;
@ -454,7 +508,9 @@ namespace Avalonia.Controls
if (e.InitialPressMouseButton == MouseButton.Right)
{
var point = e.GetPosition(this);
var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
var hit = TextLayout.HitTestPoint(point);
@ -487,11 +543,6 @@ namespace Avalonia.Controls
InvalidateTextLayout();
break;
}
case nameof(TextProperty):
{
InvalidateTextLayout();
break;
}
}
}

7
src/Avalonia.Controls/TextBlock.cs

@ -505,7 +505,12 @@ namespace Avalonia.Controls
}
}
TextLayout.Draw(context, new Point(padding.Left, top));
RenderTextLayout(context, new Point(padding.Left, top));
}
protected virtual void RenderTextLayout(DrawingContext context, Point origin)
{
TextLayout.Draw(context, origin);
}
void IAddChild<string>.AddChild(string text)

285
src/Avalonia.Controls/TextBox.cs

@ -53,7 +53,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<char> PasswordCharProperty =
AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar));
public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionBrush));
@ -80,7 +80,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
public static readonly DirectProperty<TextBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
o => o.Text,
@ -105,7 +105,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<TextBox>();
/// <summary>
/// Defines see <see cref="TextPresenter.LineHeight"/> property.
/// </summary>
@ -202,6 +202,7 @@ namespace Avalonia.Controls
private string _newLine = Environment.NewLine;
private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
private int _wordSelectionStart = -1;
private int _selectedTextChangesMadeSinceLastUndoSnapshot;
private bool _hasDoneSnapshotOnce;
private const int _maxCharsBeforeUndoSnapshot = 7;
@ -275,7 +276,7 @@ namespace Avalonia.Controls
get => GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value);
}
public char PasswordChar
{
get => GetValue(PasswordCharProperty);
@ -307,7 +308,7 @@ namespace Avalonia.Controls
{
value = CoerceCaretIndex(value);
var changed = SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
if (changed)
{
UpdateCommandStates();
@ -327,12 +328,12 @@ namespace Avalonia.Controls
{
value = CoerceCaretIndex(value);
var changed = SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
if (changed)
{
UpdateCommandStates();
}
if (SelectionStart == value && CaretIndex != value)
{
CaretIndex = value;
@ -351,7 +352,7 @@ namespace Avalonia.Controls
get => GetValue(MaxLinesProperty);
set => SetValue(MaxLinesProperty, value);
}
/// <summary>
/// Gets or sets the line height.
/// </summary>
@ -370,7 +371,7 @@ namespace Avalonia.Controls
var caretIndex = CaretIndex;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(selectionStart, value);
SelectionEnd = CoerceCaretIndex(selectionEnd, value);
@ -567,7 +568,7 @@ namespace Avalonia.Controls
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
_imClient.SetPresenter(_presenter, this);
if (IsFocused)
{
_presenter?.ShowCaret();
@ -577,7 +578,7 @@ namespace Avalonia.Controls
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (IsFocused)
{
_presenter?.ShowCaret();
@ -587,7 +588,7 @@ namespace Avalonia.Controls
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_imClient.SetPresenter(null, null);
}
@ -637,7 +638,7 @@ namespace Avalonia.Controls
}
UpdateCommandStates();
_imClient.SetPresenter(_presenter, this);
_presenter?.ShowCaret();
@ -657,7 +658,7 @@ namespace Avalonia.Controls
UpdateCommandStates();
_presenter?.HideCaret();
_imClient.SetPresenter(null, null);
}
@ -700,14 +701,14 @@ namespace Avalonia.Controls
if (grapheme.FirstCodepoint.IsBreakChar)
{
if(lineCount + 1 > MaxLines)
if (lineCount + 1 > MaxLines)
{
break;
}
else
{
lineCount++;
}
}
}
length += grapheme.Text.Length;
@ -736,7 +737,7 @@ namespace Avalonia.Controls
text = Text ?? string.Empty;
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
ClearSelection();
if (IsUndoEnabled)
{
_undoRedoHelper.DiscardRedo();
@ -746,7 +747,7 @@ namespace Avalonia.Controls
{
RaisePropertyChanged(TextProperty, oldText, _text);
}
CaretIndex = caretIndex + input.Length;
}
}
@ -828,7 +829,7 @@ namespace Avalonia.Controls
{
return;
}
var text = Text ?? string.Empty;
var caretIndex = CaretIndex;
var movement = false;
@ -985,87 +986,87 @@ namespace Avalonia.Controls
break;
case Key.Up:
{
selection = DetectSelection();
_presenter.MoveCaretVertical(LogicalDirection.Backward);
if (caretIndex != _presenter.CaretIndex)
{
movement = true;
}
selection = DetectSelection();
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
}
else
{
CaretIndex = _presenter.CaretIndex;
_presenter.MoveCaretVertical(LogicalDirection.Backward);
if (caretIndex != _presenter.CaretIndex)
{
movement = true;
}
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
}
else
{
CaretIndex = _presenter.CaretIndex;
}
break;
}
break;
}
case Key.Down:
{
selection = DetectSelection();
_presenter.MoveCaretVertical();
if (caretIndex != _presenter.CaretIndex)
{
movement = true;
}
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
}
else
{
CaretIndex = _presenter.CaretIndex;
selection = DetectSelection();
_presenter.MoveCaretVertical();
if (caretIndex != _presenter.CaretIndex)
{
movement = true;
}
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
}
else
{
CaretIndex = _presenter.CaretIndex;
}
break;
}
break;
}
case Key.Back:
{
SnapshotUndoRedo();
if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
{
SetSelectionForControlBackspace();
}
if (!DeleteSelection())
{
var characterHit = _presenter.GetNextCharacterHit(LogicalDirection.Backward);
SnapshotUndoRedo();
var backspacePosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
{
SetSelectionForControlBackspace();
}
if (caretIndex != backspacePosition)
if (!DeleteSelection())
{
var start = Math.Min(backspacePosition, caretIndex);
var end = Math.Max(backspacePosition, caretIndex);
var characterHit = _presenter.GetNextCharacterHit(LogicalDirection.Backward);
var length = end - start;
var backspacePosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
if (caretIndex != backspacePosition)
{
var start = Math.Min(backspacePosition, caretIndex);
var end = Math.Max(backspacePosition, caretIndex);
SetTextInternal(editedText);
var length = end - start;
CaretIndex = start;
}
}
SnapshotUndoRedo();
var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
handled = true;
break;
}
SetTextInternal(editedText);
CaretIndex = start;
}
}
SnapshotUndoRedo();
handled = true;
break;
}
case Key.Delete:
SnapshotUndoRedo();
if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
{
SetSelectionForControlDelete();
@ -1077,7 +1078,7 @@ namespace Avalonia.Controls
var nextPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if(nextPosition != caretIndex)
if (nextPosition != caretIndex)
{
var start = Math.Min(nextPosition, caretIndex);
var end = Math.Max(nextPosition, caretIndex);
@ -1144,7 +1145,7 @@ namespace Avalonia.Controls
{
return;
}
var text = Text;
var clickInfo = e.GetCurrentPoint(this);
@ -1170,24 +1171,50 @@ namespace Avalonia.Controls
case 1:
if (clickToSelect)
{
SelectionStart = Math.Min(oldIndex, index);
SelectionEnd = Math.Max(oldIndex, index);
if (_wordSelectionStart >= 0)
{
var previousWord = StringUtils.PreviousWord(text, index);
if (index > _wordSelectionStart)
{
SelectionEnd = StringUtils.NextWord(text, index);
}
if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
{
SelectionStart = previousWord;
}
}
else
{
SelectionStart = Math.Min(oldIndex, index);
SelectionEnd = Math.Max(oldIndex, index);
}
}
else
{
SelectionStart = SelectionEnd = index;
if(_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
{
SelectionStart = SelectionEnd = index;
_wordSelectionStart = -1;
}
}
break;
case 2:
case 2:
if (!StringUtils.IsStartOfWord(text, index))
{
SelectionStart = StringUtils.PreviousWord(text, index);
}
_wordSelectionStart = SelectionStart;
SelectionEnd = StringUtils.NextWord(text, index);
break;
case 3:
_wordSelectionStart = -1;
SelectAll();
break;
}
@ -1203,7 +1230,7 @@ namespace Avalonia.Controls
{
return;
}
// selection should not change during pointer move if the user right clicks
if (e.Pointer.Captured == _presenter && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
@ -1215,7 +1242,32 @@ namespace Avalonia.Controls
_presenter.MoveCaretToPoint(point);
SelectionEnd = _presenter.CaretIndex;
var caretIndex = _presenter.CaretIndex;
var text = Text;
if (text != null && _wordSelectionStart >= 0)
{
var distance = caretIndex - _wordSelectionStart;
if (distance <= 0)
{
SelectionStart = StringUtils.PreviousWord(text, caretIndex);
}
if (distance >= 0)
{
if(SelectionStart != _wordSelectionStart)
{
SelectionStart = _wordSelectionStart;
}
SelectionEnd = StringUtils.NextWord(text, caretIndex);
}
}
else
{
SelectionEnd = _presenter.CaretIndex;
}
}
}
@ -1234,9 +1286,9 @@ namespace Avalonia.Controls
if (e.InitialPressMouseButton == MouseButton.Right)
{
var point = e.GetPosition(_presenter);
_presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex;
// see if mouse clicked inside current selection
@ -1250,7 +1302,7 @@ namespace Avalonia.Controls
CaretIndex = SelectionEnd = SelectionStart = caretIndex;
}
}
e.Pointer.Capture(null);
}
@ -1309,7 +1361,7 @@ namespace Avalonia.Controls
{
return;
}
var text = Text ?? string.Empty;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
@ -1319,11 +1371,11 @@ namespace Avalonia.Controls
if (isSelecting)
{
_presenter.MoveCaretToTextPosition(selectionEnd);
_presenter.MoveCaretHorizontal(direction > 0 ?
LogicalDirection.Forward :
LogicalDirection.Backward);
SelectionEnd = _presenter.CaretIndex;
}
else
@ -1347,7 +1399,7 @@ namespace Avalonia.Controls
else
{
int offset;
if (direction > 0)
{
offset = StringUtils.NextWord(text, selectionEnd) - selectionEnd;
@ -1356,7 +1408,7 @@ namespace Avalonia.Controls
{
offset = StringUtils.PreviousWord(text, selectionEnd) - selectionEnd;
}
SelectionEnd += offset;
_presenter.MoveCaretToTextPosition(SelectionEnd);
@ -1378,7 +1430,7 @@ namespace Avalonia.Controls
{
return;
}
var caretIndex = CaretIndex;
if (document)
@ -1401,7 +1453,7 @@ namespace Avalonia.Controls
{
return;
}
var text = Text ?? string.Empty;
var caretIndex = CaretIndex;
@ -1432,8 +1484,9 @@ namespace Avalonia.Controls
private bool DeleteSelection(bool raiseTextChanged = true)
{
if (IsReadOnly) return true;
if (IsReadOnly)
return true;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
@ -1444,40 +1497,40 @@ namespace Avalonia.Controls
var text = Text!;
SetTextInternal(text.Substring(0, start) + text.Substring(end), raiseTextChanged);
_presenter?.MoveCaretToTextPosition(start);
CaretIndex= start;
CaretIndex = start;
ClearSelection();
return true;
}
CaretIndex = SelectionStart;
return false;
}
private string GetSelection()
{
var text = Text;
if (string.IsNullOrEmpty(text))
{
return "";
}
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd);
var end = Math.Max(selectionStart, selectionEnd);
if (start == end || (Text?.Length ?? 0) < end)
{
return "";
}
return text.Substring(start, end - start);
}
@ -1496,7 +1549,7 @@ namespace Avalonia.Controls
private void SetSelectionForControlBackspace()
{
var selectionStart = CaretIndex;
MoveHorizontal(-1, true, false);
SelectionStart = selectionStart;
@ -1508,9 +1561,9 @@ namespace Avalonia.Controls
{
return;
}
SelectionStart = CaretIndex;
MoveHorizontal(1, true, true);
if (SelectionEnd < _text.Length && _text[SelectionEnd] == ' ')

4
src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs

@ -21,7 +21,7 @@ namespace Avalonia.OpenGL.Angle
var display = IntPtr.Zero;
AngleOptions.PlatformApi angleApi = default;
{
if (_egl.GetPlatformDisplayEXT == null)
if (!_egl.IsGetPlatformDisplayExtAvailable)
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll");
var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis
@ -37,7 +37,7 @@ namespace Avalonia.OpenGL.Angle
else
continue;
display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
display = _egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE });
if (display != IntPtr.Zero)
{

1
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

@ -11,4 +11,5 @@
</ItemGroup>
<Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\SourceGenerators.props" />
</Project>

14
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@ -66,11 +66,9 @@ namespace Avalonia.OpenGL.Controls
return;
gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer);
if (_depthBuffer != 0) gl.DeleteRenderbuffers(1, new[] { _depthBuffer });
if (_depthBuffer != 0) gl.DeleteRenderbuffer(_depthBuffer);
var oneArr = new int[1];
gl.GenRenderbuffers(1, oneArr);
_depthBuffer = oneArr[0];
_depthBuffer = gl.GenRenderbuffer();
gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
gl.RenderbufferStorage(GL_RENDERBUFFER,
GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT,
@ -88,9 +86,9 @@ namespace Avalonia.OpenGL.Controls
var gl = _context.GlInterface;
gl.BindTexture(GL_TEXTURE_2D, 0);
gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
gl.DeleteFramebuffers(1, new[] { _fb });
gl.DeleteFramebuffer(_fb);
_fb = 0;
gl.DeleteRenderbuffers(1, new[] { _depthBuffer });
gl.DeleteRenderbuffer(_depthBuffer);
_depthBuffer = 0;
_attachment?.Dispose();
_attachment = null;
@ -174,9 +172,7 @@ namespace Avalonia.OpenGL.Controls
{
_depthBufferSize = GetPixelSize();
var gl = _context.GlInterface;
var oneArr = new int[1];
gl.GenFramebuffers(1, oneArr);
_fb = oneArr[0];
_fb = gl.GenFramebuffer();
gl.BindFramebuffer(GL_FRAMEBUFFER, _fb);
EnsureDepthBufferAttachment(gl);

2
src/Avalonia.OpenGL/Egl/EglContext.cs

@ -24,7 +24,7 @@ namespace Avalonia.OpenGL.Egl
SampleCount = sampleCount;
StencilSize = stencilSize;
using (MakeCurrent())
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, b => _egl.GetProcAddress(b));
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress);
}
public IntPtr Context { get; }

4
src/Avalonia.OpenGL/Egl/EglDisplay.cs

@ -34,9 +34,9 @@ namespace Avalonia.OpenGL.Egl
}
else
{
if (egl.GetPlatformDisplayEXT == null)
if (!egl.IsGetPlatformDisplayExtAvailable)
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl");
display = egl.GetPlatformDisplayEXT(platformType, platformDisplay, attrs);
display = egl.GetPlatformDisplayExt(platformType, platformDisplay, attrs);
}
if (display == IntPtr.Zero)

225
src/Avalonia.OpenGL/Egl/EglInterface.cs

@ -2,31 +2,26 @@
using System.Runtime.InteropServices;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using Avalonia.SourceGenerator;
namespace Avalonia.OpenGL.Egl
{
public class EglInterface : GlInterfaceBase
public unsafe partial class EglInterface
{
public EglInterface() : base(Load())
public EglInterface(Func<string, IntPtr> getProcAddress)
{
}
public EglInterface(Func<Utf8Buffer,IntPtr> getProcAddress) : base(getProcAddress)
{
Initialize(getProcAddress);
}
public EglInterface(Func<string, IntPtr> getProcAddress) : base(getProcAddress)
public EglInterface(string library) : this(Load(library))
{
}
public EglInterface(string library) : base(Load(library))
public EglInterface() : this(Load())
{
}
static Func<string, IntPtr> Load()
{
var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem;
@ -46,119 +41,75 @@ namespace Avalonia.OpenGL.Egl
}
// ReSharper disable UnassignedGetOnlyAutoProperty
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int EglGetError();
[GlEntryPoint("eglGetError")]
public EglGetError GetError { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay);
[GlEntryPoint("eglGetDisplay")]
public EglGetDisplay GetDisplay { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglGetPlatformDisplayEXT(int platform, IntPtr nativeDisplay, int[] attrs);
[GlEntryPoint("eglGetPlatformDisplayEXT")]
[GlOptionalEntryPoint]
public EglGetPlatformDisplayEXT GetPlatformDisplayEXT { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglInitialize(IntPtr display, out int major, out int minor);
[GlEntryPoint("eglInitialize")]
public EglInitialize Initialize { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglGetProcAddress(Utf8Buffer proc);
[GlEntryPoint("eglGetProcAddress")]
public EglGetProcAddress GetProcAddress { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglBindApi(int api);
[GlEntryPoint("eglBindAPI")]
public EglBindApi BindApi { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglChooseConfig(IntPtr display, int[] attribs,
out IntPtr surfaceConfig, int numConfigs, out int choosenConfig);
[GlEntryPoint("eglChooseConfig")]
public EglChooseConfig ChooseConfig { get; }
[GetProcAddress("eglGetError")]
public partial int GetError();
[GetProcAddress("eglGetDisplay")]
public partial IntPtr GetDisplay(IntPtr nativeDisplay);
[GetProcAddress("eglGetPlatformDisplayEXT", true)]
public partial IntPtr GetPlatformDisplayExt(int platform, IntPtr nativeDisplay, int[] attrs);
[GetProcAddress("eglInitialize")]
public partial bool Initialize(IntPtr display, out int major, out int minor);
[GetProcAddress("eglGetProcAddress")]
public partial IntPtr GetProcAddress(IntPtr proc);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglCreateContext(IntPtr display, IntPtr config,
[GetProcAddress("eglBindAPI")]
public partial bool BindApi(int api);
[GetProcAddress("eglChooseConfig")]
public partial bool ChooseConfig(IntPtr display, int[] attribs,
out IntPtr surfaceConfig, int numConfigs, out int choosenConfig);
[GetProcAddress("eglCreateContext")]
public partial IntPtr CreateContext(IntPtr display, IntPtr config,
IntPtr share, int[] attrs);
[GlEntryPoint("eglCreateContext")]
public EglCreateContext CreateContext { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglDestroyContext(IntPtr display, IntPtr context);
[GlEntryPoint("eglDestroyContext")]
public EglDestroyContext DestroyContext { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
[GlEntryPoint("eglCreatePbufferSurface")]
public EglCreatePBufferSurface CreatePBufferSurface { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglMakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GlEntryPoint("eglMakeCurrent")]
public EglMakeCurrent MakeCurrent { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglGetCurrentContext();
[GlEntryPoint("eglGetCurrentContext")]
public EglGetCurrentContext GetCurrentContext { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglGetCurrentDisplay();
[GlEntryPoint("eglGetCurrentDisplay")]
public EglGetCurrentContext GetCurrentDisplay { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglGetCurrentSurface(int readDraw);
[GlEntryPoint("eglGetCurrentSurface")]
public EglGetCurrentSurface GetCurrentSurface { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface);
[GlEntryPoint("eglDestroySurface")]
public EglDisplaySurfaceVoidDelegate DestroySurface { get; }
[GlEntryPoint("eglSwapBuffers")]
public EglDisplaySurfaceVoidDelegate SwapBuffers { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr
EglCreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
[GlEntryPoint("eglCreateWindowSurface")]
public EglCreateWindowSurface CreateWindowSurface { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglGetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
[GlEntryPoint("eglGetConfigAttrib")]
public EglGetConfigAttrib GetConfigAttrib { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglWaitGL();
[GlEntryPoint("eglWaitGL")]
public EglWaitGL WaitGL { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglWaitClient();
[GlEntryPoint("eglWaitClient")]
public EglWaitGL WaitClient { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglWaitNative(int engine);
[GlEntryPoint("eglWaitNative")]
public EglWaitNative WaitNative { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglQueryString(IntPtr display, int i);
[GlEntryPoint("eglQueryString")]
public EglQueryString QueryStringNative { get; }
[GetProcAddress("eglDestroyContext")]
public partial bool DestroyContext(IntPtr display, IntPtr context);
[GetProcAddress("eglCreatePbufferSurface")]
public partial IntPtr CreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
[GetProcAddress("eglMakeCurrent")]
public partial bool MakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GetProcAddress("eglGetCurrentContext")]
public partial IntPtr GetCurrentContext();
[GetProcAddress("eglGetCurrentDisplay")]
public partial IntPtr GetCurrentDisplay();
[GetProcAddress("eglGetCurrentSurface")]
public partial IntPtr GetCurrentSurface(int readDraw);
[GetProcAddress("eglDestroySurface")]
public partial void DestroySurface(IntPtr display, IntPtr surface);
[GetProcAddress("eglSwapBuffers")]
public partial void SwapBuffers(IntPtr display, IntPtr surface);
[GetProcAddress("eglCreateWindowSurface")]
public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
[GetProcAddress("eglGetConfigAttrib")]
public partial bool GetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
[GetProcAddress("eglWaitGL")]
public partial bool WaitGL();
[GetProcAddress("eglWaitClient")]
public partial bool WaitClient();
[GetProcAddress("eglWaitNative")]
public partial bool WaitNative(int engine);
[GetProcAddress("eglQueryString")]
public partial IntPtr QueryStringNative(IntPtr display, int i);
public string QueryString(IntPtr display, int i)
{
var rv = QueryStringNative(display, i);
@ -166,25 +117,15 @@ namespace Avalonia.OpenGL.Egl
return null;
return Marshal.PtrToStringAnsi(rv);
}
[GetProcAddress("eglCreatePbufferFromClientBuffer")]
public partial IntPtr CreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list);
[GetProcAddress("eglQueryDisplayAttribEXT", true)]
public partial bool QueryDisplayAttribExt(IntPtr display, int attr, out IntPtr res);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr EglCreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list);
[GlEntryPoint("eglCreatePbufferFromClientBuffer")]
public EglCreatePbufferFromClientBuffer CreatePbufferFromClientBuffer { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglQueryDisplayAttribEXT(IntPtr display, int attr, out IntPtr res);
[GlEntryPoint("eglQueryDisplayAttribEXT"), GlOptionalEntryPoint]
public EglQueryDisplayAttribEXT QueryDisplayAttribExt { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EglQueryDeviceAttribEXT(IntPtr display, int attr, out IntPtr res);
[GlEntryPoint("eglQueryDeviceAttribEXT"), GlOptionalEntryPoint]
public EglQueryDisplayAttribEXT QueryDeviceAttribExt { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty
[GetProcAddress("eglQueryDeviceAttribEXT", true)]
public partial bool QueryDeviceAttribExt(IntPtr display, int attr, out IntPtr res);
}
}

44
src/Avalonia.OpenGL/GlBasicInfoInterface.cs

@ -3,46 +3,24 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
using Avalonia.SourceGenerator;
namespace Avalonia.OpenGL
{
public class GlBasicInfoInterface : GlBasicInfoInterface<object>
public unsafe partial class GlBasicInfoInterface
{
public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress) : base(getProcAddress, null)
{
}
public GlBasicInfoInterface(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : base(nativeGetProcAddress, null)
{
public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress){
Initialize(getProcAddress);
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGetIntegerv(int name, out int rv);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr GlGetString(int v);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr GlGetStringi(int v, int v1);
}
public class GlBasicInfoInterface<TContextInfo> : GlInterfaceBase<TContextInfo>
{
public GlBasicInfoInterface(Func<string, IntPtr> getProcAddress, TContextInfo context) : base(getProcAddress, context)
{
}
[GetProcAddress("glGetIntegerv")]
public partial void GetIntegerv(int name, out int rv);
public GlBasicInfoInterface(Func<Utf8Buffer, IntPtr> nativeGetProcAddress, TContextInfo context) : base(nativeGetProcAddress, context)
{
}
[GlEntryPoint("glGetIntegerv")]
public GlBasicInfoInterface.GlGetIntegerv GetIntegerv { get; }
[GlEntryPoint("glGetString")]
public GlBasicInfoInterface.GlGetString GetStringNative { get; }
[GlEntryPoint("glGetStringi")]
public GlBasicInfoInterface.GlGetStringi GetStringiNative { get; }
[GetProcAddress("glGetString")]
public partial IntPtr GetStringNative(int v);
[GetProcAddress("glGetStringi")]
public partial IntPtr GetStringiNative(int v, int v1);
public string GetString(int v)
{

103
src/Avalonia.OpenGL/GlEntryPointAttribute.cs

@ -2,117 +2,54 @@ using System;
namespace Avalonia.OpenGL
{
public interface IGlEntryPointAttribute
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
class GlMinVersionEntryPoint : Attribute
{
IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress);
}
public interface IGlEntryPointAttribute<in TContext>
{
IntPtr GetProcAddress(TContext context, Func<string, IntPtr> getProcAddress);
}
[AttributeUsage(AttributeTargets.Property)]
public class GlOptionalEntryPoint : Attribute
{
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class GlEntryPointAttribute : Attribute, IGlEntryPointAttribute
{
public string[] EntryPoints { get; }
public GlEntryPointAttribute(string entryPoint)
{
EntryPoints = new []{entryPoint};
}
/*
public GlEntryPointAttribute(params string[] entryPoints)
public GlMinVersionEntryPoint(string entry, int minVersionMajor, int minVersionMinor)
{
EntryPoints = entryPoints;
}
*/
public IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress)
{
foreach (var name in EntryPoints)
{
var rv = getProcAddress(name);
if (rv != IntPtr.Zero)
return rv;
}
return IntPtr.Zero;
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class GlMinVersionEntryPoint : Attribute, IGlEntryPointAttribute<GlInterface.GlContextInfo>
{
private readonly string _entry;
private readonly GlProfileType? _profile;
private readonly int _minVersionMajor;
private readonly int _minVersionMinor;
public GlMinVersionEntryPoint(string entry, GlProfileType profile, int minVersionMajor,
int minVersionMinor)
{
_entry = entry;
_profile = profile;
_minVersionMajor = minVersionMajor;
_minVersionMinor = minVersionMinor;
}
public GlMinVersionEntryPoint(string entry, int minVersionMajor,
int minVersionMinor)
public GlMinVersionEntryPoint(string entry, int minVersionMajor, int minVersionMinor, GlProfileType profile)
{
_entry = entry;
_minVersionMajor = minVersionMajor;
_minVersionMinor = minVersionMinor;
}
public IntPtr GetProcAddress(GlInterface.GlContextInfo context, Func<string, IntPtr> getProcAddress)
public static IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress, GlInterface.GlContextInfo context,
string entry, int minVersionMajor, int minVersionMinor, GlProfileType? profile = null)
{
if(_profile.HasValue && context.Version.Type != _profile)
if(profile.HasValue && context.Version.Type != profile)
return IntPtr.Zero;
if(context.Version.Major<_minVersionMajor)
if(context.Version.Major<minVersionMajor)
return IntPtr.Zero;
if (context.Version.Major == _minVersionMajor && context.Version.Minor < _minVersionMinor)
if (context.Version.Major == minVersionMajor && context.Version.Minor < minVersionMinor)
return IntPtr.Zero;
return getProcAddress(_entry);
return getProcAddress(entry);
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class GlExtensionEntryPoint : Attribute, IGlEntryPointAttribute<GlInterface.GlContextInfo>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
class GlExtensionEntryPoint : Attribute
{
private readonly string _entry;
private readonly GlProfileType? _profile;
private readonly string _extension;
public GlExtensionEntryPoint(string entry, GlProfileType profile, string extension)
public GlExtensionEntryPoint(string entry, string extension)
{
_entry = entry;
_profile = profile;
_extension = extension;
}
public GlExtensionEntryPoint(string entry, string extension)
public GlExtensionEntryPoint(string entry, string extension, GlProfileType profile)
{
_entry = entry;
_extension = extension;
}
public IntPtr GetProcAddress(GlInterface.GlContextInfo context, Func<string, IntPtr> getProcAddress)
public static IntPtr GetProcAddress(Func<string, IntPtr> getProcAddress, GlInterface.GlContextInfo context,
string entry, string extension, GlProfileType? profile = null)
{
// Ignore different profile type
if (_profile.HasValue && _profile != context.Version.Type)
if (profile.HasValue && profile != context.Version.Type)
return IntPtr.Zero;
// Check if extension is supported by the current context
if (!context.Extensions.Contains(_extension))
if (!context.Extensions.Contains(extension))
return IntPtr.Zero;
return getProcAddress(_entry);
return getProcAddress(entry);
}
}
}

494
src/Avalonia.OpenGL/GlInterface.cs

@ -3,14 +3,14 @@ using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Platform.Interop;
using Avalonia.SourceGenerator;
using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.OpenGL
{
public delegate IntPtr GlGetProcAddressDelegate(string procName);
public unsafe class GlInterface : GlBasicInfoInterface<GlInterface.GlContextInfo>
public unsafe partial class GlInterface : GlBasicInfoInterface
{
private readonly Func<string, IntPtr> _getProcAddress;
public string Version { get; }
public string Vendor { get; }
public string Renderer { get; }
@ -35,12 +35,14 @@ namespace Avalonia.OpenGL
}
}
private GlInterface(GlContextInfo info, Func<string, IntPtr> getProcAddress) : base(getProcAddress, info)
private GlInterface(GlContextInfo info, Func<string, IntPtr> getProcAddress) : base(getProcAddress)
{
_getProcAddress = getProcAddress;
ContextInfo = info;
Version = GetString(GlConsts.GL_VERSION);
Renderer = GetString(GlConsts.GL_RENDERER);
Vendor = GetString(GlConsts.GL_VENDOR);
Vendor = GetString(GlConsts.GL_VENDOR);
Initialize(getProcAddress, ContextInfo);
}
public GlInterface(GlVersion version, Func<string, IntPtr> getProcAddress) : this(
@ -48,92 +50,58 @@ namespace Avalonia.OpenGL
{
}
public GlInterface(GlVersion version, Func<Utf8Buffer, IntPtr> n) : this(version, ConvertNative(n))
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc);
[GetProcAddress("glGetError")]
public partial int GetError();
[GetProcAddress("glClearStencil")]
public partial void ClearStencil(int s);
[GetProcAddress("glClearColor")]
public partial void ClearColor(float r, float g, float b, float a);
[GetProcAddress("glClear")]
public partial void Clear(int bits);
[GetProcAddress("glViewport")]
public partial void Viewport(int x, int y, int width, int height);
[GetProcAddress("glFlush")]
public partial void Flush();
[GetProcAddress("glFinish")]
public partial void Finish();
[GetProcAddress("glGetIntegerv")]
public partial void GetIntegerv(int name, out int rv);
[GetProcAddress("glGenFramebuffers")]
public partial void GenFramebuffers(int count, int* res);
public int GenFramebuffer()
{
int rv = 0;
GenFramebuffers(1, &rv);
return rv;
}
public static GlInterface FromNativeUtf8GetProcAddress(GlVersion version, Func<Utf8Buffer, IntPtr> getProcAddress) =>
new GlInterface(version, getProcAddress);
public T GetProcAddress<T>(string proc) => Marshal.GetDelegateForFunctionPointer<T>(GetProcAddress(proc));
// ReSharper disable UnassignedGetOnlyAutoProperty
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int GlGetError();
[GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlClearStencil(int s);
[GlEntryPoint("glClearStencil")]
public GlClearStencil ClearStencil { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlClearColor(float r, float g, float b, float a);
[GlEntryPoint("glClearColor")]
public GlClearColor ClearColor { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlClear(int bits);
[GlEntryPoint("glClear")]
public GlClear Clear { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlViewport(int x, int y, int width, int height);
[GlEntryPoint("glViewport")]
public GlViewport Viewport { get; }
[GlEntryPoint("glFlush")]
public UnmanagedAction Flush { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void UnmanagedAction();
[GlEntryPoint("glFinish")]
public UnmanagedAction Finish { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr GlGetString(int v);
[GlEntryPoint("glGetString")]
public GlGetString GetStringNative { get; }
public string GetString(int v)
[GetProcAddress("glDeleteFramebuffers")]
public partial void DeleteFramebuffers(int count, int* framebuffers);
public void DeleteFramebuffer(int fb)
{
var ptr = GetStringNative(v);
if (ptr != IntPtr.Zero)
return Marshal.PtrToStringAnsi(ptr);
return null;
DeleteFramebuffers(1, &fb);
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGetIntegerv(int name, out int rv);
[GlEntryPoint("glGetIntegerv")]
public GlGetIntegerv GetIntegerv { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGenFramebuffers(int count, int[] res);
[GlEntryPoint("glGenFramebuffers")]
public GlGenFramebuffers GenFramebuffers { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlDeleteFramebuffers(int count, int[] framebuffers);
[GlEntryPoint("glDeleteFramebuffers")]
public GlDeleteFramebuffers DeleteFramebuffers { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlBindFramebuffer(int target, int fb);
[GlEntryPoint("glBindFramebuffer")]
public GlBindFramebuffer BindFramebuffer { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int GlCheckFramebufferStatus(int target);
[GlEntryPoint("glCheckFramebufferStatus")]
public GlCheckFramebufferStatus CheckFramebufferStatus { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlBlitFramebuffer(int srcX0,
[GetProcAddress("glBindFramebuffer")]
public partial void BindFramebuffer(int target, int fb);
[GetProcAddress("glCheckFramebufferStatus")]
public partial int CheckFramebufferStatus(int target);
[GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0), GetProcAddress(true)]
public partial void BlitFramebuffer(int srcX0,
int srcY0,
int srcX1,
int srcY1,
@ -143,89 +111,78 @@ namespace Avalonia.OpenGL
int dstY1,
int mask,
int filter);
[GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0), GlOptionalEntryPoint]
public GlBlitFramebuffer BlitFramebuffer { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGenRenderbuffers(int count, int[] res);
[GlEntryPoint("glGenRenderbuffers")]
public GlGenRenderbuffers GenRenderbuffers { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlDeleteRenderbuffers(int count, int[] renderbuffers);
[GlEntryPoint("glDeleteRenderbuffers")]
public GlDeleteTextures DeleteRenderbuffers { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlBindRenderbuffer(int target, int fb);
[GlEntryPoint("glBindRenderbuffer")]
public GlBindRenderbuffer BindRenderbuffer { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlRenderbufferStorage(int target, int internalFormat, int width, int height);
[GlEntryPoint("glRenderbufferStorage")]
public GlRenderbufferStorage RenderbufferStorage { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlFramebufferRenderbuffer(int target, int attachment,
[GetProcAddress("glGenRenderbuffers")]
public partial void GenRenderbuffers(int count, int* res);
public int GenRenderbuffer()
{
int rv = 0;
GenRenderbuffers(1, &rv);
return rv;
}
[GetProcAddress("glDeleteRenderbuffers")]
public partial void DeleteRenderbuffers(int count, int* renderbuffers);
public void DeleteRenderbuffer(int renderbuffer)
{
DeleteRenderbuffers(1, &renderbuffer);
}
[GetProcAddress("glBindRenderbuffer")]
public partial void BindRenderbuffer(int target, int fb);
[GetProcAddress("glRenderbufferStorage")]
public partial void RenderbufferStorage(int target, int internalFormat, int width, int height);
[GetProcAddress("glFramebufferRenderbuffer")]
public partial void FramebufferRenderbuffer(int target, int attachment,
int renderbufferTarget, int renderbuffer);
[GlEntryPoint("glFramebufferRenderbuffer")]
public GlFramebufferRenderbuffer FramebufferRenderbuffer { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGenTextures(int count, int[] res);
[GlEntryPoint("glGenTextures")]
public GlGenTextures GenTextures { get; }
[GetProcAddress("glGenTextures")]
public partial void GenTextures(int count, int* res);
public int GenTexture()
{
int rv = 0;
GenTextures(1, &rv);
return rv;
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlBindTexture(int target, int fb);
[GlEntryPoint("glBindTexture")]
public GlBindTexture BindTexture { get; }
[GetProcAddress("glBindTexture")]
public partial void BindTexture(int target, int fb);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlActiveTexture(int texture);
[GlEntryPoint("glActiveTexture")]
public GlActiveTexture ActiveTexture { get; }
[GetProcAddress("glActiveTexture")]
public partial void ActiveTexture(int texture);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlDeleteTextures(int count, int[] textures);
[GlEntryPoint("glDeleteTextures")]
public GlDeleteTextures DeleteTextures { get; }
[GetProcAddress("glDeleteTextures")]
public partial void DeleteTextures(int count, int* textures);
public void DeleteTexture(int texture) => DeleteTextures(1, &texture);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlTexImage2D(int target, int level, int internalFormat, int width, int height, int border,
[GetProcAddress("glTexImage2D")]
public partial void TexImage2D(int target, int level, int internalFormat, int width, int height, int border,
int format, int type, IntPtr data);
[GlEntryPoint("glTexImage2D")]
public GlTexImage2D TexImage2D { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y,
[GetProcAddress("glCopyTexSubImage2D")]
public partial void CopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y,
int width, int height);
[GlEntryPoint("glCopyTexSubImage2D")]
public GlCopyTexSubImage2D CopyTexSubImage2D { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlTexParameteri(int target, int name, int value);
[GlEntryPoint("glTexParameteri")]
public GlTexParameteri TexParameteri { get; }
[GetProcAddress("glTexParameteri")]
public partial void TexParameteri(int target, int name, int value);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlFramebufferTexture2D(int target, int attachment,
[GetProcAddress("glFramebufferTexture2D")]
public partial void FramebufferTexture2D(int target, int attachment,
int texTarget, int texture, int level);
[GlEntryPoint("glFramebufferTexture2D")]
public GlFramebufferTexture2D FramebufferTexture2D { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int GlCreateShader(int shaderType);
[GlEntryPoint("glCreateShader")]
public GlCreateShader CreateShader { get; }
[GetProcAddress("glCreateShader")]
public partial int CreateShader(int shaderType);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlShaderSource(int shader, int count, IntPtr strings, IntPtr lengths);
[GlEntryPoint("glShaderSource")]
public GlShaderSource ShaderSource { get; }
[GetProcAddress("glShaderSource")]
public partial void ShaderSource(int shader, int count, IntPtr strings, IntPtr lengths);
public void ShaderSourceString(int shader, string source)
{
@ -237,20 +194,14 @@ namespace Avalonia.OpenGL
}
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlCompileShader(int shader);
[GlEntryPoint("glCompileShader")]
public GlCompileShader CompileShader { get; }
[GetProcAddress("glCompileShader")]
public partial void CompileShader(int shader);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGetShaderiv(int shader, int name, int* parameters);
[GlEntryPoint("glGetShaderiv")]
public GlGetShaderiv GetShaderiv { get; }
[GetProcAddress("glGetShaderiv")]
public partial void GetShaderiv(int shader, int name, int* parameters);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGetShaderInfoLog(int shader, int maxLength, out int length, void*infoLog);
[GlEntryPoint("glGetShaderInfoLog")]
public GlGetShaderInfoLog GetShaderInfoLog { get; }
[GetProcAddress("glGetShaderInfoLog")]
public partial void GetShaderInfoLog(int shader, int maxLength, out int length, void* infoLog);
public unsafe string CompileShaderAndGetError(int shader, string source)
{
@ -268,33 +219,24 @@ namespace Avalonia.OpenGL
int len;
fixed (void* ptr = logData)
GetShaderInfoLog(shader, logLength, out len, ptr);
return Encoding.UTF8.GetString(logData,0, len);
return Encoding.UTF8.GetString(logData, 0, len);
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int GlCreateProgram();
[GlEntryPoint("glCreateProgram")]
public GlCreateProgram CreateProgram { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlAttachShader(int program, int shader);
[GlEntryPoint("glAttachShader")]
public GlAttachShader AttachShader { get; }
[GetProcAddress("glCreateProgram")]
public partial int CreateProgram();
[GetProcAddress("glAttachShader")]
public partial void AttachShader(int program, int shader);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlLinkProgram(int program);
[GlEntryPoint("glLinkProgram")]
public GlLinkProgram LinkProgram { get; }
[GetProcAddress("glLinkProgram")]
public partial void LinkProgram(int program);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGetProgramiv(int program, int name, int* parameters);
[GlEntryPoint("glGetProgramiv")]
public GlGetProgramiv GetProgramiv { get; }
[GetProcAddress("glGetProgramiv")]
public partial void GetProgramiv(int program, int name, int* parameters);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGetProgramInfoLog(int program, int maxLength, out int len, void* infoLog);
[GlEntryPoint("glGetProgramInfoLog")]
public GlGetProgramInfoLog GetProgramInfoLog { get; }
[GetProcAddress("glGetProgramInfoLog")]
public partial void GetProgramInfoLog(int program, int maxLength, out int len, void* infoLog);
public unsafe string LinkProgramAndGetError(int program)
{
@ -309,13 +251,11 @@ namespace Avalonia.OpenGL
int len;
fixed (void* ptr = logData)
GetProgramInfoLog(program, logLength, out len, ptr);
return Encoding.UTF8.GetString(logData,0, len);
return Encoding.UTF8.GetString(logData, 0, len);
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlBindAttribLocation(int program, int index, IntPtr name);
[GlEntryPoint("glBindAttribLocation")]
public GlBindAttribLocation BindAttribLocation { get; }
[GetProcAddress("glBindAttribLocation")]
public partial void BindAttribLocation(int program, int index, IntPtr name);
public void BindAttribLocationString(int program, int index, string name)
{
@ -323,32 +263,24 @@ namespace Avalonia.OpenGL
BindAttribLocation(program, index, b.DangerousGetHandle());
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlGenBuffers(int len, int[] rv);
[GlEntryPoint("glGenBuffers")]
public GlGenBuffers GenBuffers { get; }
[GetProcAddress("glGenBuffers")]
public partial void GenBuffers(int len, int* rv);
public int GenBuffer()
{
var rv = new int[1];
GenBuffers(1, rv);
return rv[0];
int rv;
GenBuffers(1, &rv);
return rv;
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlBindBuffer(int target, int buffer);
[GlEntryPoint("glBindBuffer")]
public GlBindBuffer BindBuffer { get; }
[GetProcAddress("glBindBuffer")]
public partial void BindBuffer(int target, int buffer);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlBufferData(int target, IntPtr size, IntPtr data, int usage);
[GlEntryPoint("glBufferData")]
public GlBufferData BufferData { get; }
[GetProcAddress("glBufferData")]
public partial void BufferData(int target, IntPtr size, IntPtr data, int usage);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int GlGetAttribLocation(int program, IntPtr name);
[GlEntryPoint("glGetAttribLocation")]
public GlGetAttribLocation GetAttribLocation { get; }
[GetProcAddress("glGetAttribLocation")]
public partial int GetAttribLocation(int program, IntPtr name);
public int GetAttribLocationString(int program, string name)
{
@ -356,36 +288,24 @@ namespace Avalonia.OpenGL
return GetAttribLocation(program, b.DangerousGetHandle());
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlVertexAttribPointer(int index, int size, int type,
[GetProcAddress("glVertexAttribPointer")]
public partial void VertexAttribPointer(int index, int size, int type,
int normalized, int stride, IntPtr pointer);
[GlEntryPoint("glVertexAttribPointer")]
public GlVertexAttribPointer VertexAttribPointer { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlEnableVertexAttribArray(int index);
[GlEntryPoint("glEnableVertexAttribArray")]
public GlEnableVertexAttribArray EnableVertexAttribArray { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlUseProgram(int program);
[GlEntryPoint("glUseProgram")]
public GlUseProgram UseProgram { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlDrawArrays(int mode, int first, IntPtr count);
[GlEntryPoint("glDrawArrays")]
public GlDrawArrays DrawArrays { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlDrawElements(int mode, int count, int type, IntPtr indices);
[GlEntryPoint("glDrawElements")]
public GlDrawElements DrawElements { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int GlGetUniformLocation(int program, IntPtr name);
[GlEntryPoint("glGetUniformLocation")]
public GlGetUniformLocation GetUniformLocation { get; }
[GetProcAddress("glEnableVertexAttribArray")]
public partial void EnableVertexAttribArray(int index);
[GetProcAddress("glUseProgram")]
public partial void UseProgram(int program);
[GetProcAddress("glDrawArrays")]
public partial void DrawArrays(int mode, int first, IntPtr count);
[GetProcAddress("glDrawElements")]
public partial void DrawElements(int mode, int count, int type, IntPtr indices);
[GetProcAddress("glGetUniformLocation")]
public partial int GetUniformLocation(int program, IntPtr name);
public int GetUniformLocationString(int program, string name)
{
@ -393,41 +313,65 @@ namespace Avalonia.OpenGL
return GetUniformLocation(program, b.DangerousGetHandle());
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlUniform1f(int location, float falue);
[GlEntryPoint("glUniform1f")]
public GlUniform1f Uniform1f { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlUniformMatrix4fv(int location, int count, bool transpose, void* value);
[GlEntryPoint("glUniformMatrix4fv")]
public GlUniformMatrix4fv UniformMatrix4fv { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlEnable(int what);
[GlEntryPoint("glEnable")]
public GlEnable Enable { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlDeleteBuffers(int count, int[] buffers);
[GlEntryPoint("glDeleteBuffers")]
public GlDeleteBuffers DeleteBuffers { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlDeleteProgram(int program);
[GlEntryPoint("glDeleteProgram")]
public GlDeleteProgram DeleteProgram { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GlDeleteShader(int shader);
[GlEntryPoint("glDeleteShader")]
public GlDeleteShader DeleteShader { get; }
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void GLGetRenderbufferParameteriv(int target, int name, int[] value);
[GlEntryPoint("glGetRenderbufferParameteriv")]
public GLGetRenderbufferParameteriv GetRenderbufferParameteriv { get; }
[GetProcAddress("glUniform1f")]
public partial void Uniform1f(int location, float falue);
[GetProcAddress("glUniformMatrix4fv")]
public partial void UniformMatrix4fv(int location, int count, bool transpose, void* value);
[GetProcAddress("glEnable")]
public partial void Enable(int what);
[GetProcAddress("glDeleteBuffers")]
public partial void DeleteBuffers(int count, int* buffers);
public void DeleteBuffer(int buffer) => DeleteBuffers(1, &buffer);
[GetProcAddress("glDeleteProgram")]
public partial void DeleteProgram(int program);
[GetProcAddress("glDeleteShader")]
public partial void DeleteShader(int shader);
[GetProcAddress("glGetRenderbufferParameteriv")]
public partial void GetRenderbufferParameteriv(int target, int name, out int value);
// ReSharper restore UnassignedGetOnlyAutoProperty
[GetProcAddress(true)]
[GlMinVersionEntryPoint("glDeleteVertexArrays", 3, 0)]
[GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")]
public partial void DeleteVertexArrays(int count, int* arrays);
public void DeleteVertexArray(int array) => DeleteVertexArrays(1, &array);
[GetProcAddress(true)]
[GlMinVersionEntryPoint("glBindVertexArray", 3, 0)]
[GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")]
public partial void BindVertexArray(int array);
[GetProcAddress(true)]
[GlMinVersionEntryPoint("glGenVertexArrays", 3, 0)]
[GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")]
public partial void GenVertexArrays(int n, int* rv);
public int GenVertexArray()
{
int rv = 0;
GenVertexArrays(1, &rv);
return rv;
}
public static GlInterface FromNativeUtf8GetProcAddress(GlVersion version, Func<IntPtr, IntPtr> getProcAddress)
{
return new GlInterface(version, s =>
{
var ptr = Marshal.StringToHGlobalAnsi(s);
var rv = getProcAddress(ptr);
Marshal.FreeHGlobal(ptr);
return rv;
});
}
}
}
}

79
src/Avalonia.OpenGL/GlInterfaceBase.cs

@ -1,79 +0,0 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL
{
public class GlInterfaceBase : GlInterfaceBase<object>
{
public GlInterfaceBase(Func<string, IntPtr> getProcAddress) : base(getProcAddress, null)
{
}
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : base(nativeGetProcAddress, null)
{
}
}
public class GlInterfaceBase<TContext>
{
private readonly Func<string, IntPtr> _getProcAddress;
public GlInterfaceBase(Func<string, IntPtr> getProcAddress, TContext context)
{
_getProcAddress = getProcAddress;
foreach (var prop in this.GetType().GetProperties())
{
var attrs = prop.GetCustomAttributes()
.Where(a =>
a is IGlEntryPointAttribute || a is IGlEntryPointAttribute<TContext>)
.ToList();
if(attrs.Count == 0)
continue;
var isOptional = prop.GetCustomAttribute<GlOptionalEntryPoint>() != null;
var fieldName = $"<{prop.Name}>k__BackingField";
var field = prop.DeclaringType.GetField(fieldName,
BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
throw new InvalidProgramException($"Expected property {prop.Name} to have {fieldName}");
IntPtr proc = IntPtr.Zero;
foreach (var attr in attrs)
{
if (attr is IGlEntryPointAttribute<TContext> typed)
proc = typed.GetProcAddress(context, getProcAddress);
else if (attr is IGlEntryPointAttribute untyped)
proc = untyped.GetProcAddress(getProcAddress);
if (proc != IntPtr.Zero)
break;
}
if (proc != IntPtr.Zero)
field.SetValue(this, Marshal.GetDelegateForFunctionPointer(proc, prop.PropertyType));
else if (!isOptional)
throw new OpenGlException("Unable to find a suitable GL function for " + prop.Name);
}
}
protected static Func<string, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) =>
(proc) =>
{
using (var u = new Utf8Buffer(proc))
{
var rv = func(u);
return rv;
}
};
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress, TContext context) : this(ConvertNative(nativeGetProcAddress), context)
{
}
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc);
}
}

10
src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml

@ -0,0 +1,10 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<RichTextBlock IsTextSelectionEnabled="True" Text="Preview"/>
</Design.PreviewWith>
<Style Selector="RichTextBlock[IsTextSelectionEnabled=true]">
<Setter Property="Cursor" Value="IBeam" />
</Style>
</Styles>

1
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -66,4 +66,5 @@
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/FlyoutPresenter.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/MenuFlyoutPresenter.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/ManagedFileChooser.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Controls/RichTextBlock.xaml"/>
</Styles>

1
src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml

@ -68,6 +68,7 @@
<ResourceInclude Source="avares://Avalonia.Themes.Fluent/Controls/Slider.xaml" />
<!-- ManagedFileChooser comes last because it uses (and overrides) styles for a multitude of other controls...the dialogs were originally UserControls, after all -->
<ResourceInclude Source="avares://Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml" />
<ResourceInclude Source="avares://Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Styles.Resources>

14
src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml

@ -0,0 +1,14 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<RichTextBlock IsTextSelectionEnabled="True" Text="Preview"/>
</Design.PreviewWith>
<ControlTheme x:Key="{x:Type RichTextBlock}" TargetType="RichTextBlock">
<Style Selector="^[IsTextSelectionEnabled=True]">
<Setter Property="Cursor" Value="IBeam" />
</Style>
</ControlTheme>
</ResourceDictionary>

2
src/Avalonia.X11/Avalonia.X11.csproj

@ -11,5 +11,5 @@
<ProjectReference Include="..\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj" />
<Compile Include="..\Shared\RawEventGrouping.cs" />
</ItemGroup>
<Import Project="..\..\build\SourceGenerators.props" />
</Project>

111
src/Avalonia.X11/Glx/Glx.cs

@ -4,111 +4,97 @@ using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.OpenGL;
using Avalonia.Platform.Interop;
using Avalonia.SourceGenerator;
// ReSharper disable UnassignedGetOnlyAutoProperty
namespace Avalonia.X11.Glx
{
unsafe class GlxInterface : GlInterfaceBase
unsafe partial class GlxInterface
{
private const string libGL = "libGL.so.1";
[GlEntryPointAttribute("glXMakeContextCurrent")]
public GlxMakeContextCurrent MakeContextCurrent { get; }
public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GetProcAddress("glXMakeContextCurrent")]
public partial bool MakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GlEntryPoint("glXGetCurrentContext")]
public GlxGetCurrentContext GetCurrentContext { get; }
public delegate IntPtr GlxGetCurrentContext();
[GetProcAddress("glXGetCurrentContext")]
public partial IntPtr GetCurrentContext();
[GlEntryPoint("glXGetCurrentDisplay")]
public GlxGetCurrentDisplay GetCurrentDisplay { get; }
public delegate IntPtr GlxGetCurrentDisplay();
[GetProcAddress("glXGetCurrentDisplay")]
public partial IntPtr GetCurrentDisplay();
[GlEntryPoint("glXGetCurrentDrawable")]
public GlxGetCurrentDrawable GetCurrentDrawable { get; }
public delegate IntPtr GlxGetCurrentDrawable();
[GetProcAddress("glXGetCurrentDrawable")]
public partial IntPtr GetCurrentDrawable();
[GlEntryPoint("glXGetCurrentReadDrawable")]
public GlxGetCurrentReadDrawable GetCurrentReadDrawable { get; }
public delegate IntPtr GlxGetCurrentReadDrawable();
[GetProcAddress("glXGetCurrentReadDrawable")]
public partial IntPtr GetCurrentReadDrawable();
[GlEntryPoint("glXCreatePbuffer")]
public GlxCreatePbuffer CreatePbuffer { get; }
public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list);
[GetProcAddress("glXCreatePbuffer")]
public partial IntPtr CreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list);
[GlEntryPoint("glXDestroyPbuffer")]
public GlxDestroyPbuffer DestroyPbuffer { get; }
public delegate IntPtr GlxDestroyPbuffer(IntPtr dpy, IntPtr fb);
[GetProcAddress("glXDestroyPbuffer")]
public partial IntPtr DestroyPbuffer(IntPtr dpy, IntPtr fb);
[GlEntryPointAttribute("glXChooseVisual")]
public GlxChooseVisual ChooseVisual { get; }
public delegate XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList);
[GetProcAddress("glXChooseVisual")]
public partial XVisualInfo* ChooseVisual(IntPtr dpy, int screen, int[] attribList);
[GlEntryPointAttribute("glXCreateContext")]
public GlxCreateContext CreateContext { get; }
public delegate IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct);
[GetProcAddress("glXCreateContext")]
public partial IntPtr CreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct);
[GlEntryPointAttribute("glXCreateContextAttribsARB")]
public GlxCreateContextAttribsARB CreateContextAttribsARB { get; }
public delegate IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList,
[GetProcAddress("glXCreateContextAttribsARB")]
public partial IntPtr CreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList,
bool direct, int[] attribs);
[DllImport(libGL, EntryPoint = "glXGetProcAddress")]
public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer);
public static extern IntPtr GlxGetProcAddress(string buffer);
[GlEntryPointAttribute("glXDestroyContext")]
public GlxDestroyContext DestroyContext { get; }
public delegate void GlxDestroyContext(IntPtr dpy, IntPtr ctx);
[GetProcAddress("glXDestroyContext")]
public partial void DestroyContext(IntPtr dpy, IntPtr ctx);
[GlEntryPointAttribute("glXChooseFBConfig")]
public GlxChooseFBConfig ChooseFBConfig { get; }
public delegate IntPtr* GlxChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements);
[GetProcAddress("glXChooseFBConfig")]
public partial IntPtr* ChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements);
public IntPtr* GlxChooseFbConfig(IntPtr dpy, int screen, IEnumerable<int> attribs, out int nelements)
public IntPtr* ChooseFbConfig(IntPtr dpy, int screen, IEnumerable<int> attribs, out int nelements)
{
var arr = attribs.Concat(new[]{0}).ToArray();
return ChooseFBConfig(dpy, screen, arr, out nelements);
}
[GlEntryPointAttribute("glXGetVisualFromFBConfig")]
public GlxGetVisualFromFBConfig GetVisualFromFBConfig { get; }
public delegate XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config);
[GetProcAddress("glXGetVisualFromFBConfig")]
public partial XVisualInfo * GetVisualFromFBConfig(IntPtr dpy, IntPtr config);
[GlEntryPointAttribute("glXGetFBConfigAttrib")]
public GlxGetFBConfigAttrib GetFBConfigAttrib { get; }
public delegate int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value);
[GetProcAddress("glXGetFBConfigAttrib")]
public partial int GetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value);
[GlEntryPointAttribute("glXSwapBuffers")]
public GlxSwapBuffers SwapBuffers { get; }
public delegate void GlxSwapBuffers(IntPtr dpy, IntPtr drawable);
[GetProcAddress("glXSwapBuffers")]
public partial void SwapBuffers(IntPtr dpy, IntPtr drawable);
[GlEntryPointAttribute("glXWaitX")]
public GlxWaitX WaitX { get; }
public delegate void GlxWaitX();
[GetProcAddress("glXWaitX")]
public partial void WaitX();
[GlEntryPointAttribute("glXWaitGL")]
public GlxWaitGL WaitGL { get; }
public delegate void GlxWaitGL();
[GetProcAddress("glXWaitGL")]
public partial void WaitGL();
public delegate int GlGetError();
[GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
public delegate IntPtr GlxQueryExtensionsString(IntPtr display, int screen);
[GlEntryPoint("glXQueryExtensionsString")]
public GlxQueryExtensionsString QueryExtensionsString { get; }
[GetProcAddress("glGetError")]
public partial int GlGetError();
[GetProcAddress("glXQueryExtensionsString")]
public partial IntPtr QueryExtensionsString(IntPtr display, int screen);
public GlxInterface() : base(SafeGetProcAddress)
public GlxInterface()
{
Initialize(SafeGetProcAddress);
}
// Ignores egl functions.
@ -122,10 +108,9 @@ namespace Avalonia.X11.Glx
return IntPtr.Zero;
}
return GlxConverted(proc);
return GlxGetProcAddress(proc);
}
private static readonly Func<string, IntPtr> GlxConverted = ConvertNative(GlxGetProcAddress);
public string[] GetExtensions(IntPtr display)
{

2
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs

@ -164,7 +164,7 @@ namespace Avalonia.LinuxFramebuffer.Output
else
{
Fd = open(path, 2, 0);
if(Fd != -1) throw new Win32Exception($"Couldn't open {path}");
if(Fd == -1) throw new Win32Exception($"Couldn't open {path}");
}
}

2
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs

@ -50,7 +50,7 @@ namespace Avalonia.LinuxFramebuffer.Output
}
[DllImport("libEGL.so.1")]
static extern IntPtr eglGetProcAddress(Utf8Buffer proc);
static extern IntPtr eglGetProcAddress(string proc);
private GbmBoUserDataDestroyCallbackDelegate FbDestroyDelegate;
private drmModeModeInfo _mode;

24
src/Shared/SourceGeneratorAttributes.cs

@ -14,4 +14,28 @@ namespace Avalonia.SourceGenerator
public string Namespace { get; }
public Type BaseType { get; }
}
internal class GetProcAddressAttribute : Attribute
{
public GetProcAddressAttribute(string proc)
{
}
public GetProcAddressAttribute(string proc, bool optional = false)
{
}
public GetProcAddressAttribute(bool optional)
{
}
public GetProcAddressAttribute()
{
}
}
}

28
src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs

@ -27,16 +27,13 @@ namespace Avalonia.Skia
gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderbuffer);
gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture);
var arr = new int[2];
// Generate FBO
gl.GenFramebuffers(1, arr);
_fbo = arr[0];
_fbo = gl.GenFramebuffer();
gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo);
// Create a texture to render into
gl.GenTextures(1, arr);
_texture = arr[0];
_texture = gl.GenTexture();
gl.BindTexture(GL_TEXTURE_2D, _texture);
gl.TexImage2D(GL_TEXTURE_2D, 0,
InternalFormat, pixelSize.Width, pixelSize.Height,
@ -48,8 +45,7 @@ namespace Avalonia.Skia
var success = false;
foreach (var useStencil8 in TrueFalse)
{
gl.GenRenderbuffers(1, arr);
_depthStencil = arr[0];
_depthStencil = gl.GenRenderbuffer();
gl.BindRenderbuffer(GL_RENDERBUFFER, _depthStencil);
if (useStencil8)
@ -73,7 +69,7 @@ namespace Avalonia.Skia
else
{
gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer);
gl.DeleteRenderbuffers(1, arr);
gl.DeleteRenderbuffer(_depthStencil);
}
}
@ -83,10 +79,8 @@ namespace Avalonia.Skia
if (!success)
{
arr[0] = _fbo;
gl.DeleteFramebuffers(1, arr);
arr[0] = _texture;
gl.DeleteTextures(1, arr);
gl.DeleteFramebuffer(_fbo);
gl.DeleteTexture(_texture);
throw new OpenGlException("Unable to create FBO with stencil");
}
@ -94,7 +88,7 @@ namespace Avalonia.Skia
new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat()));
Surface = SKSurface.Create(_grContext, target,
surfaceOrigin, SKColorType.Rgba8888);
CanBlit = gl.BlitFramebuffer != null;
CanBlit = gl.IsBlitFramebufferAvailable;
}
public void Dispose()
@ -106,9 +100,9 @@ namespace Avalonia.Skia
var gl = _glContext.GlInterface;
if (_fbo != 0)
{
gl.DeleteFramebuffers(1, new[] { _fbo });
gl.DeleteTextures(1, new[] { _texture });
gl.DeleteRenderbuffers(1, new[] { _depthStencil });
gl.DeleteFramebuffer(_fbo);
gl.DeleteTexture(_texture);
gl.DeleteRenderbuffer(_depthStencil);
_fbo = _texture = _depthStencil = 0;
}
}

2
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs

@ -54,7 +54,7 @@ namespace Avalonia.Skia
return null;
// Blit feature requires glBlitFramebuffer
if (_glContext.GlInterface.BlitFramebuffer == null)
if (!_glContext.GlInterface.IsBlitFramebufferAvailable)
return null;
size = new PixelSize(Math.Max(size.Width, 1), Math.Max(size.Height, 1));

11
src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs

@ -101,7 +101,7 @@ namespace Avalonia.Skia
private bool _disposed;
private readonly DisposableLock _lock = new DisposableLock();
public SharedOpenGlBitmapAttachment(GlOpenGlBitmapImpl bitmap, IGlContext context, Action presentCallback)
public unsafe SharedOpenGlBitmapAttachment(GlOpenGlBitmapImpl bitmap, IGlContext context, Action presentCallback)
{
_bitmap = bitmap;
_context = context;
@ -119,7 +119,8 @@ namespace Avalonia.Skia
var gl = _context.GlInterface;
var textures = new int[2];
gl.GenTextures(2, textures);
fixed (int* ptex = textures)
gl.GenTextures(2, ptex);
_texture = textures[0];
_frontBuffer = textures[1];
@ -178,7 +179,7 @@ namespace Avalonia.Skia
_presentCallback();
}
public void Dispose()
public unsafe void Dispose()
{
var gl = _context.GlInterface;
_bitmap.Present(null);
@ -191,7 +192,9 @@ namespace Avalonia.Skia
if(_disposed)
return;
_disposed = true;
gl.DeleteTextures(2, new[] { _texture, _frontBuffer });
var tex = new[] { _texture, _frontBuffer };
fixed (int* ptex = tex)
gl.DeleteTextures(2, ptex);
}
}

52
src/iOS/Avalonia.iOS/LayerFbo.cs

@ -10,12 +10,12 @@ namespace Avalonia.iOS
private readonly EAGLContext _context;
private readonly GlInterface _gl;
private readonly CAEAGLLayer _layer;
private int[] _framebuffer;
private int[] _renderbuffer;
private int[] _depthBuffer;
private int _framebuffer;
private int _renderbuffer;
private int _depthBuffer;
private bool _disposed;
private LayerFbo(EAGLContext context, GlInterface gl, CAEAGLLayer layer, int[] framebuffer, int[] renderbuffer, int[] depthBuffer)
private LayerFbo(EAGLContext context, GlInterface gl, CAEAGLLayer layer, int framebuffer, int renderbuffer, int depthBuffer)
{
_context = context;
_gl = gl;
@ -30,42 +30,36 @@ namespace Avalonia.iOS
if (context != EAGLContext.CurrentContext)
return null;
var fb = new int[2];
var rb = new int[2];
var db = new int[2];
gl.GenRenderbuffers(1, rb);
gl.BindRenderbuffer(GlConsts.GL_RENDERBUFFER, rb[0]);
var rb = gl.GenRenderbuffer();
gl.BindRenderbuffer(GlConsts.GL_RENDERBUFFER, rb);
context.RenderBufferStorage(GlConsts.GL_RENDERBUFFER, layer);
gl.GenFramebuffers(1, fb);
gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, fb[0]);
gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_COLOR_ATTACHMENT0, GlConsts.GL_RENDERBUFFER, rb[0]);
int[] w = new int[1];
int[] h = new int[1];
gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_WIDTH, w);
gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_HEIGHT, h);
var fb = gl.GenFramebuffer();
gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, fb);
gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_COLOR_ATTACHMENT0, GlConsts.GL_RENDERBUFFER, rb);
gl.GenRenderbuffers(1, db);
gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_WIDTH, out var w);
gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_HEIGHT, out var h);
var db = gl.GenRenderbuffer();
//GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, depthBuffer);
//GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferInternalFormat.DepthComponent16, w, h);
gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_DEPTH_ATTACHMENT, GlConsts.GL_RENDERBUFFER, db[0]);
gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_DEPTH_ATTACHMENT, GlConsts.GL_RENDERBUFFER, db);
var frameBufferError = gl.CheckFramebufferStatus(GlConsts.GL_FRAMEBUFFER);
if(frameBufferError != GlConsts.GL_FRAMEBUFFER_COMPLETE)
{
gl.DeleteFramebuffers(1, fb);
gl.DeleteRenderbuffers(1, db);
gl.DeleteRenderbuffers(1, rb);
gl.DeleteFramebuffer(fb);
gl.DeleteRenderbuffer(db);
gl.DeleteRenderbuffer(rb);
return null;
}
return new LayerFbo(context, gl, layer, fb, rb, db)
{
Width = w[0],
Height = h[0]
Width = w,
Height = h
};
}
@ -74,7 +68,7 @@ namespace Avalonia.iOS
public void Bind()
{
_gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, _framebuffer[0]);
_gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, _framebuffer);
}
public void Present()
@ -88,9 +82,9 @@ namespace Avalonia.iOS
if(_disposed)
return;
_disposed = true;
_gl.DeleteFramebuffers(1, _framebuffer);
_gl.DeleteRenderbuffers(1, _depthBuffer);
_gl.DeleteRenderbuffers(1, _renderbuffer);
_gl.DeleteFramebuffer(_framebuffer);
_gl.DeleteRenderbuffer(_depthBuffer);
_gl.DeleteRenderbuffer(_renderbuffer);
if (_context != EAGLContext.CurrentContext)
throw new InvalidOperationException("Associated EAGLContext is not current");
}

338
src/tools/DevGenerators/GetProcAddressInitialization.cs

@ -0,0 +1,338 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Generator;
[Generator(LanguageNames.CSharp)]
public class GetProcAddressInitializationGenerator : IIncrementalGenerator
{
const string GetProcAddressFullName = "global::Avalonia.SourceGenerator.GetProcAddressAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var allMethodsWithAttributes = context.SyntaxProvider
.CreateSyntaxProvider(
static (s, _) => s is MethodDeclarationSyntax
{
AttributeLists.Count: > 0,
} md && md.Modifiers.Any(m=>m.IsKind(SyntaxKind.PartialKeyword)),
static (context, _) =>
(IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!);
var fieldsWithAttribute = allMethodsWithAttributes
.Where(s => s.HasAttributeWithFullyQualifiedName(GetProcAddressFullName));
var all = fieldsWithAttribute.Collect();
context.RegisterSourceOutput(all, static (context, methods) =>
{
foreach (var typeGroup in methods.GroupBy(f => f.ContainingType))
{
var nextContext = 0;
var contexts = new Dictionary<string, int>();
string GetContextNameFromIndex(int c) => "context" + (c == 0 ? "" : c);
string GetContextName(string type)
{
if (contexts.TryGetValue(type, out var idx))
return GetContextNameFromIndex(idx);
if (nextContext != 0)
idx += nextContext;
nextContext++;
return GetContextNameFromIndex(contexts[type] = idx);
}
var classBuilder = new StringBuilder();
if (typeGroup.Key.ContainingNamespace != null)
classBuilder
.AppendLine("using System;")
.Append("namespace ")
.Append(typeGroup.Key.ContainingNamespace)
.AppendLine(";");
classBuilder
.Append("unsafe partial class ")
.AppendLine(typeGroup.Key.Name)
.AppendLine("{");
var initializeBody = new StringBuilder()
.Pad(2)
.AppendLine("var addr = IntPtr.Zero;");
foreach (var method in typeGroup)
{
var isOptional = false;
var first = true;
var fieldName = "_addr_" + method.Name;
var delegateType = BuildDelegateType(method);
void AppendNextAddr()
{
if (first)
{
first = false;
initializeBody.Pad(2);
}
else
initializeBody
.Pad(2)
.Append("if(addr == IntPtr.Zero) ");
}
initializeBody
.Pad(2).Append("// Initializing ").AppendLine(method.Name)
.Pad(2)
.AppendLine("addr = IntPtr.Zero;");
foreach (var attr in method.GetAttributes())
{
if (attr.AttributeClass?.HasFullyQualifiedName(GetProcAddressFullName) == true)
{
string? primaryName = null;
foreach (var arg in attr.ConstructorArguments)
{
if (arg.Value is string name)
primaryName = name;
if (arg.Value is bool opt)
isOptional = opt;
}
if (primaryName != null)
{
AppendNextAddr();
initializeBody
.Append("addr = getProcAddress(\"")
.Append(primaryName)
.AppendLine("\");");
}
}
else
{
if (attr.AttributeClass != null
&& attr.AttributeClass.MemberNames.Contains("GetProcAddress"))
{
var getProcMethod = attr.AttributeClass.GetMembers()
.FirstOrDefault(m => m.Name == "GetProcAddress") as IMethodSymbol;
if (getProcMethod == null || !getProcMethod.IsStatic || getProcMethod.Parameters.Length < 2)
continue;
var contextName =
GetContextName(getProcMethod
.Parameters[1].Type
.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
AppendNextAddr();
initializeBody
.Append("addr = ")
.Append(attr.AttributeClass.GetFullyQualifiedName())
.Append(".GetProcAddress(")
.Append("getProcAddress, ")
.Append(contextName);
var syntaxNode = (AttributeSyntax)attr.ApplicationSyntaxReference.GetSyntax();
foreach (var arg in syntaxNode.ArgumentList.Arguments)
initializeBody.Append(", ").Append(arg.GetText());
initializeBody.AppendLine(");");
}
}
}
if (!isOptional)
{
initializeBody
.Pad(2)
.Append("if (addr == IntPtr.Zero) throw new System.EntryPointNotFoundException(\"")
.Append(fieldName).AppendLine("\");");
}
initializeBody
.Pad(2)
.Append(fieldName)
.Append(" = (")
.Append(delegateType)
.AppendLine(")addr;");
classBuilder
.Pad(1)
.Append(delegateType);
classBuilder
.Append(fieldName)
.AppendLine(";");
classBuilder
.Pad(1)
.Append("public partial ")
.Append(method.ReturnType.GetFullyQualifiedName())
.Append(" ")
.Append(method.Name)
.Append("(");
var firstArg = true;
foreach (var p in method.Parameters)
{
if (firstArg)
firstArg = false;
else
classBuilder.Append(", ");
AppendRefKind(classBuilder, p.RefKind);
classBuilder
.Append(p.Type.GetFullyQualifiedName())
.Append(" @")
.Append(p.Name);
}
classBuilder
.AppendLine(")")
.Pad(1)
.AppendLine("{");
if (isOptional)
classBuilder
.Pad(2)
.Append("if (")
.Append(fieldName)
.Append(" == null) throw new System.EntryPointNotFoundException(\"")
.Append(method.Name)
.AppendLine("\");");
foreach(var p in method.Parameters)
if (NeedsPin(p.Type))
classBuilder.Pad(2)
.Append("fixed(")
.Append(MapToNative(p.Type))
.Append(" @__p_")
.Append(p.Name)
.Append(" = ")
.Append(p.Name)
.AppendLine(")");
classBuilder.Pad(2);
if (!method.ReturnsVoid)
classBuilder.Append("return ");
var invokeBuilder = new StringBuilder();
invokeBuilder
.Append(fieldName)
.Append("(");
firstArg = true;
foreach (var p in method.Parameters)
{
if (firstArg)
firstArg = false;
else
invokeBuilder.Append(", ");
AppendRefKind(invokeBuilder, p.RefKind);
invokeBuilder
.Append("@")
.Append(ConvertToNative(p.Name, p.Type));
}
invokeBuilder.Append(")");
classBuilder.Append(ConvertToManaged(method.ReturnType, invokeBuilder.ToString()));
classBuilder.AppendLine(";").Pad(1).AppendLine("}");
if (isOptional)
classBuilder
.Pad(1)
.Append("public bool Is")
.Append(method.Name)
.Append("Available => ")
.Append(fieldName)
.AppendLine(" != null;");
}
classBuilder
.Pad(1)
.Append("void Initialize(Func<string, IntPtr> getProcAddress");
foreach (var kv in contexts.OrderBy(x => x.Value))
{
classBuilder
.Append(", ")
.Append(kv.Key)
.Append(" ")
.Append(GetContextNameFromIndex(kv.Value));
}
classBuilder.AppendLine(")").Pad(1).AppendLine("{");
classBuilder.Append(initializeBody.ToString());
classBuilder.Append("}\n}");
context.AddSource(typeGroup.Key.GetFullyQualifiedName().Replace(":", ""), classBuilder.ToString());
}
});
}
static StringBuilder AppendRefKind(StringBuilder sb, RefKind kind)
{
if (kind == RefKind.Ref)
sb.Append("ref ");
if (kind == RefKind.Out)
sb.Append("out ");
return sb;
}
static bool NeedsPin(ITypeSymbol type)
{
if (type.TypeKind == TypeKind.Array)
return true;
return false;
}
static string ConvertToNative(string name, ITypeSymbol type)
{
if (NeedsPin(type))
return "__p_" + name;
if (IsBool(type))
return $"{name} ? 1 : 0";
return name;
}
static string ConvertToManaged(ITypeSymbol type, string expr)
{
if (IsBool(type))
return expr + " != 0";
return expr;
}
static bool IsBool(ITypeSymbol type) => type.GetFullyQualifiedName() == "global::System.Boolean" ||
type.GetFullyQualifiedName() == "bool";
static string MapToNative(ITypeSymbol type)
{
if (type.TypeKind == TypeKind.Array)
return ((IArrayTypeSymbol)type).ElementType.GetFullyQualifiedName() + "*";
if (IsBool(type))
return "int";
return type.GetFullyQualifiedName();
}
static string BuildDelegateType(IMethodSymbol method)
{
StringBuilder name = new("delegate* unmanaged[Stdcall]<");
var firstArg = true;
void AppendArg(string a, RefKind kind)
{
if (firstArg)
firstArg = false;
else
name.Append(",");
AppendRefKind(name, kind);
name.Append(a);
}
foreach (var p in method.Parameters)
{
AppendArg(MapToNative(p.Type), p.RefKind);
}
AppendArg(MapToNative(method.ReturnType), RefKind.None);
name.Append(">");
return name.ToString();
}
}

31
src/tools/DevGenerators/Helpers.cs

@ -0,0 +1,31 @@
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace Generator;
static class Helpers
{
public static StringBuilder Pad(this StringBuilder sb, int count) => sb.Append(' ', count * 4);
public static string GetFullyQualifiedName(this ISymbol symbol)
{
return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
public static bool HasFullyQualifiedName(this ISymbol symbol, string name)
{
return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == name;
}
public static bool HasAttributeWithFullyQualifiedName(this ISymbol symbol, string name)
{
ImmutableArray<AttributeData> attributes = symbol.GetAttributes();
foreach (AttributeData attribute in attributes)
if (attribute.AttributeClass?.HasFullyQualifiedName(name) == true)
return true;
return false;
}
}

BIN
tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf

Binary file not shown.

4
tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs

@ -15,6 +15,8 @@ namespace Avalonia.Skia.UnitTests.Media
private readonly Typeface _defaultTypeface =
new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono");
private readonly Typeface _arabicTypeface =
new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Kufi Arabic");
private readonly Typeface _italicTypeface =
new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans", FontStyle.Italic);
private readonly Typeface _emojiTypeface =
@ -22,7 +24,7 @@ namespace Avalonia.Skia.UnitTests.Media
public CustomFontManagerImpl()
{
_customTypefaces = new[] { _emojiTypeface, _italicTypeface, _defaultTypeface };
_customTypefaces = new[] { _emojiTypeface, _italicTypeface, _arabicTypeface, _defaultTypeface };
_defaultFamilyName = _defaultTypeface.FontFamily.FamilyNames.PrimaryFamilyName;
}

8
tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs

@ -33,15 +33,11 @@ namespace Avalonia.Skia.UnitTests.Media
{
var fontManager = new FontManagerImpl();
//we need to have a valid font name different from the default one
string fontName = fontManager.GetInstalledFontFamilyNames().First();
var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface(
new Typeface(new FontFamily($"A, B, {fontName}"), weight: FontWeight.Bold));
new Typeface(new FontFamily($"A, B, Arial"), weight: FontWeight.Bold));
var skTypeface = glyphTypeface.Typeface;
Assert.Equal(fontName, skTypeface.FamilyName);
Assert.True(skTypeface.FontWeight >= 600);
}

129
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -154,7 +154,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
j += inner.Current.Text.Length;
if(j + i > text.Length)
if (j + i > text.Length)
{
break;
}
@ -738,7 +738,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine = layout.TextLines[0];
var start = textLine.GetDistanceFromCharacterHit(new CharacterHit(5, 1));
var end = textLine.GetDistanceFromCharacterHit(new CharacterHit(6, 1));
var rects = layout.HitTestTextRange(0, 7).ToArray();
@ -746,7 +746,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(1, rects.Length);
var expected = rects[0];
Assert.Equal(expected.Left, start);
Assert.Equal(expected.Right, end);
}
@ -818,11 +818,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var expected = text.Substring(textLine.FirstTextSourceIndex, textLine.Length);
Assert.Equal(expected, actual);
}
}
}
}
}
[Fact]
public void Should_Layout_Empty_String()
{
@ -833,11 +833,128 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Typeface.Default,
12,
Brushes.Black);
Assert.True(layout.Bounds.Height > 0);
}
}
[Fact]
public void Should_HitTestPoint_RightToLeft()
{
using (Start())
{
var text = "אאא AAA";
var layout = new TextLayout(
text,
Typeface.Default,
12,
Brushes.Black,
flowDirection: FlowDirection.RightToLeft);
var firstRun = layout.TextLines[0].TextRuns[0] as ShapedTextCharacters;
var hit = layout.HitTestPoint(new Point());
Assert.Equal(4, hit.TextPosition);
var currentX = 0.0;
for (var i = 0; i < firstRun.GlyphRun.GlyphClusters.Count; i++)
{
var cluster = firstRun.GlyphRun.GlyphClusters[i];
var advance = firstRun.GlyphRun.GlyphAdvances[i];
hit = layout.HitTestPoint(new Point(currentX, 0));
Assert.Equal(cluster, hit.TextPosition);
var hitRange = layout.HitTestTextRange(hit.TextPosition, 1);
var distance = hitRange.First().Left;
Assert.Equal(currentX, distance);
currentX += advance;
}
var secondRun = layout.TextLines[0].TextRuns[1] as ShapedTextCharacters;
hit = layout.HitTestPoint(new Point(firstRun.Size.Width, 0));
Assert.Equal(7, hit.TextPosition);
hit = layout.HitTestPoint(new Point(layout.TextLines[0].WidthIncludingTrailingWhitespace, 0));
Assert.Equal(0, hit.TextPosition);
currentX = firstRun.Size.Width + 0.5;
for (var i = 0; i < secondRun.GlyphRun.GlyphClusters.Count; i++)
{
var cluster = secondRun.GlyphRun.GlyphClusters[i];
var advance = secondRun.GlyphRun.GlyphAdvances[i];
hit = layout.HitTestPoint(new Point(currentX, 0));
Assert.Equal(cluster, hit.CharacterHit.FirstCharacterIndex);
var hitRange = layout.HitTestTextRange(hit.CharacterHit.FirstCharacterIndex, hit.CharacterHit.TrailingLength);
var distance = hitRange.First().Left + 0.5;
Assert.Equal(currentX, distance);
currentX += advance;
}
}
}
[Fact]
public void Should_Get_CharacterHit_From_Distance_RTL()
{
using (Start())
{
var text = "أَبْجَدِيَّة عَرَبِيَّة";
var layout = new TextLayout(
text,
Typeface.Default,
12,
Brushes.Black);
var textLine = layout.TextLines[0];
var firstRun = (ShapedTextCharacters)textLine.TextRuns[0];
var firstCluster = firstRun.ShapedBuffer.GlyphClusters[0];
var characterHit = textLine.GetCharacterHitFromDistance(0);
Assert.Equal(firstCluster, characterHit.FirstCharacterIndex);
Assert.Equal(text.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength);
var distance = textLine.GetDistanceFromCharacterHit(characterHit);
Assert.Equal(0, distance);
distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(characterHit.FirstCharacterIndex));
var firstAdvance = firstRun.ShapedBuffer.GlyphAdvances[0];
Assert.Equal(firstAdvance, distance, 5);
var rect = layout.HitTestTextPosition(22);
Assert.Equal(firstAdvance, rect.Left, 5);
rect = layout.HitTestTextPosition(23);
Assert.Equal(0, rect.Left, 5);
}
}
private static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface

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

@ -867,28 +867,29 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textBounds = textLine.GetTextBounds(0, 4);
var firstRun = textLine.TextRuns[1] as ShapedTextCharacters;
var secondRun = textLine.TextRuns[1] as ShapedTextCharacters;
Assert.Equal(1, textBounds.Count);
Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(4, 3);
var secondRun = textLine.TextRuns[0] as ShapedTextCharacters;
var firstRun = textLine.TextRuns[0] as ShapedTextCharacters;
Assert.Equal(1, textBounds.Count);
Assert.Equal(3, textBounds[0].TextRunBounds.Sum(x=> x.Length));
Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width));
textBounds = textLine.GetTextBounds(0, 5);
Assert.Equal(2, textBounds.Count);
Assert.Equal(5, textBounds.Sum(x=> x.TextRunBounds.Sum(x => x.Length)));
Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width);
Assert.Equal(7.201171875, textBounds[1].Rectangle.Width);
Assert.Equal(textLine.Start + 7.201171875, textBounds[1].Rectangle.Right);
Assert.Equal(secondRun.Size.Width, textBounds[1].Rectangle.Width);
Assert.Equal(7.201171875, textBounds[0].Rectangle.Width);
Assert.Equal(textLine.Start + 7.201171875, textBounds[0].Rectangle.Right);
Assert.Equal(textLine.Start + firstRun.Size.Width, textBounds[1].Rectangle.Left);
textBounds = textLine.GetTextBounds(0, text.Length);
@ -896,7 +897,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(7, textBounds.Sum(x => x.TextRunBounds.Sum(x => x.Length)));
Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width));
}
}
}
private class FixedRunsTextSource : ITextSource
{

Loading…
Cancel
Save