Browse Source

Merge branch 'master' into datagrid-control-theme

pull/8568/head
Max Katz 4 years ago
committed by GitHub
parent
commit
df98020d1d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      samples/BindingDemo/App.xaml
  2. 2
      samples/BindingDemo/BindingDemo.csproj
  3. 1
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  4. 44
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  5. 2
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  6. 8
      samples/RenderDemo/App.xaml
  7. 109
      src/Avalonia.Base/Media/GlyphRun.cs
  8. 2
      src/Avalonia.Base/Media/TextDecoration.cs
  9. 51
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  10. 433
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  11. 4
      src/Avalonia.Base/Platform/IGeometryImpl.cs
  12. 4
      src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml
  13. 25
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  14. 23
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
  15. 346
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml
  16. 100
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml
  17. 165
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  18. 76
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml
  19. 9
      src/Avalonia.Controls/Documents/LineBreak.cs
  20. 13
      src/Avalonia.Controls/Platform/PlatformManager.cs
  21. 11
      src/Avalonia.Controls/ProgressBar.cs
  22. 119
      src/Avalonia.Controls/RichTextBlock.cs
  23. 7
      src/Avalonia.Controls/TextBlock.cs
  24. 285
      src/Avalonia.Controls/TextBox.cs
  25. 4
      src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs
  26. 1
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  27. 14
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  28. 2
      src/Avalonia.OpenGL/Egl/EglContext.cs
  29. 4
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  30. 225
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  31. 44
      src/Avalonia.OpenGL/GlBasicInfoInterface.cs
  32. 103
      src/Avalonia.OpenGL/GlEntryPointAttribute.cs
  33. 494
      src/Avalonia.OpenGL/GlInterface.cs
  34. 79
      src/Avalonia.OpenGL/GlInterfaceBase.cs
  35. 10
      src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml
  36. 1
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  37. 1
      src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
  38. 14
      src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml
  39. 2
      src/Avalonia.X11/Avalonia.X11.csproj
  40. 111
      src/Avalonia.X11/Glx/Glx.cs
  41. 3
      src/Avalonia.X11/X11Platform.cs
  42. 23
      src/Avalonia.X11/X11Window.cs
  43. 14
      src/Avalonia.X11/XLib.cs
  44. 27
      src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs
  45. 2
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  46. 2
      src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs
  47. 8
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
  48. 51
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  49. 24
      src/Shared/SourceGeneratorAttributes.cs
  50. 28
      src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs
  51. 2
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  52. 11
      src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
  53. 3
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  54. 7
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  55. 16
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
  56. 52
      src/iOS/Avalonia.iOS/LayerFbo.cs
  57. 338
      src/tools/DevGenerators/GetProcAddressInitialization.cs
  58. 31
      src/tools/DevGenerators/Helpers.cs
  59. 22
      tests/Avalonia.IntegrationTests.Appium/MenuTests.cs
  60. BIN
      tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf
  61. 4
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  62. 8
      tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs
  63. 129
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  64. 17
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

4
samples/BindingDemo/App.xaml

@ -3,7 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BindingDemo.App">
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
</Application.Styles>
</Application>
</Application>

2
samples/BindingDemo/BindingDemo.csproj

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup>

1
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -9,7 +9,6 @@
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<AndroidPackageFormat>apk</AndroidPackageFormat>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
<RuntimeIdentifiers>android-arm64;android-x64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<AndroidResource Include="..\..\build\Assets\Icon.png">

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>

8
samples/RenderDemo/App.xaml

@ -3,6 +3,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</Application.Styles>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

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

4
src/Avalonia.Base/Platform/IGeometryImpl.cs

@ -38,8 +38,8 @@ namespace Avalonia.Platform
/// Intersects the geometry with another geometry.
/// </summary>
/// <param name="geometry">The other geometry.</param>
/// <returns>A new <see cref="IGeometryImpl"/> representing the intersection.</returns>
IGeometryImpl Intersect(IGeometryImpl geometry);
/// <returns>A new <see cref="IGeometryImpl"/> representing the intersection or <c>null</c> when the operation failed.</returns>
IGeometryImpl? Intersect(IGeometryImpl geometry);
/// <summary>
/// Indicates whether the geometry's stroke contains the specified point.

4
src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml

@ -37,4 +37,8 @@
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml" />
<!-- Controls -->
<!-- Note the ColorPicker and ColorView are unsupported in the default theme -->
<!-- These controls depend on fluent styles for TabControl, Button, TextBox, etc. -->
</Styles>

25
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@ -1,14 +1,13 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls"
x:CompileBindings="True">
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls"
x:CompileBindings="True">
<Styles.Resources>
<!-- This must follow OverlayCornerRadius -->
<CornerRadius x:Key="TopOverlayCornerRadius">5,5,0,0</CornerRadius>
</Styles.Resources>
<!-- This must follow OverlayCornerRadius -->
<CornerRadius x:Key="TopOverlayCornerRadius">5,5,0,0</CornerRadius>
<Style Selector="ColorPicker">
<ControlTheme x:Key="{x:Type ColorPicker}"
TargetType="ColorPicker">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Height" Value="32" />
<Setter Property="Width" Value="64" />
@ -80,12 +79,12 @@
</DropDownButton>
</ControlTemplate>
</Setter>
</Style>
</ControlTheme>
<!-- Adjust Background within Flyout -->
<!-- Note: This is implemented but there seems to be an issue and the selector can't match across the Flyout -->
<Style Selector="ColorPicker /template/ ColorView#FlyoutColorView /template/ Border#TabBackgroundBorder">
<!--<Style Selector="ColorPicker /template/ ColorView#FlyoutColorView /template/ Border#TabBackgroundBorder">
<Setter Property="CornerRadius" Value="{DynamicResource TopOverlayCornerRadius}" />
</Style>
</Style>-->
</Styles>
</ResourceDictionary>

23
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml

@ -1,15 +1,14 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pc="using:Avalonia.Controls.Primitives.Converters"
x:CompileBindings="True">
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pc="using:Avalonia.Controls.Primitives.Converters"
x:CompileBindings="True">
<Styles.Resources>
<pc:AccentColorConverter x:Key="AccentColorConverter" />
<x:Double x:Key="ColorPreviewerAccentSectionWidth">80</x:Double>
<x:Double x:Key="ColorPreviewerAccentSectionHeight">40</x:Double>
</Styles.Resources>
<pc:AccentColorConverter x:Key="AccentColorConverter" />
<x:Double x:Key="ColorPreviewerAccentSectionWidth">80</x:Double>
<x:Double x:Key="ColorPreviewerAccentSectionHeight">40</x:Double>
<Style Selector="ColorPreviewer">
<ControlTheme x:Key="{x:Type ColorPreviewer}"
TargetType="ColorPreviewer">
<Setter Property="Height" Value="70" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template">
@ -97,6 +96,6 @@
</Panel>
</ControlTemplate>
</Setter>
</Style>
</ControlTheme>
</Styles>
</ResourceDictionary>

346
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml

@ -1,188 +1,190 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Style Selector="Thumb.ColorSliderThumbStyle">
<Setter Property="BorderThickness" Value="0" />
<ControlTheme x:Key="ColorSliderThumbTheme"
TargetType="Thumb">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="BorderThickness" Value="3" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="10" />
CornerRadius="{TemplateBinding CornerRadius}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ControlTheme>
<Style Selector="ColorSlider:horizontal">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Height" Value="20" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
Orientation="Horizontal">
<Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.IncreaseButton>
<Thumb Classes="ColorSliderThumbStyle"
Name="ColorSliderThumb"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Height}" />
</Track>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<ControlTheme x:Key="{x:Type ColorSlider}"
TargetType="ColorSlider">
<Style Selector="ColorSlider:vertical">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Width" Value="20" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
Orientation="Vertical">
<Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.IncreaseButton>
<Thumb Classes="ColorSliderThumbStyle"
Name="ColorSliderThumb"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"
Height="{TemplateBinding Width}"
Width="{TemplateBinding Width}" />
</Track>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="^:horizontal">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Height" Value="20" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
Orientation="Horizontal">
<Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Height}" />
</Track>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<!-- Normal State -->
<Style Selector="ColorSlider /template/ Thumb.ColorSliderThumbStyle">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="BorderThickness" Value="3" />
</Style>
<Style Selector="^:vertical">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Width" Value="20" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
Orientation="Vertical">
<Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton"
Background="Transparent"
Focusable="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Border Name="FocusTarget"
Background="Transparent"
Margin="0,-10" />
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"
Height="{TemplateBinding Width}"
Width="{TemplateBinding Width}" />
</Track>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<!-- Selector/Thumb Color -->
<Style Selector="^:pointerover /template/ Thumb#ColorSliderThumb">
<Setter Property="Opacity" Value="0.75" />
</Style>
<Style Selector="^:pointerover:dark-selector /template/ Thumb#ColorSliderThumb">
<Setter Property="Opacity" Value="0.7" />
</Style>
<Style Selector="^:pointerover:light-selector /template/ Thumb#ColorSliderThumb">
<Setter Property="Opacity" Value="0.8" />
</Style>
<!-- Selector/Thumb Color -->
<Style Selector="ColorSlider:pointerover /template/ Thumb.ColorSliderThumbStyle">
<Setter Property="Opacity" Value="0.75" />
</Style>
<Style Selector="ColorSlider:pointerover:dark-selector /template/ Thumb.ColorSliderThumbStyle">
<Setter Property="Opacity" Value="0.7" />
</Style>
<Style Selector="ColorSlider:pointerover:light-selector /template/ Thumb.ColorSliderThumbStyle">
<Setter Property="Opacity" Value="0.8" />
</Style>
<Style Selector="^:dark-selector /template/ Thumb#ColorSliderThumb">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" />
</Style>
<Style Selector="^:light-selector /template/ Thumb#ColorSliderThumb">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" />
</Style>
<Style Selector="ColorSlider:dark-selector /template/ Thumb.ColorSliderThumbStyle">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" />
</Style>
<Style Selector="ColorSlider:light-selector /template/ Thumb.ColorSliderThumbStyle">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" />
</Style>
</ControlTheme>
</Styles>
</ResourceDictionary>

100
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml

@ -1,9 +1,10 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls"
x:CompileBindings="True">
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls"
x:CompileBindings="True">
<Style Selector="ColorSpectrum">
<ControlTheme x:Key="{x:Type ColorSpectrum}"
TargetType="ColorSpectrum">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ColorSpectrum}">
@ -79,53 +80,54 @@
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Normal -->
<!-- Separating this allows easier customization in applications -->
<Style Selector="ColorSpectrum /template/ Ellipse#BorderEllipse,
ColorSpectrum /template/ Rectangle#BorderRectangle">
<Setter Property="Stroke" Value="{DynamicResource SystemControlForegroundListLowBrush}" />
<Setter Property="StrokeThickness" Value="1" />
</Style>
<!-- Normal -->
<!-- Separating this allows easier customization in applications -->
<Style Selector="^ /template/ Ellipse#BorderEllipse,
^ /template/ Rectangle#BorderRectangle">
<Setter Property="Stroke" Value="{DynamicResource SystemControlForegroundListLowBrush}" />
<Setter Property="StrokeThickness" Value="1" />
</Style>
<!-- Focus -->
<Style Selector="ColorSpectrum /template/ Ellipse#FocusEllipse">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="ColorSpectrum:focus-visible /template/ Ellipse#FocusEllipse">
<Setter Property="IsVisible" Value="True" />
</Style>
<!-- Focus -->
<Style Selector="^ /template/ Ellipse#FocusEllipse">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:focus-visible /template/ Ellipse#FocusEllipse">
<Setter Property="IsVisible" Value="True" />
</Style>
<!-- Selector Color -->
<Style Selector="ColorSpectrum /template/ Ellipse#FocusEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" />
</Style>
<Style Selector="ColorSpectrum /template/ Ellipse#SelectionEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" />
</Style>
<Style Selector="ColorSpectrum:light-selector /template/ Ellipse#FocusEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" />
</Style>
<Style Selector="ColorSpectrum:light-selector /template/ Ellipse#SelectionEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" />
</Style>
<!-- Selector Color -->
<Style Selector="^ /template/ Ellipse#FocusEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" />
</Style>
<Style Selector="^ /template/ Ellipse#SelectionEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" />
</Style>
<Style Selector="^:light-selector /template/ Ellipse#FocusEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" />
</Style>
<Style Selector="^:light-selector /template/ Ellipse#SelectionEllipse">
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" />
</Style>
<Style Selector="ColorSpectrum:pointerover /template/ Ellipse#SelectionEllipse">
<Setter Property="Opacity" Value="0.7" />
</Style>
<Style Selector="ColorSpectrum:pointerover:light-selector /template/ Ellipse#SelectionEllipse">
<Setter Property="Opacity" Value="0.8" />
</Style>
<Style Selector="^:pointerover /template/ Ellipse#SelectionEllipse">
<Setter Property="Opacity" Value="0.7" />
</Style>
<Style Selector="^:pointerover:light-selector /template/ Ellipse#SelectionEllipse">
<Setter Property="Opacity" Value="0.8" />
</Style>
<!-- Selector Size -->
<Style Selector="ColorSpectrum /template/ Panel#PART_SelectionEllipsePanel">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
</Style>
<Style Selector="ColorSpectrum:large-selector /template/ Panel#PART_SelectionEllipsePanel">
<Setter Property="Width" Value="48" />
<Setter Property="Height" Value="48" />
</Style>
<!-- Selector Size -->
<Style Selector="^ /template/ Panel#PART_SelectionEllipsePanel">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
</Style>
<Style Selector="^:large-selector /template/ Panel#PART_SelectionEllipsePanel">
<Setter Property="Width" Value="48" />
<Setter Property="Height" Value="48" />
</Style>
</Styles>
</ControlTheme>
</ResourceDictionary>

165
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@ -1,86 +1,85 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls"
xmlns:converters="using:Avalonia.Controls.Converters"
xmlns:primitives="using:Avalonia.Controls.Primitives"
xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker"
xmlns:globalization="clr-namespace:System.Globalization;assembly=mscorlib"
x:CompileBindings="True">
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls"
xmlns:converters="using:Avalonia.Controls.Converters"
xmlns:primitives="using:Avalonia.Controls.Primitives"
xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker"
xmlns:globalization="clr-namespace:System.Globalization;assembly=mscorlib"
x:CompileBindings="True">
<Styles.Resources>
<pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
<pc:ThirdComponentConverter x:Key="ThirdComponentConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<converters:ColorToHexConverter x:Key="ColorToHexConverter" />
<globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
<x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
<x:Double x:Key="ColorViewComponentLabelWidth">30</x:Double>
<x:Double x:Key="ColorViewComponentTextInputWidth">80</x:Double>
<!-- Fluent UI System Icons : ic_fluent_inking_tool_20_regular.svg -->
<PathGeometry x:Key="ColorViewSpectrumIconGeometry">
M3 2C3.27614 2 3.5 2.22386 3.5 2.5V5.5C3.5 5.77614 3.72386 6 4 6H16C16.2761 6 16.5 5.77614
16.5 5.5V2.5C16.5 2.22386 16.7239 2 17 2C17.2761 2 17.5 2.22386 17.5 2.5V5.5C17.5 6.32843
16.8284 7 16 7H15.809L12.2236 14.1708C12.0615 14.4951 11.7914 14.7431 11.4695
14.8802C11.4905 15.0808 11.5 15.2891 11.5 15.5C11.5 16.0818 11.4278 16.6623 11.2268
17.1165C11.019 17.5862 10.6266 18 10 18C9.37343 18 8.98105 17.5862 8.77323 17.1165C8.57222
16.6623 8.5 16.0818 8.5 15.5C8.5 15.2891 8.50952 15.0808 8.53051 14.8802C8.20863 14.7431
7.93851 14.4951 7.77639 14.1708L4.19098 7H4C3.17157 7 2.5 6.32843 2.5 5.5V2.5C2.5 2.22386
2.72386 2 3 2ZM9.11803 14H10.882C11.0714 14 11.2445 13.893 11.3292 13.7236L14.691
7H5.30902L8.67082 13.7236C8.75552 13.893 8.92865 14 9.11803 14ZM9.52346 15C9.50787 15.1549
9.5 15.3225 9.5 15.5C9.5 16.0228 9.56841 16.4423 9.6877 16.7119C9.8002 16.9661 9.90782 17
10 17C10.0922 17 10.1998 16.9661 10.3123 16.7119C10.4316 16.4423 10.5 16.0228 10.5
15.5C10.5 15.3225 10.4921 15.1549 10.4765 15H9.52346Z
</PathGeometry>
<!-- Fluent UI System Icons : ic_fluent_color_20_regular.svg -->
<PathGeometry x:Key="ColorViewPaletteIconGeometry">
M9.75003 6.5C10.1642 6.5 10.5 6.16421 10.5 5.75C10.5 5.33579 10.1642 5 9.75003 5C9.33582
5 9.00003 5.33579 9.00003 5.75C9.00003 6.16421 9.33582 6.5 9.75003 6.5ZM12.75 7.5C13.1642
7.5 13.5 7.16421 13.5 6.75C13.5 6.33579 13.1642 6 12.75 6C12.3358 6 12 6.33579 12 6.75C12
7.16421 12.3358 7.5 12.75 7.5ZM15.25 9C15.25 9.41421 14.9142 9.75 14.5 9.75C14.0858 9.75
13.75 9.41421 13.75 9C13.75 8.58579 14.0858 8.25 14.5 8.25C14.9142 8.25 15.25 8.58579
15.25 9ZM14.5 12.75C14.9142 12.75 15.25 12.4142 15.25 12C15.25 11.5858 14.9142 11.25 14.5
11.25C14.0858 11.25 13.75 11.5858 13.75 12C13.75 12.4142 14.0858 12.75 14.5 12.75ZM13.25
14C13.25 14.4142 12.9142 14.75 12.5 14.75C12.0858 14.75 11.75 14.4142 11.75 14C11.75
13.5858 12.0858 13.25 12.5 13.25C12.9142 13.25 13.25 13.5858 13.25 14ZM13.6972
2.99169C10.9426 1.57663 8.1432 1.7124 5.77007 3.16636C4.55909 3.9083 3.25331 5.46925
2.51605 7.05899C2.14542 7.85816 1.89915 8.70492 1.90238 9.49318C1.90566 10.2941 2.16983
11.0587 2.84039 11.6053C3.45058 12.1026 3.98165 12.353 4.49574 12.3784C5.01375 12.404
5.41804 12.1942 5.73429 12.0076C5.80382 11.9666 5.86891 11.927 5.93113 11.8892C6.17332
11.7421 6.37205 11.6214 6.62049 11.5426C6.90191 11.4534 7.2582 11.4205 7.77579
11.5787C7.96661 11.637 8.08161 11.7235 8.16212 11.8229C8.24792 11.9289 8.31662 12.0774
8.36788 12.2886C8.41955 12.5016 8.44767 12.7527 8.46868 13.0491C8.47651 13.1594 8.48379
13.2855 8.49142 13.4176C8.50252 13.6098 8.51437 13.8149 8.52974 14.0037C8.58435 14.6744
8.69971 15.4401 9.10362 16.1357C9.51764 16.8488 10.2047 17.439 11.307 17.8158C12.9093
18.3636 14.3731 17.9191 15.5126 17.0169C16.6391 16.125 17.4691 14.7761 17.8842
13.4272C19.1991 9.15377 17.6728 5.03394 13.6972 2.99169ZM6.29249 4.01905C8.35686 2.75426
10.7844 2.61959 13.2403 3.88119C16.7473 5.68275 18.1135 9.28161 16.9284 13.1332C16.5624
14.3227 15.8338 15.4871 14.8919 16.2329C13.963 16.9684 12.8486 17.286 11.6305
16.8696C10.7269 16.5607 10.2467 16.1129 9.96842 15.6336C9.68001 15.1369 9.57799 14.5556
9.52644 13.9225C9.51101 13.733 9.50132 13.5621 9.49147 13.3884C9.48399 13.2564 9.47642
13.1229 9.46618 12.9783C9.44424 12.669 9.41175 12.3499 9.33968 12.0529C9.26719 11.7541
9.14902 11.4527 8.93935 11.1937C8.72439 10.9282 8.43532 10.7346 8.06801 10.6223C7.36648
10.408 6.80266 10.4359 6.31839 10.5893C5.94331 10.7082 5.62016 10.9061 5.37179
11.0582C5.31992 11.0899 5.2713 11.1197 5.22616 11.1463C4.94094 11.3146 4.75357 11.39
4.54514 11.3796C4.33279 11.3691 4.00262 11.2625 3.47218 10.8301C3.0866 10.5158 2.90473
10.0668 2.90237 9.48908C2.89995 8.89865 3.08843 8.20165 3.42324 7.47971C4.09686 6.0272
5.28471 4.63649 6.29249 4.01905Z
</PathGeometry>
<!-- Fluent UI System Icons : ic_fluent_options_20_regular.svg -->
<PathGeometry x:Key="ColorViewComponentsIconGeometry">
M14.95 5C14.7184 3.85888 13.7095 3 12.5 3C11.2905 3 10.2816 3.85888 10.05 5H2.5C2.22386
5 2 5.22386 2 5.5C2 5.77614 2.22386 6 2.5 6H10.05C10.2816 7.14112 11.2905 8 12.5 8C13.7297
8 14.752 7.11217 14.961 5.94254C14.9575 5.96177 14.9539 5.98093 14.95 6H17.5C17.7761 6 18
5.77614 18 5.5C18 5.22386 17.7761 5 17.5 5H14.95ZM12.5 7C11.6716 7 11 6.32843 11 5.5C11
4.67157 11.6716 4 12.5 4C13.3284 4 14 4.67157 14 5.5C14 6.32843 13.3284 7 12.5 7ZM9.94999
14C9.71836 12.8589 8.70948 12 7.5 12C6.29052 12 5.28164 12.8589 5.05001 14H2.5C2.22386
14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H5.05001C5.28164 16.1411 6.29052 17 7.5
17C8.70948 17 9.71836 16.1411 9.94999 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239
17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5
13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z
</PathGeometry>
</Styles.Resources>
<pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
<pc:ThirdComponentConverter x:Key="ThirdComponentConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<converters:ColorToHexConverter x:Key="ColorToHexConverter" />
<globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
<x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
<x:Double x:Key="ColorViewComponentLabelWidth">30</x:Double>
<x:Double x:Key="ColorViewComponentTextInputWidth">80</x:Double>
<!-- Fluent UI System Icons : ic_fluent_inking_tool_20_regular.svg -->
<PathGeometry x:Key="ColorViewSpectrumIconGeometry">
M3 2C3.27614 2 3.5 2.22386 3.5 2.5V5.5C3.5 5.77614 3.72386 6 4 6H16C16.2761 6 16.5 5.77614
16.5 5.5V2.5C16.5 2.22386 16.7239 2 17 2C17.2761 2 17.5 2.22386 17.5 2.5V5.5C17.5 6.32843
16.8284 7 16 7H15.809L12.2236 14.1708C12.0615 14.4951 11.7914 14.7431 11.4695
14.8802C11.4905 15.0808 11.5 15.2891 11.5 15.5C11.5 16.0818 11.4278 16.6623 11.2268
17.1165C11.019 17.5862 10.6266 18 10 18C9.37343 18 8.98105 17.5862 8.77323 17.1165C8.57222
16.6623 8.5 16.0818 8.5 15.5C8.5 15.2891 8.50952 15.0808 8.53051 14.8802C8.20863 14.7431
7.93851 14.4951 7.77639 14.1708L4.19098 7H4C3.17157 7 2.5 6.32843 2.5 5.5V2.5C2.5 2.22386
2.72386 2 3 2ZM9.11803 14H10.882C11.0714 14 11.2445 13.893 11.3292 13.7236L14.691
7H5.30902L8.67082 13.7236C8.75552 13.893 8.92865 14 9.11803 14ZM9.52346 15C9.50787 15.1549
9.5 15.3225 9.5 15.5C9.5 16.0228 9.56841 16.4423 9.6877 16.7119C9.8002 16.9661 9.90782 17
10 17C10.0922 17 10.1998 16.9661 10.3123 16.7119C10.4316 16.4423 10.5 16.0228 10.5
15.5C10.5 15.3225 10.4921 15.1549 10.4765 15H9.52346Z
</PathGeometry>
<!-- Fluent UI System Icons : ic_fluent_color_20_regular.svg -->
<PathGeometry x:Key="ColorViewPaletteIconGeometry">
M9.75003 6.5C10.1642 6.5 10.5 6.16421 10.5 5.75C10.5 5.33579 10.1642 5 9.75003 5C9.33582
5 9.00003 5.33579 9.00003 5.75C9.00003 6.16421 9.33582 6.5 9.75003 6.5ZM12.75 7.5C13.1642
7.5 13.5 7.16421 13.5 6.75C13.5 6.33579 13.1642 6 12.75 6C12.3358 6 12 6.33579 12 6.75C12
7.16421 12.3358 7.5 12.75 7.5ZM15.25 9C15.25 9.41421 14.9142 9.75 14.5 9.75C14.0858 9.75
13.75 9.41421 13.75 9C13.75 8.58579 14.0858 8.25 14.5 8.25C14.9142 8.25 15.25 8.58579
15.25 9ZM14.5 12.75C14.9142 12.75 15.25 12.4142 15.25 12C15.25 11.5858 14.9142 11.25 14.5
11.25C14.0858 11.25 13.75 11.5858 13.75 12C13.75 12.4142 14.0858 12.75 14.5 12.75ZM13.25
14C13.25 14.4142 12.9142 14.75 12.5 14.75C12.0858 14.75 11.75 14.4142 11.75 14C11.75
13.5858 12.0858 13.25 12.5 13.25C12.9142 13.25 13.25 13.5858 13.25 14ZM13.6972
2.99169C10.9426 1.57663 8.1432 1.7124 5.77007 3.16636C4.55909 3.9083 3.25331 5.46925
2.51605 7.05899C2.14542 7.85816 1.89915 8.70492 1.90238 9.49318C1.90566 10.2941 2.16983
11.0587 2.84039 11.6053C3.45058 12.1026 3.98165 12.353 4.49574 12.3784C5.01375 12.404
5.41804 12.1942 5.73429 12.0076C5.80382 11.9666 5.86891 11.927 5.93113 11.8892C6.17332
11.7421 6.37205 11.6214 6.62049 11.5426C6.90191 11.4534 7.2582 11.4205 7.77579
11.5787C7.96661 11.637 8.08161 11.7235 8.16212 11.8229C8.24792 11.9289 8.31662 12.0774
8.36788 12.2886C8.41955 12.5016 8.44767 12.7527 8.46868 13.0491C8.47651 13.1594 8.48379
13.2855 8.49142 13.4176C8.50252 13.6098 8.51437 13.8149 8.52974 14.0037C8.58435 14.6744
8.69971 15.4401 9.10362 16.1357C9.51764 16.8488 10.2047 17.439 11.307 17.8158C12.9093
18.3636 14.3731 17.9191 15.5126 17.0169C16.6391 16.125 17.4691 14.7761 17.8842
13.4272C19.1991 9.15377 17.6728 5.03394 13.6972 2.99169ZM6.29249 4.01905C8.35686 2.75426
10.7844 2.61959 13.2403 3.88119C16.7473 5.68275 18.1135 9.28161 16.9284 13.1332C16.5624
14.3227 15.8338 15.4871 14.8919 16.2329C13.963 16.9684 12.8486 17.286 11.6305
16.8696C10.7269 16.5607 10.2467 16.1129 9.96842 15.6336C9.68001 15.1369 9.57799 14.5556
9.52644 13.9225C9.51101 13.733 9.50132 13.5621 9.49147 13.3884C9.48399 13.2564 9.47642
13.1229 9.46618 12.9783C9.44424 12.669 9.41175 12.3499 9.33968 12.0529C9.26719 11.7541
9.14902 11.4527 8.93935 11.1937C8.72439 10.9282 8.43532 10.7346 8.06801 10.6223C7.36648
10.408 6.80266 10.4359 6.31839 10.5893C5.94331 10.7082 5.62016 10.9061 5.37179
11.0582C5.31992 11.0899 5.2713 11.1197 5.22616 11.1463C4.94094 11.3146 4.75357 11.39
4.54514 11.3796C4.33279 11.3691 4.00262 11.2625 3.47218 10.8301C3.0866 10.5158 2.90473
10.0668 2.90237 9.48908C2.89995 8.89865 3.08843 8.20165 3.42324 7.47971C4.09686 6.0272
5.28471 4.63649 6.29249 4.01905Z
</PathGeometry>
<!-- Fluent UI System Icons : ic_fluent_options_20_regular.svg -->
<PathGeometry x:Key="ColorViewComponentsIconGeometry">
M14.95 5C14.7184 3.85888 13.7095 3 12.5 3C11.2905 3 10.2816 3.85888 10.05 5H2.5C2.22386
5 2 5.22386 2 5.5C2 5.77614 2.22386 6 2.5 6H10.05C10.2816 7.14112 11.2905 8 12.5 8C13.7297
8 14.752 7.11217 14.961 5.94254C14.9575 5.96177 14.9539 5.98093 14.95 6H17.5C17.7761 6 18
5.77614 18 5.5C18 5.22386 17.7761 5 17.5 5H14.95ZM12.5 7C11.6716 7 11 6.32843 11 5.5C11
4.67157 11.6716 4 12.5 4C13.3284 4 14 4.67157 14 5.5C14 6.32843 13.3284 7 12.5 7ZM9.94999
14C9.71836 12.8589 8.70948 12 7.5 12C6.29052 12 5.28164 12.8589 5.05001 14H2.5C2.22386
14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H5.05001C5.28164 16.1411 6.29052 17 7.5
17C8.70948 17 9.71836 16.1411 9.94999 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239
17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5
13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z
</PathGeometry>
<Style Selector="ColorView">
<ControlTheme x:Key="{x:Type ColorView}"
TargetType="ColorView">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Palette">
<controls:FluentColorPalette />
@ -645,6 +644,6 @@
</Grid>
</ControlTemplate>
</Setter>
</Style>
</ControlTheme>
</Styles>
</ResourceDictionary>

76
src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml

@ -3,42 +3,48 @@
xmlns:converters="using:Avalonia.Controls.Converters">
<Styles.Resources>
<!-- Shared Resources -->
<VisualBrush x:Key="ColorControlCheckeredBackgroundBrush"
TileMode="Tile"
Stretch="Uniform"
DestinationRect="0,0,8,8">
<VisualBrush.Visual>
<DrawingPresenter Width="8"
Height="8">
<DrawingGroup>
<GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z"
Brush="Transparent" />
<GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z"
Brush="#19808080" />
</DrawingGroup>
</DrawingPresenter>
</VisualBrush.Visual>
</VisualBrush>
<!-- Shared Converters -->
<converters:EnumToBoolConverter x:Key="EnumToBoolConverter" />
<converters:ToBrushConverter x:Key="ToBrushConverter" />
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/>
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/>
<converters:CornerRadiusFilterConverter x:Key="TopCornerRadiusFilterConverter" Filter="TopLeft, TopRight"/>
<converters:CornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" Filter="BottomLeft, BottomRight"/>
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadiusConverter" Corner="TopLeft" />
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadiusConverter" Corner="BottomRight" />
</Styles.Resources>
<ResourceDictionary>
<!-- Shared Resources -->
<VisualBrush x:Key="ColorControlCheckeredBackgroundBrush"
TileMode="Tile"
Stretch="Uniform"
DestinationRect="0,0,8,8">
<VisualBrush.Visual>
<DrawingPresenter Width="8"
Height="8">
<DrawingGroup>
<GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z"
Brush="Transparent" />
<GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z"
Brush="#19808080" />
</DrawingGroup>
</DrawingPresenter>
</VisualBrush.Visual>
</VisualBrush>
<!-- Shared Converters -->
<converters:EnumToBoolConverter x:Key="EnumToBoolConverter" />
<converters:ToBrushConverter x:Key="ToBrushConverter" />
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/>
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/>
<converters:CornerRadiusFilterConverter x:Key="TopCornerRadiusFilterConverter" Filter="TopLeft, TopRight"/>
<converters:CornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" Filter="BottomLeft, BottomRight"/>
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadiusConverter" Corner="TopLeft" />
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadiusConverter" Corner="BottomRight" />
<!-- Primitives -->
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml" />
<ResourceDictionary.MergedDictionaries>
<!-- Controls -->
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml" />
<!-- Primitives -->
<ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml" />
<ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml" />
<ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml" />
<!-- Controls -->
<ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml" />
<ResourceInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Styles.Resources>
</Styles>

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)

13
src/Avalonia.Controls/Platform/PlatformManager.cs

@ -20,17 +20,8 @@ namespace Avalonia.Controls.Platform
{
}
public static ITrayIconImpl? CreateTrayIcon()
{
var platform = AvaloniaLocator.Current.GetService<IWindowingPlatform>();
if (platform == null)
{
throw new Exception("Could not CreateTrayIcon(): IWindowingPlatform is not registered.");
}
return s_designerMode ? null : platform.CreateTrayIcon();
}
public static ITrayIconImpl? CreateTrayIcon() =>
s_designerMode ? null : AvaloniaLocator.Current.GetService<IWindowingPlatform>()?.CreateTrayIcon();
public static IWindowImpl CreateWindow()

11
src/Avalonia.Controls/ProgressBar.cs

@ -100,6 +100,7 @@ namespace Avalonia.Controls
private double _indeterminateStartingOffset;
private double _indeterminateEndingOffset;
private Border? _indicator;
private IDisposable? _trackSizeChangedListener;
public static readonly StyledProperty<bool> IsIndeterminateProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
@ -195,8 +196,9 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
var result = base.ArrangeOverride(finalSize);
UpdateIndicator();
return base.ArrangeOverride(finalSize);
return result;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
@ -216,8 +218,15 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
// dispose any previous track size listener
_trackSizeChangedListener?.Dispose();
_indicator = e.NameScope.Get<Border>("PART_Indicator");
// listen to size changes of the indicators track (parent) and update the indicator there.
_trackSizeChangedListener = _indicator.Parent?.GetPropertyChangedObservable(BoundsProperty)
.Subscribe(_ => UpdateIndicator());
UpdateIndicator();
}

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

3
src/Avalonia.X11/X11Platform.cs

@ -277,7 +277,8 @@ namespace Avalonia
// and sometimes attempts to use GLX might cause a segfault
"llvmpipe"
};
public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication";
public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name;
/// <summary>
/// Enables multitouch support. The default value is true.

23
src/Avalonia.X11/X11Window.cs

@ -168,8 +168,7 @@ namespace Avalonia.X11
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM,
32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1);
if (platform.Options.WmClass != null)
SetWmClass(platform.Options.WmClass);
SetWmClass(_platform.Options.WmClass);
var surfaces = new List<object>
{
@ -1082,12 +1081,24 @@ namespace Avalonia.X11
public void SetWmClass(string wmClass)
{
var data = Encoding.ASCII.GetBytes(wmClass);
fixed (void* pdata = data)
// See https://tronche.com/gui/x/icccm/sec-4.html#WM_CLASS
// We don't actually parse the application's command line, so we only use RESOURCE_NAME and argv[0]
var appId = Environment.GetEnvironmentVariable("RESOURCE_NAME")
?? Process.GetCurrentProcess().ProcessName;
var encodedAppId = Encoding.ASCII.GetBytes(appId);
var encodedWmClass = Encoding.ASCII.GetBytes(wmClass ?? appId);
var hint = XAllocClassHint();
fixed(byte* pAppId = encodedAppId)
fixed (byte* pWmClass = encodedWmClass)
{
XChangeProperty(_x11.Display, _handle, _x11.Atoms.XA_WM_CLASS, _x11.Atoms.XA_STRING, 8,
PropertyMode.Replace, pdata, data.Length);
hint->res_name = pAppId;
hint->res_class = pWmClass;
XSetClassHint(_x11.Display, _handle, hint);
}
XFree(hint);
}
public void SetMinMaxSize(Size minSize, Size maxSize)

14
src/Avalonia.X11/XLib.cs

@ -109,6 +109,9 @@ namespace Avalonia.X11
[DllImport(libX11)]
public static extern int XFree(IntPtr data);
[DllImport(libX11)]
public static extern int XFree(void* data);
[DllImport(libX11)]
public static extern int XRaiseWindow(IntPtr display, IntPtr window);
@ -628,6 +631,12 @@ namespace Avalonia.X11
return XISelectEvents(display, window, emasks, devices.Count);
}
[DllImport(libX11)]
public static extern XClassHint* XAllocClassHint();
[DllImport(libX11)]
public static extern int XSetClassHint(IntPtr display, IntPtr window, XClassHint* class_hints);
public struct XGeometry
{
@ -639,6 +648,11 @@ namespace Avalonia.X11
public int bw;
public int d;
}
public struct XClassHint
{
public byte* res_name;
public byte* res_class;
}
public struct XSyncValue {
public int Hi;

27
src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs

@ -0,0 +1,27 @@
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Media;
using JetBrains.Annotations;
namespace Avalonia.LinuxFramebuffer
{
public class DrmOutputOptions
{
/// <summary>
/// Scaling factor.
/// Default: 1.0
/// </summary>
public double Scaling { get; set; } = 1.0;
/// <summary>
/// If true an two cycle buffer swapping is processed at init.
/// Default: True
/// </summary>
public bool EnableInitialBufferSwapping { get; set; } = true;
/// <summary>
/// Color for <see cref="EnableInitialBufferSwapping"/>
/// Default: R0 G0 B0 A0
/// </summary>
public Color InitialBufferSwappingColor { get; set; } = new Color(0, 0, 0, 0);
}
}

2
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -140,6 +140,8 @@ public static class LinuxFramebufferPlatformExtensions
public static int StartLinuxDrm<T>(this T builder, string[] args, string card = null, double scaling = 1)
where T : AppBuilderBase<T>, new() => StartLinuxDirect(builder, args, new DrmOutput(card) {Scaling = scaling});
public static int StartLinuxDrm<T>(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null)
where T : AppBuilderBase<T>, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options));
public static int StartLinuxDirect<T>(this T builder, string[] args, IOutputBackend backend)
where T : AppBuilderBase<T>, new()

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

@ -162,6 +162,8 @@ namespace Avalonia.LinuxFramebuffer.Output
[DllImport(libdrm, SetLastError = true)]
public static extern drmModeConnector* drmModeGetConnector(int fd, uint connector);
[DllImport(libdrm, SetLastError = true)]
public static extern drmModeConnector* drmModeGetConnectorCurrent(int fd, uint connector);
[DllImport(libdrm, SetLastError = true)]
public static extern void drmModeFreeConnector(drmModeConnector* res);
[DllImport(libdrm, SetLastError = true)]

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

@ -84,7 +84,7 @@ namespace Avalonia.LinuxFramebuffer.Output
{
public List<DrmConnector> Connectors { get; }= new List<DrmConnector>();
internal Dictionary<uint, DrmEncoder> Encoders { get; } = new Dictionary<uint, DrmEncoder>();
public DrmResources(int fd)
public DrmResources(int fd, bool connectorsForceProbe = false)
{
var res = drmModeGetResources(fd);
if (res == null)
@ -107,7 +107,7 @@ namespace Avalonia.LinuxFramebuffer.Output
for (var c = 0; c < res->count_connectors; c++)
{
var conn = drmModeGetConnector(fd, res->connectors[c]);
var conn = connectorsForceProbe ? drmModeGetConnector(fd, res->connectors[c]) : drmModeGetConnectorCurrent(fd, res->connectors[c]);
Connectors.Add(new DrmConnector(conn));
drmModeFreeConnector(conn);
}
@ -164,11 +164,11 @@ 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}");
}
}
public DrmResources GetResources() => new DrmResources(Fd);
public DrmResources GetResources(bool connectorsForceProbe = false) => new DrmResources(Fd, connectorsForceProbe);
public void Dispose()
{
close(Fd);

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

@ -7,6 +7,7 @@ using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform.Interop;
using JetBrains.Annotations;
using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods;
using static Avalonia.LinuxFramebuffer.Output.LibDrm;
using static Avalonia.LinuxFramebuffer.Output.LibDrm.GbmColorFormats;
@ -15,20 +16,35 @@ namespace Avalonia.LinuxFramebuffer.Output
{
public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface
{
private DrmOutputOptions _outputOptions = new();
private DrmCard _card;
public PixelSize PixelSize => _mode.Resolution;
public double Scaling { get; set; }
public double Scaling
{
get => _outputOptions.Scaling;
set => _outputOptions.Scaling = value;
}
public IGlContext PrimaryContext => _deferredContext;
private EglPlatformOpenGlInterface _platformGl;
public IPlatformOpenGlInterface PlatformOpenGlInterface => _platformGl;
public DrmOutput(string path = null)
public DrmOutput(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo,
DrmOutputOptions? options = null)
{
if(options != null)
_outputOptions = options;
Init(card, resources, connector, modeInfo);
}
public DrmOutput(string path = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null)
{
if(options != null)
_outputOptions = options;
var card = new DrmCard(path);
var resources = card.GetResources();
var resources = card.GetResources(connectorsForceProbe);
var connector =
resources.Connectors.FirstOrDefault(x => x.Connection == DrmModeConnection.DRM_MODE_CONNECTED);
@ -50,7 +66,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;
@ -142,9 +158,14 @@ namespace Avalonia.LinuxFramebuffer.Output
_deferredContext = _platformGl.PrimaryEglContext;
var initialBufferSwappingColorR = _outputOptions.InitialBufferSwappingColor.R / 255.0f;
var initialBufferSwappingColorG = _outputOptions.InitialBufferSwappingColor.G / 255.0f;
var initialBufferSwappingColorB = _outputOptions.InitialBufferSwappingColor.B / 255.0f;
var initialBufferSwappingColorA = _outputOptions.InitialBufferSwappingColor.A / 255.0f;
using (_deferredContext.MakeCurrent(_eglSurface))
{
_deferredContext.GlInterface.ClearColor(0, 0, 0, 0);
_deferredContext.GlInterface.ClearColor(initialBufferSwappingColorR, initialBufferSwappingColorG,
initialBufferSwappingColorB, initialBufferSwappingColorA);
_deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
_eglSurface.SwapBuffers();
}
@ -162,13 +183,17 @@ namespace Avalonia.LinuxFramebuffer.Output
_mode = mode;
_currentBo = bo;
// Go trough two cycles of buffer swapping (there are render artifacts otherwise)
for(var c=0;c<2;c++)
using (CreateGlRenderTarget().BeginDraw())
{
_deferredContext.GlInterface.ClearColor(0, 0, 0, 0);
_deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
}
if (_outputOptions.EnableInitialBufferSwapping)
{
//Go trough two cycles of buffer swapping (there are render artifacts otherwise)
for(var c=0;c<2;c++)
using (CreateGlRenderTarget().BeginDraw())
{
_deferredContext.GlInterface.ClearColor(initialBufferSwappingColorR, initialBufferSwappingColorG,
initialBufferSwappingColorB, initialBufferSwappingColorA);
_deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
}
}
}

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

3
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -57,7 +57,8 @@ namespace Avalonia.Skia
public ImmutableBitmap(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode)
{
using (var skStream = new SKManagedStream(stream))
using (var codec = SKCodec.Create(skStream))
using (var skData = SKData.Create(skStream))
using (var codec = SKCodec.Create(skData))
{
var info = codec.Info;

7
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@ -4,7 +4,6 @@ using System.Threading;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Skia.Helpers;
using Avalonia.Media.Imaging;
using SkiaSharp;
namespace Avalonia.Skia
@ -25,8 +24,9 @@ namespace Avalonia.Skia
public WriteableBitmapImpl(Stream stream)
{
using (var skiaStream = new SKManagedStream(stream))
using (var skData = SKData.Create(skiaStream))
{
_bitmap = SKBitmap.Decode(skiaStream);
_bitmap = SKBitmap.Decode(skData);
if (_bitmap == null)
{
@ -41,7 +41,8 @@ namespace Avalonia.Skia
public WriteableBitmapImpl(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode)
{
using (var skStream = new SKManagedStream(stream))
using (var codec = SKCodec.Create(skStream))
using (var skData = SKData.Create(skStream))
using (var codec = SKCodec.Create(skData))
{
var info = codec.Info;

16
src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs

@ -120,16 +120,14 @@ namespace Avalonia.Win32.WinRT.Composition
private void RunLoop()
{
using (var act = _compositor5.RequestCommitAsync())
act.SetCompleted(new RunLoopHandler(this));
while (true)
{
var st = Stopwatch.StartNew();
using (var act = _compositor5.RequestCommitAsync())
act.SetCompleted(new RunLoopHandler(this));
while (true)
{
UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0);
lock (_pumpLock)
UnmanagedMethods.DispatchMessage(ref msg);
}
UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0);
lock (_pumpLock)
UnmanagedMethods.DispatchMessage(ref msg);
}
}

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

22
tests/Avalonia.IntegrationTests.Appium/MenuTests.cs

@ -60,6 +60,8 @@ namespace Avalonia.IntegrationTests.Appium
[PlatformFact(TestPlatforms.Windows)]
public void Select_Child_With_Alt_Arrow_Keys()
{
MovePointerOutOfTheWay();
new Actions(_session)
.KeyDown(Keys.Alt).KeyUp(Keys.Alt)
.SendKeys(Keys.Down + Keys.Enter)
@ -72,6 +74,8 @@ namespace Avalonia.IntegrationTests.Appium
[PlatformFact(TestPlatforms.Windows)]
public void Select_Grandchild_With_Alt_Arrow_Keys()
{
MovePointerOutOfTheWay();
new Actions(_session)
.KeyDown(Keys.Alt).KeyUp(Keys.Alt)
.SendKeys(Keys.Down + Keys.Down + Keys.Right + Keys.Enter)
@ -84,6 +88,8 @@ namespace Avalonia.IntegrationTests.Appium
[PlatformFact(TestPlatforms.Windows)]
public void Select_Child_With_Alt_Access_Keys()
{
MovePointerOutOfTheWay();
new Actions(_session)
.KeyDown(Keys.Alt).KeyUp(Keys.Alt)
.SendKeys("rc")
@ -96,6 +102,8 @@ namespace Avalonia.IntegrationTests.Appium
[PlatformFact(TestPlatforms.Windows)]
public void Select_Grandchild_With_Alt_Access_Keys()
{
MovePointerOutOfTheWay();
new Actions(_session)
.KeyDown(Keys.Alt).KeyUp(Keys.Alt)
.SendKeys("rhg")
@ -111,6 +119,8 @@ namespace Avalonia.IntegrationTests.Appium
var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem");
rootMenuItem.SendClick();
MovePointerOutOfTheWay();
new Actions(_session)
.SendKeys(Keys.Down + Keys.Enter)
.Perform();
@ -125,6 +135,8 @@ namespace Avalonia.IntegrationTests.Appium
var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem");
rootMenuItem.SendClick();
MovePointerOutOfTheWay();
new Actions(_session)
.SendKeys(Keys.Down + Keys.Down + Keys.Right + Keys.Enter)
.Perform();
@ -159,5 +171,15 @@ namespace Avalonia.IntegrationTests.Appium
Assert.True(textBox.GetIsFocused());
}
private void MovePointerOutOfTheWay()
{
// Move the pointer to the menu tab item so that it's not over the menu in preparation
// for key press tests. This prevents the mouse accidentially selecting the wrong item
// by hovering.
var tabs = _session.FindElementByAccessibilityId("MainTabs");
var tab = tabs.FindElementByName("Menu");
tab.MovePointerOver();
}
}
}

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