diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
index c7ec28f16d..812c4e9eb8 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
@@ -57,7 +57,7 @@ namespace Avalonia.Media.TextFormatting
textSourceLength,
paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);
- textLine.FinalizeLine();
+ textLine.FinalizeLine();
return textLine;
}
@@ -236,49 +236,49 @@ namespace Avalonia.Media.TextFormatting
switch (currentRun)
{
case UnshapedTextRun shapeableRun:
- {
- groupedRuns.Clear();
- groupedRuns.Add(shapeableRun);
+ {
+ groupedRuns.Clear();
+ groupedRuns.Add(shapeableRun);
- var text = shapeableRun.Text;
- var properties = shapeableRun.Properties;
+ var text = shapeableRun.Text;
+ var properties = shapeableRun.Properties;
- while (index + 1 < processedRuns.Count)
- {
- if (processedRuns[index + 1] is not UnshapedTextRun nextRun)
+ while (index + 1 < processedRuns.Count)
{
+ if (processedRuns[index + 1] is not UnshapedTextRun nextRun)
+ {
+ break;
+ }
+
+ if (shapeableRun.BidiLevel == nextRun.BidiLevel
+ && TryJoinContiguousMemories(text, nextRun.Text, out var joinedText)
+ && CanShapeTogether(properties, nextRun.Properties))
+ {
+ groupedRuns.Add(nextRun);
+ index++;
+ shapeableRun = nextRun;
+ text = joinedText;
+ continue;
+ }
+
break;
}
- if (shapeableRun.BidiLevel == nextRun.BidiLevel
- && TryJoinContiguousMemories(text, nextRun.Text, out var joinedText)
- && CanShapeTogether(properties, nextRun.Properties))
- {
- groupedRuns.Add(nextRun);
- index++;
- shapeableRun = nextRun;
- text = joinedText;
- continue;
- }
+ var shaperOptions = new TextShaperOptions(
+ properties.CachedGlyphTypeface,
+ properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo,
+ paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
+
+ ShapeTogether(groupedRuns, text, shaperOptions, textShaper, shapedRuns);
break;
}
-
- var shaperOptions = new TextShaperOptions(
- properties.CachedGlyphTypeface,
- properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo,
- paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
-
- ShapeTogether(groupedRuns, text, shaperOptions, textShaper, shapedRuns);
-
- break;
- }
default:
- {
- shapedRuns.Add(currentRun);
+ {
+ shapedRuns.Add(currentRun);
- break;
- }
+ break;
+ }
}
}
}
@@ -699,7 +699,7 @@ namespace Avalonia.Media.TextFormatting
switch (currentRun)
{
case ShapedTextRun:
- {
+ {
var lineBreaker = new LineBreakEnumerator(currentRun.Text.Span);
while (lineBreaker.MoveNext(out var lineBreak))
@@ -741,7 +741,7 @@ namespace Avalonia.Media.TextFormatting
break;
}
- while (lineBreaker.MoveNext(out lineBreak) && index < textRuns.Count)
+ while (lineBreaker.MoveNext(out lineBreak))
{
currentPosition += lineBreak.PositionWrap;
@@ -767,6 +767,11 @@ namespace Avalonia.Media.TextFormatting
currentPosition = currentLength + lineBreak.PositionWrap;
}
+ if (currentPosition == 0 && measuredLength > 0)
+ {
+ currentPosition = measuredLength;
+ }
+
breakFound = true;
break;
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index 4a7916275d..d29063e07d 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -172,9 +172,21 @@ namespace Avalonia.Media.TextFormatting
distance -= Start;
+ var firstRunIndex = 0;
+
+ if (_textRuns[firstRunIndex] is TextEndOfLine)
+ {
+ firstRunIndex++;
+ }
+
+ if(firstRunIndex >= _textRuns.Length)
+ {
+ return new CharacterHit(FirstTextSourceIndex);
+ }
+
if (distance <= 0)
{
- var firstRun = _textRuns[0];
+ var firstRun = _textRuns[firstRunIndex];
return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0);
}
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index 022e1a74b1..5674874c01 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
+using Avalonia.Data;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Threading;
@@ -22,11 +22,10 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly DirectProperty IsExpandedProperty =
- AvaloniaProperty.RegisterDirect(
+ public static readonly StyledProperty IsExpandedProperty =
+ AvaloniaProperty.Register(
nameof(IsExpanded),
- o => o.IsExpanded,
- (o, v) => o.IsExpanded = v);
+ defaultBindingMode: BindingMode.TwoWay);
///
/// Defines the property.
@@ -46,7 +45,6 @@ namespace Avalonia.Controls
private TreeView? _treeView;
private Control? _header;
- private bool _isExpanded;
private int _level;
private bool _templateApplied;
private bool _deferredBringIntoViewFlag;
@@ -68,8 +66,8 @@ namespace Avalonia.Controls
///
public bool IsExpanded
{
- get { return _isExpanded; }
- set { SetAndRaise(IsExpandedProperty, ref _isExpanded, value); }
+ get => GetValue(IsExpandedProperty);
+ set => SetValue(IsExpandedProperty, value);
}
///
@@ -77,8 +75,8 @@ namespace Avalonia.Controls
///
public bool IsSelected
{
- get { return GetValue(IsSelectedProperty); }
- set { SetValue(IsSelectedProperty, value); }
+ get => GetValue(IsSelectedProperty);
+ set => SetValue(IsSelectedProperty, value);
}
///
@@ -86,8 +84,8 @@ namespace Avalonia.Controls
///
public int Level
{
- get { return _level; }
- private set { SetAndRaise(LevelProperty, ref _level, value); }
+ get => _level;
+ private set => SetAndRaise(LevelProperty, ref _level, value);
}
internal TreeView? TreeViewOwner => _treeView;
@@ -115,11 +113,6 @@ namespace Avalonia.Controls
}
}
- protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
- {
- base.OnDetachedFromLogicalTree(e);
- }
-
protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e)
{
if (e.TargetObject == this)
@@ -266,7 +259,7 @@ namespace Avalonia.Controls
}
///
- /// Invoked when the event occurs in the header.
+ /// Invoked when the event occurs in the header.
///
protected virtual void OnHeaderDoubleTapped(TappedEventArgs e)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index aaaee39b0d..197815f9a0 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -49,6 +49,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertBefore(
new AvaloniaXamlIlControlThemeTransformer(),
new AvaloniaXamlIlSelectorTransformer(),
+ new AvaloniaXamlIlDuplicateSettersChecker(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlBindingPathParser(),
new AvaloniaXamlIlPropertyPathTransformer(),
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
index 1338dc7248..51fe58d1c9 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
@@ -11,7 +11,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (!(node is XamlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.ControlTheme"))
+ if (node is not XamlAstObjectNode on ||
+ !context.GetAvaloniaTypes().ControlTheme.IsAssignableFrom(on.Type.GetClrType()))
return node;
// Check if we've already transformed this node.
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs
new file mode 100644
index 0000000000..4ab9594cd8
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+
+class AvaloniaXamlIlDuplicateSettersChecker : IXamlAstTransformer
+{
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is not XamlAstObjectNode objectNode)
+ {
+ return node;
+ }
+
+ var nodeType = objectNode.Type.GetClrType();
+ if (!context.GetAvaloniaTypes().Style.IsAssignableFrom(nodeType) &&
+ !context.GetAvaloniaTypes().ControlTheme.IsAssignableFrom(nodeType))
+ {
+ return node;
+ }
+
+ var properties = objectNode.Children
+ .OfType()
+ .Where(n => n.Type.GetClrType().Name == "Setter")
+ .SelectMany(setter =>
+ setter.Children.OfType()
+ .Where(c => c.Property.GetClrProperty().Name == "Property"))
+ .Select(p => p.Values[0])
+ .OfType()
+ .Select(x => x.Text);
+ var index = new HashSet();
+ foreach (var property in properties)
+ {
+ if (!index.Add(property))
+ {
+ throw new XamlParseException($"Duplicate setter encountered for property '{property}'", node);
+ }
+ }
+
+ return node;
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
index 078e23bc02..edddc5424a 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
@@ -19,7 +19,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (!(node is XamlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Style"))
+ if (node is not XamlAstObjectNode on ||
+ !context.GetAvaloniaTypes().Style.IsAssignableFrom(on.Type.GetClrType()))
return node;
var pn = on.Children.OfType()
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index 16f6a32ae1..92710e42ce 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -112,6 +112,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
public IXamlType UriKind { get; }
public IXamlConstructor UriConstructor { get; }
+ public IXamlType Style { get; }
+ public IXamlType ControlTheme { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
@@ -250,6 +252,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
XamlIlTypes.Object));
UriKind = cfg.TypeSystem.GetType("System.UriKind");
UriConstructor = Uri.GetConstructor(new List() { cfg.WellKnownTypes.String, UriKind });
+ Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style");
+ ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme");
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
index f42f787117..be2cae8ec4 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
@@ -334,6 +334,47 @@ namespace Avalonia.Markup.Xaml.UnitTests
var parsed = (Button)AvaloniaRuntimeXamlLoader.Load(document);
Assert.Equal(Colors.Blue, ((ISolidColorBrush)parsed.Background!).Color);
}
+
+ [Fact]
+ public void Style_Parser_Throws_For_Duplicate_Setter()
+ {
+ var xaml = @"
+
+
+
+
+
+";
+ AssertThrows(() => AvaloniaRuntimeXamlLoader.Load(xaml, typeof(XamlIlTests).Assembly, designMode: true),
+ e => e.Message.StartsWith("Duplicate setter encountered for property 'Height'"));
+ }
+
+ [Fact]
+ public void Control_Theme_Parser_Throws_For_Duplicate_Setter()
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+
+
+
+";
+ AssertThrows(() => AvaloniaRuntimeXamlLoader.Load(xaml, typeof(XamlIlTests).Assembly, designMode: true),
+ e => e.Message.StartsWith("Duplicate setter encountered for property 'Height'"));
+ }
}
public class XamlIlBugTestsEventHandlerCodeBehind : Window
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs
index f963277397..7837749adf 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/SingleBufferTextSource.cs
@@ -7,25 +7,27 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
private readonly string _text;
private readonly GenericTextRunProperties _defaultGenericPropertiesRunProperties;
+ private readonly bool _addEndOfParagraph;
- public SingleBufferTextSource(string text, GenericTextRunProperties defaultProperties)
+ public SingleBufferTextSource(string text, GenericTextRunProperties defaultProperties, bool addEndOfParagraph = false)
{
_text = text;
_defaultGenericPropertiesRunProperties = defaultProperties;
+ _addEndOfParagraph = addEndOfParagraph;
}
public TextRun GetTextRun(int textSourceIndex)
{
if (textSourceIndex >= _text.Length)
{
- return null;
+ return _addEndOfParagraph ? new TextEndOfParagraph() : null;
}
var runText = _text.AsMemory(textSourceIndex);
if (runText.IsEmpty)
{
- return null;
+ return _addEndOfParagraph ? new TextEndOfParagraph() : null;
}
return new TextCharacters(runText, _defaultGenericPropertiesRunProperties);
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
index e8b87ebe00..954169f975 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -242,10 +242,16 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
- var textSource = new SingleBufferTextSource(text, defaultProperties);
+ var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.WrapWithOverflow);
+
+ var textSource = new SingleBufferTextSource("ABCDEFHFFHFJHKHFK", defaultProperties, true);
var formatter = new TextFormatterImpl();
+ var line = formatter.FormatLine(textSource, 0, 33, paragraphProperties);
+
+ textSource = new SingleBufferTextSource(text, defaultProperties);
+
var numberOfLines = 0;
var currentPosition = 0;
@@ -253,8 +259,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
while (currentPosition < text.Length)
{
var textLine =
- formatter.FormatLine(textSource, currentPosition, 1,
- new GenericTextParagraphProperties(defaultProperties, textWrap: TextWrapping.WrapWithOverflow));
+ formatter.FormatLine(textSource, currentPosition, 1, paragraphProperties);
if (text.Length - currentPosition > expectedCharactersPerLine)
{