diff --git a/samples/BindingDemo/App.xaml b/samples/BindingDemo/App.xaml index 9260dd280f..3e312c8685 100644 --- a/samples/BindingDemo/App.xaml +++ b/samples/BindingDemo/App.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="BindingDemo.App"> - + - \ No newline at end of file + diff --git a/samples/BindingDemo/BindingDemo.csproj b/samples/BindingDemo/BindingDemo.csproj index bd6054327f..056d3bf552 100644 --- a/samples/BindingDemo/BindingDemo.csproj +++ b/samples/BindingDemo/BindingDemo.csproj @@ -5,7 +5,7 @@ - + diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index 54acdd9114..a43ea4539a 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -9,7 +9,6 @@ 1.0 apk true - android-arm64;android-x64 diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs index cb79bf219a..f37e0f8701 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs +++ b/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 - { - 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]; - } - } } } diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml index cb49ba96c6..32914428ed 100644 --- a/samples/ControlCatalog/Pages/TextBlockPage.xaml +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml @@ -118,7 +118,7 @@ - + This is a TextBlock with several diff --git a/samples/RenderDemo/App.xaml b/samples/RenderDemo/App.xaml index 66d97a6444..8ea792de19 100644 --- a/samples/RenderDemo/App.xaml +++ b/samples/RenderDemo/App.xaml @@ -3,6 +3,12 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - + + + + + + + diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index ac87d521a5..2a7f3360ad 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -445,7 +445,7 @@ namespace Avalonia.Media /// 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(ref T field, T value) { _glyphRunImpl?.Dispose(); diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index 8eeb86c555..4c9764af96 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/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)); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index f3af240c58..f3e8b5969c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/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 /// The maximum number of text lines. 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(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 /// The current left. /// The current width. /// The current height. - 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 }; } @@ -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); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 7c686358e2..f3c62f4994 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/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 /// 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; } /// @@ -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 { currentRunBounds })); + result.Add(new TextBounds(currentRect, currentDirection, new List { 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 { currentRunBounds })); - } + result.Add(new TextBounds(currentRect, currentDirection, new List { 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); diff --git a/src/Avalonia.Base/Platform/IGeometryImpl.cs b/src/Avalonia.Base/Platform/IGeometryImpl.cs index c80f8923ef..5826cfb2ff 100644 --- a/src/Avalonia.Base/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Base/Platform/IGeometryImpl.cs @@ -38,8 +38,8 @@ namespace Avalonia.Platform /// Intersects the geometry with another geometry. /// /// The other geometry. - /// A new representing the intersection. - IGeometryImpl Intersect(IGeometryImpl geometry); + /// A new representing the intersection or null when the operation failed. + IGeometryImpl? Intersect(IGeometryImpl geometry); /// /// Indicates whether the geometry's stroke contains the specified point. diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml index ebecd65cc3..db1fa3ee4e 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml @@ -37,4 +37,8 @@ + + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index a4d5ff673f..907b00dfff 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -1,14 +1,13 @@ - + - - - 5,5,0,0 - + + 5,5,0,0 - + - + --> - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml index 74f33d1258..1c4a44d081 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml @@ -1,15 +1,14 @@ - + - - - 80 - 40 - + + 80 + 40 - + - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml index 162ac372de..eb67ada334 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml @@ -1,188 +1,190 @@ - + - + - + - + - - + + + + + + - - - - + + - - + - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml index dba4ad19f5..f95721c881 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml @@ -1,9 +1,10 @@ - + - - - - + + + - - - + + + - - - - - + + + + + - - + + - - - + + + - + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index e25e822f3f..993745b1e5 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -1,86 +1,85 @@ - + - - - - - - - 48 - 30 - 80 - - - 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 - - - - 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 - - - - 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 - - + + + + + + 48 + 30 + 80 + + + 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 + + + + 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 + + + + 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 + - + - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml index 186b6de9bc..a5a94a2322 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml @@ -3,42 +3,48 @@ xmlns:converters="using:Avalonia.Controls.Converters"> - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - + - - - + + + + + + + + + + + diff --git a/src/Avalonia.Controls/Documents/LineBreak.cs b/src/Avalonia.Controls/Documents/LineBreak.cs index aeb81f7313..108a38d86b 100644 --- a/src/Avalonia.Controls/Documents/LineBreak.cs +++ b/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 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) diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index 92f6f1cb52..de7708e869 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -20,17 +20,8 @@ namespace Avalonia.Controls.Platform { } - public static ITrayIconImpl? CreateTrayIcon() - { - var platform = AvaloniaLocator.Current.GetService(); - - 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()?.CreateTrayIcon(); public static IWindowImpl CreateWindow() diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index d0e8e8f1a0..56b9caf085 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/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 IsIndeterminateProperty = AvaloniaProperty.Register(nameof(IsIndeterminate)); @@ -195,8 +196,9 @@ namespace Avalonia.Controls /// 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 /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { + // dispose any previous track size listener + _trackSizeChangedListener?.Dispose(); + _indicator = e.NameScope.Get("PART_Indicator"); + // listen to size changes of the indicators track (parent) and update the indicator there. + _trackSizeChangedListener = _indicator.Parent?.GetPropertyChangedObservable(BoundsProperty) + .Subscribe(_ => UpdateIndicator()); + UpdateIndicator(); } diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 9b5afaef5d..0c8b1d125d 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -41,9 +41,6 @@ namespace Avalonia.Controls public static readonly StyledProperty SelectionBrushProperty = AvaloniaProperty.Register(nameof(SelectionBrush), Brushes.Blue); - public static readonly StyledProperty SelectionForegroundBrushProperty = - AvaloniaProperty.Register(nameof(SelectionForegroundBrush)); - /// /// Defines the property. /// @@ -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(SelectionStartProperty, SelectionEndProperty, SelectionForegroundBrushProperty, SelectionBrushProperty); + AffectsRender(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty, IsTextSelectionEnabledProperty); } public RichTextBlock() @@ -89,15 +87,6 @@ namespace Avalonia.Controls set => SetValue(SelectionBrushProperty, value); } - /// - /// Gets or sets a value that defines the brush used for selected text. - /// - public IBrush? SelectionForegroundBrush - { - get => GetValue(SelectionForegroundBrushProperty); - set => SetValue(SelectionForegroundBrushProperty, value); - } - /// /// Gets or sets a character index for the beginning of the current selection. /// @@ -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); } /// @@ -297,8 +289,9 @@ namespace Avalonia.Controls /// A object. 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(); @@ -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; - } } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 80edaf4f26..99c8068b3d 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/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.AddChild(string text) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 9531f719b9..1b268db2f7 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls public static readonly StyledProperty PasswordCharProperty = AvaloniaProperty.Register(nameof(PasswordChar)); - + public static readonly StyledProperty SelectionBrushProperty = AvaloniaProperty.Register(nameof(SelectionBrush)); @@ -80,7 +80,7 @@ namespace Avalonia.Controls public static readonly StyledProperty MaxLinesProperty = AvaloniaProperty.Register(nameof(MaxLines), defaultValue: 0); - + public static readonly DirectProperty TextProperty = TextBlock.TextProperty.AddOwnerWithDataValidation( o => o.Text, @@ -105,7 +105,7 @@ namespace Avalonia.Controls public static readonly StyledProperty TextWrappingProperty = TextBlock.TextWrappingProperty.AddOwner(); - + /// /// Defines see property. /// @@ -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); } - + /// /// Gets or sets the line height. /// @@ -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("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] == ' ') diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs index 94e7728d68..3a8fdb8491 100644 --- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs +++ b/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()?.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) { diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj index 76924d060f..bacb10c792 100644 --- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -11,4 +11,5 @@ + diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index b3469c212b..68442c1fd3 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs +++ b/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); diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs index bc517c3e27..e7b5dc7c83 100644 --- a/src/Avalonia.OpenGL/Egl/EglContext.cs +++ b/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; } diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs index 623364866b..d3d85e8c83 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs +++ b/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) diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index 6148e58440..4fec8e5356 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/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 getProcAddress) { - - } - - public EglInterface(Func getProcAddress) : base(getProcAddress) - { - + Initialize(getProcAddress); } - public EglInterface(Func getProcAddress) : base(getProcAddress) + public EglInterface(string library) : this(Load(library)) { - } - - public EglInterface(string library) : base(Load(library)) + + public EglInterface() : this(Load()) { + } - static Func Load() { var os = AvaloniaLocator.Current.GetService().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); } } diff --git a/src/Avalonia.OpenGL/GlBasicInfoInterface.cs b/src/Avalonia.OpenGL/GlBasicInfoInterface.cs index aaba2ec09c..7a7110f15b 100644 --- a/src/Avalonia.OpenGL/GlBasicInfoInterface.cs +++ b/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 + public unsafe partial class GlBasicInfoInterface { - public GlBasicInfoInterface(Func getProcAddress) : base(getProcAddress, null) - { - } - - public GlBasicInfoInterface(Func nativeGetProcAddress) : base(nativeGetProcAddress, null) - { + public GlBasicInfoInterface(Func 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 : GlInterfaceBase - { - public GlBasicInfoInterface(Func getProcAddress, TContextInfo context) : base(getProcAddress, context) - { - } + [GetProcAddress("glGetIntegerv")] + public partial void GetIntegerv(int name, out int rv); - public GlBasicInfoInterface(Func 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) { diff --git a/src/Avalonia.OpenGL/GlEntryPointAttribute.cs b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs index 2ffdaca8fe..3e31de6995 100644 --- a/src/Avalonia.OpenGL/GlEntryPointAttribute.cs +++ b/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 getProcAddress); - } - - public interface IGlEntryPointAttribute - { - IntPtr GetProcAddress(TContext context, Func 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 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 - { - 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 getProcAddress) + public static IntPtr GetProcAddress(Func 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 + [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 getProcAddress) + public static IntPtr GetProcAddress(Func 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); } } } diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 18bebe4cb5..a9dd882e3b 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/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 + public unsafe partial class GlInterface : GlBasicInfoInterface { + private readonly Func _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 getProcAddress) : base(getProcAddress, info) + private GlInterface(GlContextInfo info, Func 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 getProcAddress) : this( @@ -48,92 +50,58 @@ namespace Avalonia.OpenGL { } - public GlInterface(GlVersion version, Func 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 getProcAddress) => - new GlInterface(version, getProcAddress); - - - public T GetProcAddress(string proc) => Marshal.GetDelegateForFunctionPointer(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 getProcAddress) + { + return new GlInterface(version, s => + { + var ptr = Marshal.StringToHGlobalAnsi(s); + var rv = getProcAddress(ptr); + Marshal.FreeHGlobal(ptr); + return rv; + }); + } } -} +} \ No newline at end of file diff --git a/src/Avalonia.OpenGL/GlInterfaceBase.cs b/src/Avalonia.OpenGL/GlInterfaceBase.cs deleted file mode 100644 index e7dd440e36..0000000000 --- a/src/Avalonia.OpenGL/GlInterfaceBase.cs +++ /dev/null @@ -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 - { - public GlInterfaceBase(Func getProcAddress) : base(getProcAddress, null) - { - } - - public GlInterfaceBase(Func nativeGetProcAddress) : base(nativeGetProcAddress, null) - { - } - } - - public class GlInterfaceBase - { - private readonly Func _getProcAddress; - public GlInterfaceBase(Func getProcAddress, TContext context) - { - _getProcAddress = getProcAddress; - foreach (var prop in this.GetType().GetProperties()) - { - var attrs = prop.GetCustomAttributes() - .Where(a => - a is IGlEntryPointAttribute || a is IGlEntryPointAttribute) - .ToList(); - if(attrs.Count == 0) - continue; - - var isOptional = prop.GetCustomAttribute() != 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 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 ConvertNative(Func func) => - (proc) => - { - using (var u = new Utf8Buffer(proc)) - { - var rv = func(u); - return rv; - } - }; - - public GlInterfaceBase(Func nativeGetProcAddress, TContext context) : this(ConvertNative(nativeGetProcAddress), context) - { - - } - - public IntPtr GetProcAddress(string proc) => _getProcAddress(proc); - } -} diff --git a/src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml new file mode 100644 index 0000000000..d7bf6e5cf9 --- /dev/null +++ b/src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 468b723f5b..f266402aef 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -66,4 +66,5 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index bc2352d5d0..0499495239 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -68,6 +68,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml new file mode 100644 index 0000000000..75af2efcb1 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index 45a76bc3d6..621e6fabd7 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -11,5 +11,5 @@ - + diff --git a/src/Avalonia.X11/Glx/Glx.cs b/src/Avalonia.X11/Glx/Glx.cs index 37ca7f5603..1a26d7d5f5 100644 --- a/src/Avalonia.X11/Glx/Glx.cs +++ b/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 attribs, out int nelements) + public IntPtr* ChooseFbConfig(IntPtr dpy, int screen, IEnumerable 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 GlxConverted = ConvertNative(GlxGetProcAddress); public string[] GetExtensions(IntPtr display) { diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 7043c60ae7..cc9737668d 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/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; /// /// Enables multitouch support. The default value is true. diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 009ccb6159..7b34705b3b 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/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 { @@ -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) diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 464ec4f1c8..753d5f530c 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/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; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs new file mode 100644 index 0000000000..e92ad02c7a --- /dev/null +++ b/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 + { + /// + /// Scaling factor. + /// Default: 1.0 + /// + public double Scaling { get; set; } = 1.0; + + /// + /// If true an two cycle buffer swapping is processed at init. + /// Default: True + /// + public bool EnableInitialBufferSwapping { get; set; } = true; + + /// + /// Color for + /// Default: R0 G0 B0 A0 + /// + public Color InitialBufferSwappingColor { get; set; } = new Color(0, 0, 0, 0); + } +} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 4add4c423b..a642766809 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -140,6 +140,8 @@ public static class LinuxFramebufferPlatformExtensions public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) {Scaling = scaling}); + public static int StartLinuxDrm(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null) + where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options)); public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend backend) where T : AppBuilderBase, new() diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs index 9c476b1b63..04bc2c1f67 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs +++ b/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)] diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs index 64fafc65f3..070e1a95bc 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs @@ -84,7 +84,7 @@ namespace Avalonia.LinuxFramebuffer.Output { public List Connectors { get; }= new List(); internal Dictionary Encoders { get; } = new Dictionary(); - 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); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 46a985c0e8..ce210019c0 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/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); + } + } } diff --git a/src/Shared/SourceGeneratorAttributes.cs b/src/Shared/SourceGeneratorAttributes.cs index fdb5977d23..ac7c82c469 100644 --- a/src/Shared/SourceGeneratorAttributes.cs +++ b/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() + { + + } + } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs index 8ab275df63..984c1785b7 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs +++ b/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; } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index ec8a8436e1..08e0fbd808 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/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)); diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs index 2ebf7c680b..a4617bb4d5 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs +++ b/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); } } diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 5628ae177c..a80f406989 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/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; diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 506edf0627..80bfcc5973 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/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; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 5334b90d62..84c3cc5c51 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/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); } } diff --git a/src/iOS/Avalonia.iOS/LayerFbo.cs b/src/iOS/Avalonia.iOS/LayerFbo.cs index 955aaef59f..9a93cdfb22 100644 --- a/src/iOS/Avalonia.iOS/LayerFbo.cs +++ b/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"); } diff --git a/src/tools/DevGenerators/GetProcAddressInitialization.cs b/src/tools/DevGenerators/GetProcAddressInitialization.cs new file mode 100644 index 0000000000..64698d1f2e --- /dev/null +++ b/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 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 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(); + } + +} \ No newline at end of file diff --git a/src/tools/DevGenerators/Helpers.cs b/src/tools/DevGenerators/Helpers.cs new file mode 100644 index 0000000000..3da89d2d0e --- /dev/null +++ b/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 attributes = symbol.GetAttributes(); + + foreach (AttributeData attribute in attributes) + if (attribute.AttributeClass?.HasFullyQualifiedName(name) == true) + return true; + + return false; + } +} \ No newline at end of file diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index d1d231466f..3f1fe7de12 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/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(); + } } } diff --git a/tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf b/tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf new file mode 100644 index 0000000000..6d2ad86f94 Binary files /dev/null and b/tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf differ diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index 04b408a666..13cc14b03e 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/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; } diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs index df286d709e..649e1fbf3d 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs +++ b/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); } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 7e1103d624..7d33f094fa 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/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 diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 58cb84c4a4..d744ede87d 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/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 {