diff --git a/.editorconfig b/.editorconfig index 25e0135725..cb589a5ce1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -21,6 +21,7 @@ csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_between_query_expression_clauses = true +trim_trailing_whitespace = true # Indentation preferences csharp_indent_block_contents = true diff --git a/src/Avalonia.Base/Assets/BiDi.trie b/src/Avalonia.Base/Assets/BiDi.trie deleted file mode 100644 index 1c6122e2f1..0000000000 Binary files a/src/Avalonia.Base/Assets/BiDi.trie and /dev/null differ diff --git a/src/Avalonia.Base/Assets/GraphemeBreak.trie b/src/Avalonia.Base/Assets/GraphemeBreak.trie deleted file mode 100644 index 482bf9b44d..0000000000 Binary files a/src/Avalonia.Base/Assets/GraphemeBreak.trie and /dev/null differ diff --git a/src/Avalonia.Base/Assets/UnicodeData.trie b/src/Avalonia.Base/Assets/UnicodeData.trie deleted file mode 100644 index 46175ea644..0000000000 Binary files a/src/Avalonia.Base/Assets/UnicodeData.trie and /dev/null differ diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1f14ddede4..6633eabb5d 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -935,7 +935,8 @@ namespace Avalonia public void Dispose() { - _subscription.Dispose(); + // _subscription can be null, if Subscribe failed with an exception. + _subscription?.Dispose(); _owner._directBindings!.Remove(this); } diff --git a/src/Avalonia.Base/Input/IInputElement.cs b/src/Avalonia.Base/Input/IInputElement.cs index 78001143d7..43ac87d008 100644 --- a/src/Avalonia.Base/Input/IInputElement.cs +++ b/src/Avalonia.Base/Input/IInputElement.cs @@ -42,12 +42,12 @@ namespace Avalonia.Input /// /// Occurs when the pointer enters the control. /// - event EventHandler? PointerEnter; + event EventHandler? PointerEntered; /// /// Occurs when the pointer leaves the control. /// - event EventHandler? PointerLeave; + event EventHandler? PointerExited; /// /// Occurs when the pointer is pressed over the control. diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index f4e25ebada..d0130258c3 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -128,16 +128,20 @@ namespace Avalonia.Input RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent PointerEnterEvent = - RoutedEvent.Register(nameof(PointerEnter), RoutingStrategies.Direct); + public static readonly RoutedEvent PointerEnteredEvent = + RoutedEvent.Register( + nameof(PointerEntered), + RoutingStrategies.Direct); /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent PointerLeaveEvent = - RoutedEvent.Register(nameof(PointerLeave), RoutingStrategies.Direct); + public static readonly RoutedEvent PointerExitedEvent = + RoutedEvent.Register( + nameof(PointerExited), + RoutingStrategies.Direct); /// /// Defines the event. @@ -208,8 +212,8 @@ namespace Avalonia.Input KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e)); KeyUpEvent.AddClassHandler((x, e) => x.OnKeyUp(e)); TextInputEvent.AddClassHandler((x, e) => x.OnTextInput(e)); - PointerEnterEvent.AddClassHandler((x, e) => x.OnPointerEnterCore(e)); - PointerLeaveEvent.AddClassHandler((x, e) => x.OnPointerLeaveCore(e)); + PointerEnteredEvent.AddClassHandler((x, e) => x.OnPointerEnteredCore(e)); + PointerExitedEvent.AddClassHandler((x, e) => x.OnPointerExitedCore(e)); PointerMovedEvent.AddClassHandler((x, e) => x.OnPointerMoved(e)); PointerPressedEvent.AddClassHandler((x, e) => x.OnPointerPressed(e)); PointerReleasedEvent.AddClassHandler((x, e) => x.OnPointerReleased(e)); @@ -279,19 +283,19 @@ namespace Avalonia.Input /// /// Occurs when the pointer enters the control. /// - public event EventHandler? PointerEnter + public event EventHandler? PointerEntered { - add { AddHandler(PointerEnterEvent, value); } - remove { RemoveHandler(PointerEnterEvent, value); } + add { AddHandler(PointerEnteredEvent, value); } + remove { RemoveHandler(PointerEnteredEvent, value); } } /// /// Occurs when the pointer leaves the control. /// - public event EventHandler? PointerLeave + public event EventHandler? PointerExited { - add { AddHandler(PointerLeaveEvent, value); } - remove { RemoveHandler(PointerLeaveEvent, value); } + add { AddHandler(PointerExitedEvent, value); } + remove { RemoveHandler(PointerExitedEvent, value); } } /// @@ -539,18 +543,18 @@ namespace Avalonia.Input } /// - /// Called before the event occurs. + /// Called before the event occurs. /// /// The event args. - protected virtual void OnPointerEnter(PointerEventArgs e) + protected virtual void OnPointerEntered(PointerEventArgs e) { } /// - /// Called before the event occurs. + /// Called before the event occurs. /// /// The event args. - protected virtual void OnPointerLeave(PointerEventArgs e) + protected virtual void OnPointerExited(PointerEventArgs e) { } @@ -561,7 +565,9 @@ namespace Avalonia.Input protected virtual void OnPointerMoved(PointerEventArgs e) { if (_gestureRecognizers?.HandlePointerMoved(e) == true) + { e.Handled = true; + } } /// @@ -571,7 +577,9 @@ namespace Avalonia.Input protected virtual void OnPointerPressed(PointerPressedEventArgs e) { if (_gestureRecognizers?.HandlePointerPressed(e) == true) + { e.Handled = true; + } } /// @@ -581,7 +589,9 @@ namespace Avalonia.Input protected virtual void OnPointerReleased(PointerReleasedEventArgs e) { if (_gestureRecognizers?.HandlePointerReleased(e) == true) + { e.Handled = true; + } } /// @@ -634,23 +644,23 @@ namespace Avalonia.Input } /// - /// Called before the event occurs. + /// Called before the event occurs. /// /// The event args. - private void OnPointerEnterCore(PointerEventArgs e) + private void OnPointerEnteredCore(PointerEventArgs e) { IsPointerOver = true; - OnPointerEnter(e); + OnPointerEntered(e); } /// - /// Called before the event occurs. + /// Called before the event occurs. /// /// The event args. - private void OnPointerLeaveCore(PointerEventArgs e) + private void OnPointerExitedCore(PointerEventArgs e) { IsPointerOver = false; - OnPointerLeave(e); + OnPointerExited(e); } /// diff --git a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs index d22252893d..67d1eea7e3 100644 --- a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs +++ b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs @@ -97,7 +97,7 @@ namespace Avalonia.Input // Do not pass rootVisual, when we have unknown (negative) position, // so GetPosition won't return invalid values. var hasPosition = position.X >= 0 && position.Y >= 0; - var e = new PointerEventArgs(InputElement.PointerLeaveEvent, element, pointer, + var e = new PointerEventArgs(InputElement.PointerExitedEvent, element, pointer, hasPosition ? root : null, hasPosition ? position : default, timestamp, properties, inputModifiers); @@ -177,7 +177,7 @@ namespace Avalonia.Input el = root.PointerOverElement; - var e = new PointerEventArgs(InputElement.PointerLeaveEvent, el, pointer, root, position, + var e = new PointerEventArgs(InputElement.PointerExitedEvent, el, pointer, root, position, timestamp, properties, inputModifiers); if (el != null && branch != null && !el.IsAttachedToVisualTree) { @@ -195,7 +195,7 @@ namespace Avalonia.Input el = root.PointerOverElement = element; _lastPointer = (pointer, root.PointToScreen(position)); - e.RoutedEvent = InputElement.PointerEnterEvent; + e.RoutedEvent = InputElement.PointerEnteredEvent; while (el != null && el != branch) { diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs index d24be57d2b..404d19906a 100644 --- a/src/Avalonia.Base/Layout/LayoutHelper.cs +++ b/src/Avalonia.Base/Layout/LayoutHelper.cs @@ -36,11 +36,28 @@ namespace Avalonia.Layout public static Size MeasureChild(ILayoutable? control, Size availableSize, Thickness padding, Thickness borderThickness) { - return MeasureChild(control, availableSize, padding + borderThickness); + if (IsParentLayoutRounded(control, out double scale)) + { + padding = RoundLayoutThickness(padding, scale, scale); + borderThickness = RoundLayoutThickness(borderThickness, scale, scale); + } + + if (control != null) + { + control.Measure(availableSize.Deflate(padding + borderThickness)); + return control.DesiredSize.Inflate(padding + borderThickness); + } + + return new Size().Inflate(padding + borderThickness); } public static Size MeasureChild(ILayoutable? control, Size availableSize, Thickness padding) { + if (IsParentLayoutRounded(control, out double scale)) + { + padding = RoundLayoutThickness(padding, scale, scale); + } + if (control != null) { control.Measure(availableSize.Deflate(padding)); @@ -137,7 +154,7 @@ namespace Avalonia.Layout /// /// Rounds a size to integer values for layout purposes, compensating for high DPI screen - /// coordinates. + /// coordinates by rounding the size up to the nearest pixel. /// /// Input size. /// DPI along x-dimension. @@ -149,9 +166,9 @@ namespace Avalonia.Layout /// associated with the UseLayoutRounding property and should not be used as a general rounding /// utility. /// - public static Size RoundLayoutSize(Size size, double dpiScaleX, double dpiScaleY) + public static Size RoundLayoutSizeUp(Size size, double dpiScaleX, double dpiScaleY) { - return new Size(RoundLayoutValue(size.Width, dpiScaleX), RoundLayoutValue(size.Height, dpiScaleY)); + return new Size(RoundLayoutValueUp(size.Width, dpiScaleX), RoundLayoutValueUp(size.Height, dpiScaleY)); } /// @@ -178,10 +195,9 @@ namespace Avalonia.Layout ); } - - /// - /// Calculates the value to be used for layout rounding at high DPI. + /// Calculates the value to be used for layout rounding at high DPI by rounding the value + /// up or down to the nearest pixel. /// /// Input value to be rounded. /// Ratio of screen's DPI to layout DPI @@ -217,7 +233,46 @@ namespace Avalonia.Layout return newValue; } - + + /// + /// Calculates the value to be used for layout rounding at high DPI by rounding the value up + /// to the nearest pixel. + /// + /// Input value to be rounded. + /// Ratio of screen's DPI to layout DPI + /// Adjusted value that will produce layout rounding on screen at high dpi. + /// + /// This is a layout helper method. It takes DPI into account and also does not return + /// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper + /// associated with the UseLayoutRounding property and should not be used as a general rounding + /// utility. + /// + public static double RoundLayoutValueUp(double value, double dpiScale) + { + double newValue; + + // If DPI == 1, don't use DPI-aware rounding. + if (!MathUtilities.IsOne(dpiScale)) + { + newValue = Math.Ceiling(value * dpiScale) / dpiScale; + + // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), + // use the original value. + if (double.IsNaN(newValue) || + double.IsInfinity(newValue) || + MathUtilities.AreClose(newValue, double.MaxValue)) + { + newValue = value; + } + } + else + { + newValue = Math.Ceiling(value); + } + + return newValue; + } + /// /// Calculates the min and max height for a control. Ported from WPF. /// diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index f30925f489..101e867d56 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -548,6 +548,14 @@ namespace Avalonia.Layout if (IsVisible) { var margin = Margin; + var useLayoutRounding = UseLayoutRounding; + var scale = 1.0; + + if (useLayoutRounding) + { + scale = LayoutHelper.GetLayoutScale(this); + margin = LayoutHelper.RoundLayoutThickness(margin, scale, scale); + } ApplyStyling(); ApplyTemplate(); @@ -584,16 +592,14 @@ namespace Avalonia.Layout height = Math.Min(height, MaxHeight); height = Math.Max(height, MinHeight); - width = Math.Min(width, availableSize.Width); - height = Math.Min(height, availableSize.Height); - - if (UseLayoutRounding) + if (useLayoutRounding) { - var scale = LayoutHelper.GetLayoutScale(this); - width = LayoutHelper.RoundLayoutValue(width, scale); - height = LayoutHelper.RoundLayoutValue(height, scale); + (width, height) = LayoutHelper.RoundLayoutSizeUp(new Size(width, height), scale, scale); } + width = Math.Min(width, availableSize.Width); + height = Math.Min(height, availableSize.Height); + return NonNegative(new Size(width, height).Inflate(margin)); } else @@ -678,8 +684,8 @@ namespace Avalonia.Layout if (useLayoutRounding) { - size = LayoutHelper.RoundLayoutSize(size, scale, scale); - availableSizeMinusMargins = LayoutHelper.RoundLayoutSize(availableSizeMinusMargins, scale, scale); + size = LayoutHelper.RoundLayoutSizeUp(size, scale, scale); + availableSizeMinusMargins = LayoutHelper.RoundLayoutSizeUp(availableSizeMinusMargins, scale, scale); } size = ArrangeOverride(size).Constrain(size); diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 6f1fa03990..ac87d521a5 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -734,10 +734,9 @@ namespace Avalonia.Media private void Set(ref T field, T value) { - if (_glyphRunImpl != null) - { - throw new InvalidOperationException("GlyphRun can't be changed after it has been initialized.'"); - } + _glyphRunImpl?.Dispose(); + + _glyphRunImpl = null; _glyphRunMetrics = null; diff --git a/src/Avalonia.Base/Media/TextAlignment.cs b/src/Avalonia.Base/Media/TextAlignment.cs index b1a394e157..94416ccde2 100644 --- a/src/Avalonia.Base/Media/TextAlignment.cs +++ b/src/Avalonia.Base/Media/TextAlignment.cs @@ -19,5 +19,28 @@ namespace Avalonia.Media /// The text is right-aligned. /// Right, + + /// + /// The beginning of the text is aligned to the edge of the available space. + /// + Start, + + /// + /// The end of the text is aligned to the edge of the available space. + /// + End, + + /// + /// Text alignment is inferred from the text content. + /// + /// + /// When the TextAlignment property is set to DetectFromContent, alignment is inferred from the text content of the control. For example, English text is left aligned, and Arabic text is right aligned. + /// + DetectFromContent, + + /// + /// Text is justified within the available space. + /// + Justify } } diff --git a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs new file mode 100644 index 0000000000..df83ada34a --- /dev/null +++ b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using Avalonia.Media.TextFormatting.Unicode; + +namespace Avalonia.Media.TextFormatting +{ + internal class InterWordJustification : JustificationProperties + { + public InterWordJustification(double width) + { + Width = width; + } + + public override double Width { get; } + + public override void Justify(TextLine textLine) + { + var paragraphWidth = Width; + + if (double.IsInfinity(paragraphWidth)) + { + return; + } + + if (textLine.NewLineLength > 0) + { + return; + } + + var textLineBreak = textLine.TextLineBreak; + + if (textLineBreak is not null && textLineBreak.TextEndOfLine is not null) + { + if (textLineBreak.RemainingRuns is null || textLineBreak.RemainingRuns.Count == 0) + { + return; + } + } + + var breakOportunities = new Queue(); + + foreach (var textRun in textLine.TextRuns) + { + var text = textRun.Text; + + if (text.IsEmpty) + { + continue; + } + + var start = text.Start; + + var lineBreakEnumerator = new LineBreakEnumerator(text); + + while (lineBreakEnumerator.MoveNext()) + { + var currentBreak = lineBreakEnumerator.Current; + + if (!currentBreak.Required && currentBreak.PositionWrap != text.Length) + { + breakOportunities.Enqueue(start + currentBreak.PositionMeasure); + } + } + } + + if (breakOportunities.Count == 0) + { + return; + } + + var remainingSpace = Math.Max(0, paragraphWidth - textLine.WidthIncludingTrailingWhitespace); + var spacing = remainingSpace / breakOportunities.Count; + + foreach (var textRun in textLine.TextRuns) + { + var text = textRun.Text; + + if (text.IsEmpty) + { + continue; + } + + if (textRun is ShapedTextCharacters shapedText) + { + var glyphRun = shapedText.GlyphRun; + var shapedBuffer = shapedText.ShapedBuffer; + var currentPosition = text.Start; + + while (breakOportunities.Count > 0) + { + var characterIndex = breakOportunities.Dequeue(); + + if (characterIndex < currentPosition) + { + continue; + } + + var glyphIndex = glyphRun.FindGlyphIndex(characterIndex); + var glyphInfo = shapedBuffer.GlyphInfos[glyphIndex]; + + shapedBuffer.GlyphInfos[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex, glyphInfo.GlyphCluster, glyphInfo.GlyphAdvance + spacing); + } + + glyphRun.GlyphAdvances = shapedBuffer.GlyphAdvances; + } + } + } + } +} diff --git a/src/Avalonia.Base/Media/TextFormatting/JustificationProperties.cs b/src/Avalonia.Base/Media/TextFormatting/JustificationProperties.cs new file mode 100644 index 0000000000..620ad17189 --- /dev/null +++ b/src/Avalonia.Base/Media/TextFormatting/JustificationProperties.cs @@ -0,0 +1,16 @@ +namespace Avalonia.Media.TextFormatting +{ + public abstract class JustificationProperties + { + /// + /// Gets the width in which the range is justified. + /// + public abstract double Width { get; } + + /// + /// Justifies given text line. + /// + /// Text line to collapse. + public abstract void Justify(TextLine textLine); + } +} diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 4205268bc6..16caadb0dd 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -15,7 +15,7 @@ namespace Avalonia.Media.TextFormatting TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null) { var textWrapping = paragraphProperties.TextWrapping; - FlowDirection flowDirection; + FlowDirection resolvedFlowDirection; TextLineBreak? nextLineBreak = null; List drawableTextRuns; @@ -24,17 +24,17 @@ namespace Avalonia.Media.TextFormatting if (previousLineBreak?.RemainingRuns != null) { - flowDirection = previousLineBreak.FlowDirection; + resolvedFlowDirection = previousLineBreak.FlowDirection; drawableTextRuns = previousLineBreak.RemainingRuns.ToList(); nextLineBreak = previousLineBreak; } else { - drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out flowDirection); + drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out resolvedFlowDirection); if (nextLineBreak == null && textEndOfLine != null) { - nextLineBreak = new TextLineBreak(textEndOfLine, flowDirection); + nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection); } } @@ -45,7 +45,7 @@ namespace Avalonia.Media.TextFormatting case TextWrapping.NoWrap: { textLine = new TextLineImpl(drawableTextRuns, firstTextSourceIndex, textSourceLength, - paragraphWidth, paragraphProperties, flowDirection, nextLineBreak); + paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); textLine.FinalizeLine(); @@ -55,7 +55,7 @@ namespace Avalonia.Media.TextFormatting case TextWrapping.Wrap: { textLine = PerformTextWrapping(drawableTextRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties, - flowDirection, nextLineBreak); + resolvedFlowDirection, nextLineBreak); break; } default: @@ -404,9 +404,9 @@ namespace Avalonia.Media.TextFormatting { endOfLine = textEndOfLine; - textRuns.Add(textRun); + textSourceLength += textEndOfLine.TextSourceLength; - textSourceLength += textRun.TextSourceLength; + textRuns.Add(textRun); break; } @@ -431,9 +431,9 @@ namespace Avalonia.Media.TextFormatting break; } - case DrawableTextRun drawableTextRun: + default: { - textRuns.Add(drawableTextRun); + textRuns.Add(textRun); break; } } @@ -552,11 +552,11 @@ namespace Avalonia.Media.TextFormatting /// The first text source index. /// The paragraph width. /// The text paragraph properties. - /// + /// /// The current line break if the line was explicitly broken. /// The wrapped text line. private static TextLineImpl PerformTextWrapping(List textRuns, int firstTextSourceIndex, - double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection flowDirection, + double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection, TextLineBreak? currentLineBreak) { if(textRuns.Count == 0) @@ -684,16 +684,16 @@ namespace Avalonia.Media.TextFormatting var remainingCharacters = splitResult.Second; var lineBreak = remainingCharacters?.Count > 0 ? - new TextLineBreak(currentLineBreak?.TextEndOfLine, flowDirection, remainingCharacters) : + new TextLineBreak(currentLineBreak?.TextEndOfLine, resolvedFlowDirection, remainingCharacters) : null; if (lineBreak is null && currentLineBreak?.TextEndOfLine != null) { - lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, flowDirection); + lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection); } var textLine = new TextLineImpl(splitResult.First, firstTextSourceIndex, measuredLength, - paragraphWidth, paragraphProperties, flowDirection, + paragraphWidth, paragraphProperties, resolvedFlowDirection, lineBreak); return textLine.FinalizeLine(); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index 4f7c43a6d1..f3af240c58 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -439,7 +439,7 @@ namespace Avalonia.Media.TextFormatting var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth, _paragraphProperties, previousLine?.TextLineBreak); - if(textLine == null || textLine.Length == 0) + if(textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) { if(previousLine != null && previousLine.NewLineLength > 0) { @@ -501,6 +501,35 @@ namespace Avalonia.Media.TextFormatting Bounds = new Rect(left, 0, width, height); + if(_paragraphProperties.TextAlignment == TextAlignment.Justify) + { + var whitespaceWidth = 0d; + + foreach (var line in textLines) + { + var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace; + + if(lineWhitespaceWidth > whitespaceWidth) + { + whitespaceWidth = lineWhitespaceWidth; + } + } + + var justificationWidth = width - whitespaceWidth; + + if(justificationWidth > 0) + { + var justificationProperties = new InterWordJustification(justificationWidth); + + for (var i = 0; i < textLines.Count - 1; i++) + { + var line = textLines[i]; + + line.Justify(justificationProperties); + } + } + } + return textLines; } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs index 1f69c15acc..c8a23097db 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs @@ -15,9 +15,15 @@ namespace Avalonia.Media.TextFormatting /// The contained text runs. /// public abstract IReadOnlyList TextRuns { get; } - + + /// + /// Gets the first TextSource position of the current line. + /// public abstract int FirstTextSourceIndex { get; } + /// + /// Gets the total number of TextSource positions of the current line. + /// public abstract int Length { get; } /// @@ -56,7 +62,7 @@ namespace Avalonia.Media.TextFormatting /// Gets a value that indicates whether content of the line overflows the specified paragraph width. /// /// - /// true, it the line overflows the specified paragraph width; otherwise, false. + /// true, the line overflows the specified paragraph width; otherwise, false. /// public abstract bool HasOverflowed { get; } @@ -75,7 +81,7 @@ namespace Avalonia.Media.TextFormatting /// The number of newline characters. /// public abstract int NewLineLength { get; } - + /// /// Gets the distance that black pixels extend beyond the bottom alignment edge of a line. /// @@ -149,6 +155,15 @@ namespace Avalonia.Media.TextFormatting /// public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList); + /// + /// Create a justified line based on justification text properties. + /// + /// An object that represent the justification text properties. + /// + /// A value that represents a justified line that can be displayed. + /// + public abstract void Justify(JustificationProperties justificationProperties); + /// /// Gets the character hit corresponding to the specified distance from the beginning of the line. /// @@ -192,50 +207,5 @@ namespace Avalonia.Media.TextFormatting /// number of characters of the specified range /// an array of bounding rectangles. public abstract IReadOnlyList GetTextBounds(int firstTextSourceCharacterIndex, int textLength); - - /// - /// Gets the text line offset x. - /// - /// The line width. - /// The paragraph width including whitespace. - /// The paragraph width. - /// The text alignment. - /// The flow direction of the line. - /// The paragraph offset. - internal static double GetParagraphOffsetX(double width, double widthIncludingTrailingWhitespace, - double paragraphWidth, TextAlignment textAlignment, FlowDirection flowDirection) - { - if (double.IsPositiveInfinity(paragraphWidth)) - { - return 0; - } - - if (flowDirection == FlowDirection.LeftToRight) - { - switch (textAlignment) - { - case TextAlignment.Center: - return Math.Max(0, (paragraphWidth - width) / 2); - - case TextAlignment.Right: - return Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace); - - default: - return 0; - } - } - - switch (textAlignment) - { - case TextAlignment.Center: - return Math.Max(0, (paragraphWidth - width) / 2); - - case TextAlignment.Right: - return 0; - - default: - return Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace); - } - } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 8b5e2cc2ce..7c686358e2 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -10,10 +10,10 @@ namespace Avalonia.Media.TextFormatting private readonly double _paragraphWidth; private readonly TextParagraphProperties _paragraphProperties; private TextLineMetrics _textLineMetrics; - private readonly FlowDirection _flowDirection; + private readonly FlowDirection _resolvedFlowDirection; public TextLineImpl(List textRuns, int firstTextSourceIndex, int length, double paragraphWidth, - TextParagraphProperties paragraphProperties, FlowDirection flowDirection = FlowDirection.LeftToRight, + TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection = FlowDirection.LeftToRight, TextLineBreak? lineBreak = null, bool hasCollapsed = false) { FirstTextSourceIndex = firstTextSourceIndex; @@ -25,7 +25,7 @@ namespace Avalonia.Media.TextFormatting _paragraphWidth = paragraphWidth; _paragraphProperties = paragraphProperties; - _flowDirection = flowDirection; + _resolvedFlowDirection = resolvedFlowDirection; } /// @@ -136,7 +136,7 @@ namespace Avalonia.Media.TextFormatting } var collapsedLine = new TextLineImpl(collapsedRuns, FirstTextSourceIndex, Length, _paragraphWidth, _paragraphProperties, - _flowDirection, TextLineBreak, true); + _resolvedFlowDirection, TextLineBreak, true); if (collapsedRuns.Count > 0) { @@ -144,7 +144,14 @@ namespace Avalonia.Media.TextFormatting } return collapsedLine; + } + /// + public override void Justify(JustificationProperties justificationProperties) + { + justificationProperties.Justify(this); + + _textLineMetrics = CreateLineMetrics(); } /// @@ -167,7 +174,7 @@ namespace Avalonia.Media.TextFormatting return shapedTextCharacters.GlyphRun.GetCharacterHitFromDistance(distance, out _); } - return _flowDirection == FlowDirection.LeftToRight ? + return _resolvedFlowDirection == FlowDirection.LeftToRight ? new CharacterHit(FirstTextSourceIndex) : new CharacterHit(FirstTextSourceIndex + Length); } @@ -260,7 +267,7 @@ namespace Avalonia.Media.TextFormatting //Look at the left and right edge of the current run if (currentRun.IsLeftToRight) { - if (_flowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight)) + if (_resolvedFlowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight)) { if (characterIndex <= currentPosition) { @@ -735,7 +742,7 @@ namespace Avalonia.Media.TextFormatting // Build up the collection of ordered runs. var run = _textRuns[0]; - OrderedBidiRun orderedRun = new(run, GetRunBidiLevel(run, _flowDirection)); + OrderedBidiRun orderedRun = new(run, GetRunBidiLevel(run, _resolvedFlowDirection)); var current = orderedRun; @@ -743,7 +750,7 @@ namespace Avalonia.Media.TextFormatting { run = _textRuns[i]; - current.Next = new OrderedBidiRun(run, GetRunBidiLevel(run, _flowDirection)); + current.Next = new OrderedBidiRun(run, GetRunBidiLevel(run, _resolvedFlowDirection)); current = current.Next; } @@ -762,7 +769,7 @@ namespace Avalonia.Media.TextFormatting { var currentRun = _textRuns[i]; - var level = GetRunBidiLevel(currentRun, _flowDirection); + var level = GetRunBidiLevel(currentRun, _resolvedFlowDirection); if (level > max) { @@ -1242,8 +1249,7 @@ namespace Avalonia.Media.TextFormatting } } - var start = GetParagraphOffsetX(width, widthIncludingWhitespace, _paragraphWidth, - _paragraphProperties.TextAlignment, _paragraphProperties.FlowDirection); + var start = GetParagraphOffsetX(width, widthIncludingWhitespace); if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight)) { @@ -1257,6 +1263,55 @@ namespace Avalonia.Media.TextFormatting -ascent, trailingWhitespaceLength, width, widthIncludingWhitespace); } + /// + /// Gets the text line offset x. + /// + /// The line width. + /// The paragraph width including whitespace. + + /// The paragraph offset. + private double GetParagraphOffsetX(double width, double widthIncludingTrailingWhitespace) + { + if (double.IsPositiveInfinity(_paragraphWidth)) + { + return 0; + } + + var textAlignment = _paragraphProperties.TextAlignment; + var paragraphFlowDirection = _paragraphProperties.FlowDirection; + + switch (textAlignment) + { + case TextAlignment.Start: + { + textAlignment = paragraphFlowDirection == FlowDirection.LeftToRight ? TextAlignment.Left : TextAlignment.Right; + break; + } + case TextAlignment.End: + { + textAlignment = paragraphFlowDirection == FlowDirection.RightToLeft ? TextAlignment.Left : TextAlignment.Right; + break; + } + case TextAlignment.DetectFromContent: + { + textAlignment = _resolvedFlowDirection == FlowDirection.LeftToRight ? TextAlignment.Left : TextAlignment.Right; + break; + } + } + + switch (textAlignment) + { + case TextAlignment.Center: + return Math.Max(0, (_paragraphWidth - width) / 2); + + case TextAlignment.Right: + return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace); + + default: + return 0; + } + } + private sealed class OrderedBidiRun { public OrderedBidiRun(DrawableTextRun run, sbyte level) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDi.trie.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDi.trie.cs new file mode 100644 index 0000000000..7ce5453ec4 --- /dev/null +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDi.trie.cs @@ -0,0 +1,42 @@ +using System; + +namespace Avalonia.Media.TextFormatting.Unicode +{ + internal static class BiDiTrie + { + public static ReadOnlySpan Data => new byte[] + { + 0,16,0,0,0,0,0,0,0,0,174,224,0,68,12,187,243,236,157,11,176,85,85,25,199,151,115,207,189,247,156,203,227,94,244,132,151,128,58,122,53,184,10,74,112,26,206,61,64,105,130,89,22,163,121,85,12,17,38,26,7,197,72,37,152,166,59,56,142,132,236,64,144,16,196,212,6,26,167,98,34,197,158,71,83,10,134,116,116,236,161,165,4,50,38,61,40,64,137,178, + 28,109,6,164,255,102,175,205,93,44,246,99,61,247,62,192,254,102,126,243,173,189,246,218,235,251,214,183,190,253,60,251,156,51,171,129,144,91,193,87,192,157,224,1,176,14,172,7,143,3,7,172,144,208,63,3,191,0,207,130,223,130,109,224,79,224,85,240,23,176,15,188,5,246,131,183,3,182,63,4,26,115,225,253,247,193,186,51,192,96,208,1,206,7,99,64,23,184,24,92, + 10,38,131,43,193,117,96,6,152,5,230,128,249,96,30,88,0,22,129,101,224,223,121,66,222,5,7,65,174,64,200,80,212,141,6,99,193,90,44,175,134,94,227,150,115,132,124,15,108,4,143,131,39,192,102,240,28,93,126,17,188,194,44,191,6,118,211,246,251,193,219,116,251,67,57,111,44,141,141,132,20,192,0,112,6,24,12,58,26,123,251,63,191,209,107,127,24,109,47,108, + 244,236,249,246,29,212,141,109,244,180,203,4,148,39,49,203,159,68,249,74,186,124,53,244,245,180,252,5,232,91,192,92,112,144,246,227,128,30,44,47,4,75,193,125,76,63,142,65,30,162,253,62,194,245,191,193,176,189,31,115,253,61,133,229,167,192,211,24,239,211,116,204,91,176,252,124,163,231,203,75,92,251,237,88,126,148,137,141,3,118,161,110,15,215,110,31,150,107,200,143, + 183,104,253,255,160,79,107,242,202,5,232,211,105,253,155,232,107,64,147,55,239,131,154,188,249,60,139,182,27,78,245,72,170,29,134,49,168,27,23,80,239,80,182,129,223,180,168,197,232,227,232,247,83,96,50,184,6,244,96,28,27,193,13,40,223,8,22,162,188,24,204,199,24,230,96,121,62,184,131,250,114,119,147,55,110,159,217,76,185,22,192,38,112,15,221,118,37,244,55,185, + 237,107,224,48,234,182,210,242,90,102,204,223,13,104,91,19,96,67,200,118,47,48,229,223,131,29,133,248,88,241,125,252,153,234,61,224,128,164,95,239,128,67,92,157,19,149,203,77,92,46,55,29,159,3,110,31,141,200,131,45,180,175,124,75,111,223,173,76,185,198,113,38,214,13,5,91,209,231,11,76,191,127,104,234,221,39,55,198,196,229,231,104,187,137,46,239,68,249,175,180, + 159,55,160,255,195,249,234,128,119,154,142,221,127,30,51,184,223,59,25,36,139,1,57,101,98,80,19,56,126,56,25,36,139,1,201,98,208,144,197,32,139,1,201,98,208,144,197,32,139,1,201,98,208,144,197,160,94,99,176,179,201,187,174,117,159,9,56,2,237,115,205,189,229,66,179,247,44,205,95,190,150,62,163,217,203,212,13,96,218,59,96,16,150,75,180,110,24,179,110,36, + 182,185,144,46,143,133,254,40,45,175,198,115,156,75,104,249,49,250,156,232,114,44,95,213,236,61,183,115,151,175,109,246,158,211,77,231,108,29,224,158,33,221,200,173,119,50,72,22,3,146,197,160,33,139,65,22,3,146,197,160,33,139,65,22,3,146,197,160,225,196,141,193,205,184,198,107,193,53,104,255,130,247,185,178,10,231,182,28,95,247,29,212,141,4,31,1,75,209,247,189, + 96,46,103,227,81,172,187,28,117,183,195,135,175,130,187,184,235,205,53,117,240,252,184,212,223,99,6,88,71,203,165,0,182,131,246,86,66,166,128,89,96,61,248,73,107,239,250,255,50,229,18,24,209,134,207,186,193,15,192,223,64,251,0,66,38,130,59,193,19,224,101,80,60,29,109,17,163,85,125,97,31,122,45,88,119,90,60,175,11,182,251,96,159,222,114,55,202,171,192,203, + 224,69,216,43,130,182,126,98,253,216,162,27,62,172,203,236,147,180,98,208,157,197,159,100,249,71,82,139,65,119,150,127,36,237,252,187,2,231,128,25,253,78,221,249,143,187,62,88,210,124,236,231,221,43,155,123,223,79,89,207,189,167,229,112,92,197,93,223,60,88,135,207,219,54,98,12,223,134,95,235,221,103,134,205,222,251,97,63,106,238,93,239,62,111,124,146,46,255,146,174, + 255,21,244,175,153,54,47,49,101,135,121,87,104,71,115,239,251,131,238,251,104,187,2,218,57,13,158,253,61,204,186,127,161,252,110,72,91,7,28,196,186,92,222,43,187,244,205,123,253,223,79,183,41,230,143,125,239,111,8,179,236,112,156,21,177,206,1,195,177,126,20,215,102,12,181,55,14,250,99,224,19,116,253,228,152,190,156,0,174,102,182,153,134,242,76,174,143,155,176,124, + 27,173,155,39,209,255,135,35,222,107,236,65,63,11,21,124,117,100,200,123,122,57,213,78,138,108,166,121,177,138,241,229,225,58,240,203,225,120,30,251,218,79,21,223,169,116,20,120,4,49,216,0,126,200,196,162,70,143,115,19,66,252,168,209,245,155,19,190,119,219,72,245,34,58,151,223,247,215,53,123,250,73,140,97,170,1,159,182,129,45,121,79,63,7,253,59,240,74,130,115, + 226,104,242,26,124,221,29,227,239,114,197,220,255,99,29,238,51,14,71,13,57,240,25,238,29,212,207,130,107,90,188,119,78,95,207,247,230,206,110,58,158,197,52,135,28,142,21,1,159,1,214,20,222,7,174,25,96,106,139,119,237,193,214,181,50,227,156,137,242,46,193,207,44,157,147,148,189,121,239,251,19,81,12,44,68,175,255,0,214,159,29,210,230,60,90,63,34,166,143,161, + 17,140,118,191,71,81,16,107,91,166,237,198,115,237,251,23,188,239,144,248,223,23,25,26,192,69,220,186,75,4,109,14,213,160,127,193,27,223,104,166,110,52,37,23,98,127,116,4,147,90,226,109,250,251,250,108,170,167,67,207,101,142,127,53,212,213,40,61,45,225,239,232,187,251,210,93,96,9,88,1,86,211,62,106,76,155,26,248,22,237,99,43,152,89,72,63,223,55,193,159, + 77,17,108,67,187,109,33,60,19,179,237,166,83,0,66,240,112,54,150,110,128,155,197,35,122,10,45,183,41,210,151,161,155,246,87,161,148,24,42,71,232,36,85,50,156,116,209,229,33,160,200,232,34,104,23,164,200,216,169,112,36,35,211,48,22,2,123,83,143,142,167,66,73,70,22,80,251,61,71,237,183,37,99,184,174,165,45,128,190,33,245,109,22,40,6,228,126,137,214,85, + 24,92,241,203,238,118,126,217,109,235,226,231,57,223,214,197,173,231,235,216,126,109,137,237,254,235,221,126,82,34,50,151,65,115,47,130,169,237,101,125,209,177,201,82,62,197,9,139,125,156,196,205,93,84,155,147,81,84,198,42,51,47,39,155,4,29,59,220,243,212,196,58,216,39,202,10,76,228,116,57,164,60,241,36,39,31,66,133,146,163,243,92,162,229,34,213,21,74,89,130, + 92,157,83,182,64,94,144,18,83,206,113,62,229,50,8,31,131,184,152,71,229,38,223,71,80,91,191,141,232,253,168,201,57,74,115,206,77,230,254,201,156,183,73,197,33,237,243,131,12,34,177,240,219,250,231,15,31,190,159,160,62,211,20,223,126,144,31,113,99,14,235,75,100,123,25,255,210,138,81,154,115,35,50,31,42,125,232,138,238,124,184,215,3,46,113,109,88,123,166,108, + 235,74,84,222,7,249,25,180,63,216,244,69,182,255,176,253,82,116,255,54,33,58,115,26,151,71,108,255,170,199,31,83,194,231,135,175,77,238,207,42,99,59,145,142,173,182,124,229,207,217,62,110,126,185,90,196,151,180,207,227,190,196,229,83,216,190,46,146,59,170,249,101,35,54,97,115,22,54,95,113,146,246,117,134,170,109,209,57,139,186,206,83,149,176,115,94,92,78,197,29, + 183,101,175,59,85,69,212,127,157,188,79,235,188,147,132,61,62,167,202,100,92,107,149,84,91,187,200,4,232,241,208,230,237,149,21,8,219,150,173,15,18,19,54,146,68,87,100,198,196,174,227,183,79,66,194,108,217,142,135,74,31,124,127,34,109,117,109,165,33,38,99,31,117,126,23,57,215,171,94,7,152,18,247,125,161,100,228,225,129,85,242,208,64,211,199,90,95,76,206,169, + 136,29,222,166,236,62,24,180,191,233,30,59,253,235,21,182,157,104,126,242,207,224,221,119,98,76,73,26,199,219,168,99,174,104,63,42,54,248,253,89,231,90,223,164,36,225,71,208,121,214,166,45,214,102,212,185,159,247,69,117,31,147,221,71,109,156,219,147,190,214,10,242,39,204,47,118,189,106,223,182,198,19,53,71,65,99,50,45,188,141,168,117,54,99,200,183,211,149,160,249, + 87,201,81,153,57,143,219,71,131,252,75,83,252,227,174,234,179,31,221,103,70,166,182,119,197,189,94,20,133,125,47,215,149,160,207,29,101,9,123,239,119,4,248,52,56,7,92,20,209,174,157,27,91,123,8,67,40,21,114,71,169,74,22,148,252,235,214,176,246,225,219,47,199,246,203,142,110,175,43,236,92,4,221,99,200,206,179,104,110,70,221,215,132,105,86,68,62,3,9,178, + 39,226,135,136,191,81,219,203,32,227,187,168,255,114,156,215,81,37,157,29,252,119,30,76,248,109,90,244,199,26,157,115,21,1,218,13,115,34,141,93,180,63,81,223,121,17,189,94,145,181,159,148,184,126,179,223,151,58,222,167,194,168,42,201,143,234,34,125,160,91,160,251,65,247,133,110,133,238,15,61,0,186,13,154,208,109,223,135,229,34,150,207,132,30,8,61,8,186,29,122, + 48,244,251,153,118,68,58,6,101,14,95,226,230,48,106,93,82,241,247,125,182,97,211,70,254,219,20,27,182,147,28,127,146,241,211,57,86,37,57,183,182,108,242,251,188,219,63,95,23,134,168,63,73,30,3,162,150,195,234,76,217,246,251,230,115,68,214,166,200,123,97,58,34,155,195,162,249,16,135,138,68,109,103,195,158,136,63,54,199,43,98,63,204,23,190,94,182,223,168,49, + 152,176,225,111,103,162,141,138,77,217,88,233,140,209,244,56,121,159,117,250,178,33,236,59,221,201,157,23,39,28,246,190,31,63,238,112,58,223,207,159,70,237,79,165,246,103,96,121,58,45,207,68,249,243,199,248,149,164,164,121,223,163,123,141,88,175,215,216,73,29,227,69,236,154,56,231,156,8,247,53,186,231,184,176,24,165,113,238,246,237,218,236,219,212,184,162,226,110,82,162, + 108,196,249,28,118,30,76,99,94,227,108,199,181,211,181,231,47,219,20,27,253,71,197,43,106,126,249,114,26,34,58,135,105,251,108,211,166,232,254,166,154,163,65,219,201,230,76,84,223,34,231,5,155,34,122,204,78,43,215,69,246,199,164,37,42,199,194,252,17,201,207,36,198,34,234,135,168,63,162,62,203,216,21,21,145,220,77,34,151,69,237,217,222,175,85,226,33,114,92,11, + 138,91,82,49,228,219,132,249,97,226,120,172,42,113,190,218,144,184,123,35,255,25,174,143,238,189,150,201,251,45,219,251,130,72,174,155,234,91,71,252,152,166,113,14,243,69,118,44,113,191,77,104,234,88,99,67,146,176,35,179,159,152,122,95,67,245,184,47,115,110,240,197,84,63,65,253,138,74,146,57,162,98,203,100,158,185,199,110,153,92,176,121,220,78,251,51,104,94,252,99, + 78,82,191,53,19,246,251,51,124,125,84,187,52,126,39,135,95,167,251,187,68,38,252,115,223,27,181,53,246,122,24,159,42,238,254,20,149,79,162,121,154,86,110,197,249,152,100,28,195,16,253,189,60,27,168,198,218,214,252,37,145,59,174,4,221,151,120,117,5,124,78,153,63,250,57,229,16,250,251,143,67,168,46,74,156,251,138,33,243,93,150,232,163,221,0,188,152,232,211,164, + 63,178,162,107,199,196,189,167,159,27,58,247,178,238,246,46,252,181,11,155,151,65,215,51,186,231,147,56,191,252,255,71,24,14,46,3,227,193,40,112,5,205,233,82,0,21,9,74,41,83,214,164,162,73,145,209,174,176,117,30,159,123,175,74,174,123,175,139,92,15,61,21,250,6,232,105,208,36,52,230,37,138,223,95,24,174,136,252,166,127,208,182,252,61,181,123,254,208,141,165, + 10,113,191,75,25,151,255,162,231,70,209,253,167,72,46,45,85,201,164,35,223,55,170,88,102,74,2,54,42,145,20,58,112,126,196,119,97,250,64,183,72,127,39,166,34,137,13,137,182,121,211,176,42,153,53,172,139,204,134,190,25,250,139,208,183,64,127,9,122,14,244,109,208,183,66,207,133,190,29,122,30,244,151,161,137,196,184,158,197,54,207,72,110,67,52,249,7,108,254,29, + 54,247,66,239,129,126,3,122,31,244,126,232,55,161,15,64,255,211,176,79,95,235,172,146,133,157,93,228,110,232,69,208,14,244,98,232,37,208,95,135,190,7,122,41,244,10,232,123,161,151,67,47,131,94,9,253,13,232,85,208,247,65,223,15,189,26,250,1,232,53,157,102,125,140,99,7,108,110,135,205,157,208,175,38,108,155,128,67,176,123,208,170,93,95,108,143,69,206,70,199, + 5,85,114,246,5,93,228,92,232,115,160,135,65,127,8,186,19,122,56,52,73,120,30,236,197,72,86,108,231,129,174,232,218,77,235,247,173,125,127,116,255,15,73,213,182,202,239,108,235,92,159,134,137,191,158,239,51,41,49,53,143,182,238,143,226,226,104,99,159,172,199,227,150,234,113,36,109,31,146,242,223,244,248,249,237,85,37,169,248,155,154,195,184,216,198,197,92,103,174,77, + 140,67,39,31,85,164,30,198,171,147,7,186,199,255,52,206,223,166,8,242,39,105,177,241,31,145,50,232,94,255,164,109,95,20,87,100,234,101,250,213,17,21,159,85,125,20,181,21,214,214,190,252,31,0,0,255,255,0,0,0,255,255,99,102,0,0}; + } +} diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.trie.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.trie.cs new file mode 100644 index 0000000000..80158e9d7e --- /dev/null +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.trie.cs @@ -0,0 +1,31 @@ +using System; + +namespace Avalonia.Media.TextFormatting.Unicode +{ + internal static class GraphemeBreakTrie + { + public static ReadOnlySpan Data => new byte[] + { + 0,14,16,0,0,0,0,0,0,0,133,64,0,245,7,10,248,236,157,127,136,29,87,21,199,103,157,111,246,157,100,211,138,180,69,177,5,241,7,88,91,40,182,150,210,12,138,161,88,27,163,96,241,15,75,161,210,18,44,149,10,193,46,24,80,49,254,163,33,16,136,54,127,84,8,34,18,253,199,68,196,42,136,154,82,21,149,6,218,82,43,72,107,161,54,22,196,173,10,110, + 16,140,210,82,250,125,190,51,228,228,228,206,204,157,121,119,230,109,182,243,133,15,231,220,115,207,156,123,239,121,111,247,253,216,129,189,33,207,178,130,220,66,118,147,61,228,126,19,43,230,180,171,228,139,100,31,217,31,145,127,128,28,174,153,63,66,142,146,99,228,56,249,49,249,5,249,21,57,101,242,158,36,127,36,207,145,23,201,26,89,39,255,34,103,201,43,4,200,178,47, + 147,175,146,131,152,93,91,242,40,217,206,216,55,200,17,114,148,28,35,199,201,9,242,19,114,146,252,134,252,150,60,65,158,54,227,63,145,191,104,254,75,228,140,94,255,63,242,32,89,218,50,91,103,43,237,25,218,55,109,57,87,255,205,244,223,166,227,119,211,190,135,92,110,214,47,152,127,61,99,103,39,51,127,7,253,157,90,175,32,31,162,255,49,29,223,78,123,135,250,119, + 211,222,71,246,146,255,154,243,238,227,120,63,57,64,14,155,58,5,185,82,206,241,46,229,26,242,126,165,48,185,69,13,95,143,204,43,18,243,16,207,243,16,121,158,231,125,94,207,252,45,142,191,87,246,7,231,231,95,193,248,247,93,15,30,118,227,194,240,51,157,251,37,237,99,234,239,37,47,235,252,83,140,253,129,60,71,78,147,53,178,174,121,103,213,190,26,168,143,101,62, + 255,150,47,140,151,172,146,203,107,230,139,72,62,201,243,223,165,61,184,201,196,247,146,7,106,206,93,140,100,125,247,224,42,247,248,158,12,60,7,138,4,252,213,252,12,188,61,193,115,170,24,201,198,30,100,99,15,242,177,7,99,15,178,177,7,249,216,131,177,7,217,216,131,124,236,193,216,131,108,236,65,62,246,96,236,65,150,188,7,87,47,207,190,75,43,199,159,136,248,254, + 226,189,188,230,102,243,153,247,24,191,83,123,133,236,100,108,151,198,111,167,189,131,92,37,179,239,3,239,162,15,250,159,214,249,251,105,87,201,62,29,239,167,253,51,237,129,134,207,210,135,57,127,43,235,124,152,220,70,118,145,143,144,221,228,163,228,214,113,46,27,251,146,141,207,9,25,127,30,198,223,5,89,178,223,5,55,144,29,228,3,164,24,223,139,100,99,15,178,177,7, + 249,216,131,62,122,112,164,231,191,167,236,87,138,0,187,3,177,51,198,191,199,253,237,179,88,0,187,46,153,113,144,172,109,111,230,20,243,158,13,228,190,229,210,243,199,187,56,62,72,78,145,236,141,179,216,141,180,159,33,223,33,143,144,117,178,115,37,203,62,71,94,220,202,191,125,109,203,50,89,154,113,167,241,197,113,98,229,194,88,136,47,153,26,63,162,255,15,94,119,45, + 247,113,47,57,176,61,174,198,72,54,246,96,105,236,193,216,131,108,236,193,210,230,235,193,58,95,23,50,190,46,60,202,247,9,167,249,122,248,146,222,231,177,198,239,174,222,202,215,231,119,232,107,244,223,201,239,152,179,194,207,44,143,47,207,230,127,79,123,141,121,13,127,54,240,94,227,36,57,189,124,238,62,178,53,250,235,21,239,73,254,195,248,171,102,110,153,223,131,93,170, + 247,152,21,1,46,227,220,149,102,254,157,147,115,247,61,77,185,118,114,254,253,95,239,171,169,117,115,205,92,65,174,227,57,111,114,239,87,62,200,107,110,35,31,159,94,11,222,203,164,53,110,113,121,69,4,159,50,235,223,75,255,179,110,63,15,112,188,91,235,126,161,97,175,133,225,43,45,114,139,30,185,175,67,79,138,158,249,46,123,179,99,192,245,246,178,7,39,184,230,195, + 129,199,228,231,27,228,113,42,90,178,71,239,3,91,85,246,56,86,107,248,60,46,204,127,189,81,204,193,209,134,207,118,47,187,241,177,13,120,111,221,113,238,233,135,21,251,186,34,209,253,159,63,104,121,238,159,6,242,207,246,220,135,175,233,207,255,161,13,240,123,224,65,238,225,50,190,206,127,211,236,229,219,9,246,181,74,126,61,153,217,83,180,79,145,103,54,192,121,139,72, + 94,224,94,255,214,176,223,67,29,207,243,207,139,160,15,135,220,30,255,125,17,236,185,232,192,35,27,96,15,215,231,75,255,39,231,219,243,60,130,55,24,127,41,242,154,60,130,81,139,211,216,255,44,217,243,56,239,200,162,31,55,188,206,73,41,212,176,153,133,139,144,114,223,165,197,212,9,248,155,93,147,10,98,133,22,164,254,221,149,90,232,129,20,123,26,66,168,97,210,144, + 227,107,100,53,57,41,53,81,139,148,69,91,10,9,217,204,66,11,46,86,33,130,216,58,214,135,62,215,177,0,36,16,203,52,46,138,31,195,252,108,214,9,117,147,145,18,3,28,226,226,165,38,165,211,81,147,142,215,133,36,166,230,36,148,16,56,87,121,29,166,206,156,130,214,18,199,84,162,216,92,104,12,186,95,49,22,101,226,130,132,10,68,247,8,71,72,147,10,208,1, + 49,126,149,80,193,68,237,144,130,33,239,161,126,174,181,183,168,159,39,170,153,87,144,66,121,75,134,88,175,73,112,207,61,184,121,113,115,80,242,6,144,128,124,78,250,18,28,161,24,166,193,134,49,76,124,40,193,80,55,39,126,114,64,65,215,135,142,197,128,6,36,144,47,74,89,27,21,196,10,202,34,4,119,158,161,215,246,123,128,163,148,40,126,140,50,208,32,52,204,251, + 92,216,64,207,130,243,225,206,58,148,224,122,43,26,67,69,46,2,99,84,212,76,181,63,4,98,177,18,205,135,99,42,40,50,29,168,21,141,165,22,148,190,132,10,134,22,140,133,233,39,12,165,68,241,99,148,129,10,193,16,43,196,38,38,22,2,231,204,76,76,166,3,55,6,137,17,34,115,196,128,152,194,9,5,183,190,152,56,20,63,198,52,80,35,40,139,16,212,138,250, + 18,240,203,60,81,166,18,5,74,157,160,196,8,138,40,139,20,148,210,151,169,19,41,84,196,68,129,193,230,75,13,208,156,121,5,181,162,62,42,136,169,131,26,82,9,145,251,133,201,109,83,27,1,134,18,148,190,215,8,89,43,81,82,158,9,45,145,138,184,173,25,18,90,208,54,31,61,48,175,16,217,55,184,124,113,185,125,74,26,214,66,205,92,91,193,209,69,80,43,234, + 35,34,183,171,208,245,194,134,154,80,250,20,20,49,62,2,72,3,112,62,34,137,221,99,95,130,65,28,8,196,189,68,129,241,197,1,67,159,130,34,10,122,90,71,204,58,168,161,220,147,181,165,143,26,164,2,68,82,10,1,164,156,28,72,168,65,140,21,7,140,191,40,33,130,174,53,165,237,133,9,37,129,115,136,139,139,155,79,45,184,53,196,0,55,7,51,215,86,210,0, + 26,16,151,55,175,96,240,99,68,34,13,243,94,240,129,138,57,209,49,76,204,11,3,81,181,214,60,130,97,72,65,73,85,7,74,40,134,10,188,208,48,174,138,165,148,40,80,36,144,131,158,214,20,183,246,80,66,34,186,174,93,55,135,26,250,16,90,32,74,234,245,69,129,67,92,92,148,170,58,67,10,21,136,130,138,113,151,117,98,114,164,41,169,71,161,167,122,48,216,57, + 148,3,39,40,109,36,230,58,81,234,242,218,74,20,84,248,153,137,45,90,216,228,107,35,64,76,174,29,135,4,71,223,130,33,52,215,70,80,82,74,180,166,164,44,218,81,8,32,21,113,24,250,18,18,215,19,3,34,144,132,107,136,137,137,137,87,9,106,197,128,6,250,16,220,30,196,204,73,197,88,90,212,142,149,40,246,90,84,96,133,0,125,75,12,104,64,2,121,162,182, + 79,33,97,29,81,80,129,4,242,36,48,143,68,123,234,42,232,94,68,125,24,74,137,3,138,24,127,72,65,73,37,49,160,1,49,121,162,182,141,96,174,19,227,35,128,168,109,146,40,112,72,32,134,166,98,29,36,6,68,32,198,79,37,49,192,81,10,138,56,80,38,168,68,237,144,66,36,67,236,35,54,15,1,68,129,97,30,137,171,5,199,84,168,64,212,166,18,58,146,153, + 189,160,2,169,240,83,9,21,248,156,210,98,234,24,27,35,9,80,214,192,212,49,113,24,155,82,80,68,73,89,19,134,148,66,13,41,106,103,166,31,210,129,58,193,209,69,104,153,47,202,34,5,197,250,152,14,84,168,161,47,193,209,103,253,33,133,30,73,181,86,147,208,148,208,49,183,173,16,160,42,142,72,230,21,106,104,154,71,36,165,80,131,157,207,106,114,82,11,142,212, + 245,96,232,83,162,244,85,87,90,176,104,137,50,228,90,178,32,182,110,0,172,182,45,152,149,158,185,196,209,86,219,18,226,181,178,1,240,226,191,181,184,160,103,125,50,244,122,94,175,1,0,0,255,255,0,0,0,255,255,99,102,0,0}; + } +} diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.cs index 471cb52bea..ff39dc5011 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.IO; +using System.Runtime.CompilerServices; namespace Avalonia.Media.TextFormatting.Unicode { @@ -17,14 +18,14 @@ namespace Avalonia.Media.TextFormatting.Unicode internal const int SCRIPT_SHIFT = CATEGORY_BITS; internal const int LINEBREAK_SHIFT = CATEGORY_BITS + SCRIPT_BITS; - + internal const int BIDIPAIREDBRACKEDTYPE_SHIFT = BIDIPAIREDBRACKED_BITS; internal const int BIDICLASS_SHIFT = BIDIPAIREDBRACKED_BITS + BIDIPAIREDBRACKEDTYPE_BITS; - + internal const int CATEGORY_MASK = (1 << CATEGORY_BITS) - 1; internal const int SCRIPT_MASK = (1 << SCRIPT_BITS) - 1; internal const int LINEBREAK_MASK = (1 << LINEBREAK_BITS) - 1; - + internal const int BIDIPAIREDBRACKED_MASK = (1 << BIDIPAIREDBRACKED_BITS) - 1; internal const int BIDIPAIREDBRACKEDTYPE_MASK = (1 << BIDIPAIREDBRACKEDTYPE_BITS) - 1; internal const int BIDICLASS_MASK = (1 << BIDICLASS_BITS) - 1; @@ -35,9 +36,29 @@ namespace Avalonia.Media.TextFormatting.Unicode static UnicodeData() { - s_unicodeDataTrie = new UnicodeTrie(typeof(UnicodeData).Assembly.GetManifestResourceStream("Avalonia.Assets.UnicodeData.trie")!); - s_graphemeBreakTrie = new UnicodeTrie(typeof(UnicodeData).Assembly.GetManifestResourceStream("Avalonia.Assets.GraphemeBreak.trie")!); - s_biDiTrie = new UnicodeTrie(typeof(UnicodeData).Assembly.GetManifestResourceStream("Avalonia.Assets.BiDi.trie")!); + unsafe + { + var unicodeData = UnicodeDataTrie.Data; + + fixed (byte* unicodeDataPtr = unicodeData) + { + s_unicodeDataTrie = new UnicodeTrie(new UnmanagedMemoryStream(unicodeDataPtr, unicodeData.Length)); + } + + var graphemeData = GraphemeBreakTrie.Data; + + fixed (byte* graphemeDataPtr = graphemeData) + { + s_graphemeBreakTrie = new UnicodeTrie(new UnmanagedMemoryStream(graphemeDataPtr, graphemeData.Length)); + } + + var bidiData = BiDiTrie.Data; + + fixed (byte* bidiDataPtr = bidiData) + { + s_biDiTrie = new UnicodeTrie(new UnmanagedMemoryStream(bidiDataPtr, bidiData.Length)); + } + } } /// @@ -72,7 +93,7 @@ namespace Avalonia.Media.TextFormatting.Unicode { return (BidiClass)((s_biDiTrie.Get(codepoint) >> BIDICLASS_SHIFT) & BIDICLASS_MASK); } - + /// /// Gets the for a Unicode codepoint. /// @@ -83,7 +104,7 @@ namespace Avalonia.Media.TextFormatting.Unicode { return (BidiPairedBracketType)((s_biDiTrie.Get(codepoint) >> BIDIPAIREDBRACKEDTYPE_SHIFT) & BIDIPAIREDBRACKEDTYPE_MASK); } - + /// /// Gets the paired bracket for a Unicode codepoint. /// diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.trie.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.trie.cs new file mode 100644 index 0000000000..dd55fda374 --- /dev/null +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeData.trie.cs @@ -0,0 +1,122 @@ +using System; + +namespace Avalonia.Media.TextFormatting.Unicode +{ + internal static class UnicodeDataTrie + { + public static ReadOnlySpan Data => new byte[] + { + 0,16,0,0,0,0,0,0,0,1,154,144,0,161,43,94,212,236,157,15,148,28,85,157,239,107,230,215,147,204,84,146,78,103,152,252,115,128,16,8,241,224,234,238,193,197,221,163,139,231,117,2,132,158,162,51,52,67,155,63,244,152,233,66,130,202,81,247,225,209,221,167,7,125,13,139,74,202,48,84,134,89,16,199,5,70,81,244,249,111,117,209,93,93,229,5,68,157,41,218, + 177,9,35,168,3,72,16,124,60,149,125,111,87,217,231,62,193,247,190,53,117,59,115,231,166,254,220,234,170,234,10,58,57,231,147,223,253,255,251,221,63,117,235,214,173,59,213,151,100,20,101,47,208,193,91,193,181,224,0,56,4,110,3,147,224,51,224,139,224,31,193,127,7,223,5,223,7,143,130,199,193,51,224,57,240,60,248,13,80,186,20,165,27,228,192,6,112,26,56,19, + 188,10,188,6,188,30,108,7,26,24,2,151,129,125,224,74,240,14,240,87,224,253,224,131,224,58,96,128,155,192,173,224,239,192,39,193,231,193,87,192,55,193,183,193,247,192,15,192,143,193,83,224,25,240,28,120,30,188,8,104,153,162,172,0,39,129,126,112,6,248,35,240,185,94,69,249,83,200,191,0,231,129,29,96,16,148,193,59,215,41,202,217,39,33,30,252,5,184,7,105, + 135,17,126,57,184,202,142,95,166,40,239,5,53,112,3,48,193,71,192,237,224,83,224,11,224,171,224,27,224,1,80,7,179,224,49,240,52,248,5,248,21,248,15,208,177,92,81,122,192,26,176,30,108,2,47,7,127,2,206,131,238,63,135,252,79,224,66,48,8,118,129,125,224,74,112,21,120,231,114,199,246,247,66,94,11,14,48,255,69,200,251,94,212,227,16,252,183,128,219,193, + 39,193,231,89,252,165,136,223,11,116,240,86,112,53,184,100,13,218,22,241,23,160,190,239,129,255,61,140,107,25,31,2,7,24,135,24,223,92,238,200,111,67,78,131,135,152,255,81,38,31,135,124,6,60,7,246,162,252,189,224,121,184,111,67,220,71,99,226,14,240,34,202,188,27,242,110,208,213,173,40,171,192,90,112,10,56,19,124,17,225,95,6,175,130,251,53,224,245,224,235, + 240,127,3,117,189,0,238,251,224,222,217,237,148,119,9,228,94,160,131,183,130,119,128,105,132,255,21,228,53,96,207,6,69,185,30,242,33,132,29,132,28,7,31,3,119,129,207,117,59,237,251,35,196,61,102,143,157,110,232,0,79,195,253,75,240,107,240,0,252,47,64,214,33,103,193,3,176,225,49,200,39,193,179,224,127,131,127,197,245,180,19,225,117,240,6,123,252,245,34,109, + 175,115,93,253,166,219,185,62,121,244,53,139,253,74,15,108,71,88,6,114,37,184,2,121,251,32,79,6,91,192,107,215,43,202,54,48,0,94,9,255,57,160,128,122,157,11,121,62,184,26,121,223,3,174,5,22,198,81,17,97,159,66,218,207,130,47,129,175,129,251,192,52,120,8,252,8,60,9,158,5,207,129,171,193,123,64,21,101,62,15,249,34,200,192,221,3,214,108,112,194, + 47,69,153,21,240,12,202,255,25,120,11,194,222,2,158,227,220,111,7,87,131,247,128,141,96,51,56,11,92,11,14,128,67,224,108,48,6,62,10,62,14,254,27,248,50,211,241,85,91,255,58,69,249,237,58,199,127,45,236,184,1,220,11,247,189,62,60,203,234,242,44,195,100,242,86,112,187,16,247,172,11,85,148,241,90,144,7,111,178,251,162,199,9,107,114,0,237,122,53,194, + 222,13,14,193,125,27,199,251,16,246,54,244,215,7,32,51,125,138,242,97,200,155,193,4,227,58,212,229,173,208,241,24,210,124,162,199,25,111,159,133,252,14,202,253,142,93,247,158,5,183,200,215,17,119,24,60,141,188,191,4,83,112,175,132,142,6,100,31,228,201,224,215,8,255,33,252,143,129,45,240,111,1,71,225,126,37,228,140,221,54,61,24,163,128,96,67,15,120,100,131, + 227,94,3,158,128,251,41,240,115,198,47,55,56,121,68,206,65,89,231,44,161,44,181,129,178,212,6,125,242,109,208,156,63,206,9,145,231,156,37,148,165,54,80,150,218,160,111,169,13,150,218,64,89,106,131,190,165,54,88,106,3,101,169,13,250,254,48,219,224,55,120,118,123,1,207,120,231,194,125,238,9,130,162,226,185,19,235,218,229,144,25,236,141,156,143,176,243,125,88,137, + 52,171,85,103,239,112,18,207,204,159,1,69,132,23,193,122,132,247,33,254,75,108,79,228,171,144,247,130,7,192,38,196,157,14,202,72,247,10,200,97,200,87,67,94,1,249,102,240,58,184,255,18,242,175,193,251,193,118,248,53,240,65,184,13,48,4,247,101,96,12,238,203,33,175,2,39,219,251,80,125,216,23,0,239,132,255,189,224,58,96,128,119,161,157,15,65,94,3,121,13, + 248,20,210,220,6,255,23,32,191,8,254,30,124,9,124,25,252,3,184,7,124,97,41,78,89,106,23,101,105,76,244,45,93,15,75,115,129,18,219,92,48,137,121,247,211,224,179,224,106,220,23,174,78,17,29,123,165,122,138,236,93,66,89,106,3,101,169,13,214,187,183,65,152,245,244,63,168,11,238,127,132,251,94,240,29,48,3,30,81,157,247,212,54,223,64,252,19,156,255,219, + 1,60,128,244,218,50,199,253,83,150,239,127,66,62,7,158,183,215,151,235,240,94,13,178,107,197,226,124,43,224,63,9,212,177,222,189,30,107,206,45,88,159,158,5,250,17,118,6,248,163,21,233,206,61,54,15,176,245,120,6,246,157,223,235,216,105,191,139,125,193,126,46,57,9,235,102,112,15,120,18,156,134,118,168,130,113,48,11,186,215,226,93,37,248,47,224,159,192,111,64, + 55,218,226,92,240,46,112,15,248,53,56,7,122,222,2,198,193,44,88,137,231,139,34,56,104,239,161,171,138,114,39,120,18,156,134,246,168,130,15,131,7,153,124,142,201,15,115,188,106,37,222,215,195,230,14,240,118,184,63,191,114,113,188,205,115,8,91,181,106,193,191,19,238,27,193,247,192,170,44,252,224,70,112,103,71,188,124,143,149,217,181,26,239,179,87,59,238,18,147,119, + 50,62,8,255,167,192,207,184,240,173,57,39,172,157,108,203,29,223,110,75,40,75,109,176,98,169,13,150,218,64,249,131,104,131,107,83,158,3,21,156,151,82,82,226,190,92,186,207,94,39,2,127,138,62,120,29,216,190,194,57,191,83,199,186,166,206,208,16,54,4,222,0,142,96,29,243,3,240,70,184,247,131,17,236,221,190,157,229,217,193,241,238,21,206,218,232,125,144,31,0, + 143,193,125,35,228,223,130,219,192,36,120,26,97,159,97,121,103,177,134,249,49,56,104,159,3,234,195,115,50,194,191,6,190,9,190,5,126,221,231,172,133,94,128,60,226,162,47,131,53,87,38,128,31,35,223,83,224,231,46,249,255,13,97,207,131,23,193,74,164,237,178,215,77,43,157,184,181,144,167,128,51,153,127,7,232,91,235,172,191,94,3,54,98,45,247,122,200,243,65,17, + 156,140,184,45,107,157,116,175,132,44,35,236,28,200,97,200,43,192,219,192,187,192,53,92,121,231,34,254,92,112,61,11,123,202,110,139,149,88,43,162,61,198,33,207,71,220,199,184,244,59,124,184,11,233,138,72,255,57,46,125,25,254,123,224,255,103,46,236,126,184,167,193,67,96,24,241,251,192,143,224,126,18,92,9,247,85,224,89,184,255,5,188,19,238,127,135,252,29,232,196, + 218,241,189,240,191,31,124,16,168,240,247,130,245,171,22,202,222,4,247,203,193,40,226,255,4,242,207,193,235,193,5,171,156,53,168,104,243,45,72,119,27,120,3,226,222,8,38,225,254,36,216,239,146,118,7,248,60,226,222,142,184,119,179,248,175,192,255,53,240,62,248,63,0,238,91,235,172,115,255,150,197,255,29,228,39,61,202,218,1,166,145,254,243,66,252,207,209,7,31,67, + 251,127,5,225,223,4,223,2,15,114,105,30,134,251,81,240,19,240,16,242,255,0,252,15,184,31,135,252,41,248,95,112,255,2,242,255,248,232,221,193,248,127,72,147,193,154,121,37,56,9,108,0,167,102,23,226,183,194,253,10,240,106,240,58,144,7,5,46,126,135,15,37,143,116,187,17,190,25,99,119,4,242,87,176,243,87,109,228,205,130,77,143,99,78,121,156,241,151,62,246, + 252,181,100,157,119,252,30,243,91,180,195,111,99,224,69,38,187,214,121,167,177,159,99,127,235,18,254,95,79,128,126,200,193,182,92,11,172,21,252,31,106,177,46,69,220,139,138,17,185,9,186,111,5,119,112,54,124,2,238,79,131,83,96,219,102,240,247,112,255,19,56,156,80,155,255,27,230,185,187,236,179,183,217,197,225,103,65,247,89,160,129,240,175,67,62,194,197,63,1,247, + 217,8,59,251,15,148,159,181,161,254,175,109,145,127,97,253,244,239,66,127,238,136,153,109,208,181,221,135,223,49,253,203,176,199,178,10,104,8,211,36,89,179,58,126,123,227,102,8,118,14,49,54,194,222,83,193,214,54,218,93,229,206,239,87,5,254,120,181,35,255,140,201,49,216,248,81,112,231,58,199,127,46,194,95,128,124,1,156,239,98,115,145,229,171,130,50,220,195,46,105, + 254,175,189,247,216,139,53,237,106,103,223,244,109,144,239,2,215,128,223,217,123,189,171,177,134,5,227,128,16,191,162,119,33,207,139,108,175,117,37,248,45,220,157,189,78,158,9,164,237,131,251,100,112,6,120,5,88,15,54,129,173,224,85,224,19,72,115,63,234,145,217,24,141,79,163,140,207,174,115,228,151,109,247,106,236,157,175,78,127,92,201,242,207,176,245,91,62,246,94,134, + 58,61,136,248,135,67,214,233,114,228,155,123,9,180,195,85,176,243,42,23,126,202,108,127,13,198,202,235,193,47,218,80,151,95,65,199,89,88,75,255,134,211,165,96,95,161,43,231,157,103,21,226,214,130,83,192,153,224,85,224,53,44,253,185,62,249,118,48,102,236,107,55,183,240,55,44,5,184,75,160,12,246,130,179,237,119,12,27,157,191,91,234,101,82,207,57,239,131,222,2, + 249,159,193,187,192,53,224,122,22,126,47,199,140,240,55,50,47,67,25,167,131,87,128,18,23,254,106,248,95,11,182,129,129,141,11,225,151,192,189,7,236,3,186,173,127,163,19,254,14,200,119,51,247,12,184,6,238,26,243,223,0,121,16,182,140,231,156,185,167,201,199,56,255,93,112,127,14,220,195,194,190,206,228,253,144,223,229,234,97,162,172,143,176,114,191,207,218,233,78,248, + 31,97,238,42,87,246,19,224,103,224,151,224,95,89,25,85,97,78,253,15,166,167,210,227,188,115,225,219,234,222,54,211,179,38,253,115,123,73,177,134,181,237,57,39,128,45,231,36,204,198,53,254,237,144,180,254,115,78,96,54,167,92,255,179,82,190,198,239,61,1,56,39,166,107,249,222,151,32,61,109,28,127,103,191,132,219,233,222,152,218,250,181,224,18,172,89,46,97,236,16, + 214,59,27,216,119,28,54,184,176,109,205,241,233,95,106,236,245,56,251,179,247,15,128,129,53,233,239,233,164,77,41,211,113,140,109,249,46,9,50,202,182,90,143,178,237,112,183,67,30,110,169,124,93,129,236,175,169,74,165,214,161,84,14,99,189,171,144,178,47,223,9,63,128,187,130,176,97,60,222,236,169,57,113,85,59,78,233,84,118,43,153,121,89,57,220,161,12,29,238,148, + 162,98,167,87,58,149,170,93,142,226,48,175,87,33,156,23,107,15,118,93,42,168,195,30,212,103,4,122,119,129,17,48,133,184,169,54,48,12,253,85,244,229,158,60,36,244,110,67,251,255,161,255,219,230,54,46,15,199,55,190,131,216,175,216,99,16,207,174,24,243,251,48,54,246,49,116,244,79,165,214,173,140,0,29,110,235,80,183,114,217,97,167,223,182,163,15,117,54,118,116, + 228,171,34,125,25,233,202,243,233,51,74,129,229,173,176,252,101,96,33,255,94,228,47,51,127,217,142,87,236,179,146,201,141,247,42,116,212,125,226,167,18,166,10,253,83,1,105,234,191,231,114,42,166,52,245,19,88,214,125,234,53,229,147,166,46,184,235,46,121,234,33,242,76,5,164,159,10,136,155,146,212,85,119,73,51,5,44,23,123,44,129,58,120,144,203,255,160,135,123,42, + 130,156,138,177,127,167,124,108,171,199,168,167,126,130,200,41,15,234,62,253,94,119,105,147,122,68,125,83,47,17,172,54,235,155,150,96,0,247,223,129,0,70,4,6,108,106,221,32,195,100,247,98,191,194,165,113,201,63,210,164,230,220,243,143,161,176,176,121,153,113,181,119,196,142,83,22,179,41,227,208,212,53,32,196,143,180,136,137,53,143,185,132,50,223,6,74,135,35,243,33, + 176,243,40,30,132,41,39,239,80,92,65,74,1,20,153,28,176,251,105,197,130,159,176,70,36,48,192,252,5,70,5,207,82,69,46,158,24,35,43,156,126,182,227,42,76,22,25,132,248,34,39,139,172,172,98,12,16,87,102,209,135,66,130,20,5,119,209,69,103,81,82,214,151,97,190,1,245,144,178,224,161,175,234,210,222,5,183,246,95,254,210,166,144,50,197,19,68,234,160,116, + 97,215,60,38,187,214,75,112,95,204,40,158,32,118,22,19,144,197,22,243,22,78,16,251,11,17,36,97,30,92,217,73,109,129,216,156,223,3,119,191,7,221,39,0,253,157,157,202,250,237,153,99,246,110,70,216,102,112,234,246,206,121,127,110,103,215,75,142,245,59,51,243,178,127,21,29,11,235,231,221,55,118,204,75,18,238,207,36,160,238,164,182,65,1,186,251,97,127,255,170, + 227,211,249,113,93,7,29,199,118,196,220,9,121,39,99,226,218,206,121,110,7,21,172,91,38,58,58,149,73,132,79,2,115,27,230,70,73,42,216,47,190,14,146,160,119,226,90,236,219,193,111,160,12,35,69,6,20,249,180,205,251,128,41,195,54,57,198,238,235,148,98,194,238,131,251,24,130,77,70,202,109,104,156,64,76,96,92,25,1,227,114,187,226,63,118,15,32,238,0,23, + 63,233,146,214,96,250,198,208,31,99,18,24,156,141,147,12,195,182,119,115,107,216,215,208,117,144,6,48,117,216,195,220,70,155,49,161,219,140,9,98,115,146,193,235,232,120,105,163,109,57,49,41,93,129,245,172,36,26,210,83,136,123,10,113,28,249,97,167,20,214,6,236,69,165,76,99,79,87,32,211,72,55,13,142,130,185,121,58,149,185,143,116,204,135,17,171,115,3,233,158, + 216,211,57,79,227,141,200,199,49,125,10,244,248,248,27,12,98,101,205,33,126,174,69,8,249,181,62,244,119,194,148,202,24,39,12,98,118,87,16,78,226,181,188,217,31,59,61,165,140,145,192,28,64,33,202,165,16,182,154,146,107,12,147,191,111,17,230,236,243,186,148,81,6,217,182,17,116,251,64,205,182,33,119,127,88,40,32,47,177,120,226,251,133,220,109,178,235,98,192,63, + 202,213,201,20,32,150,118,148,171,51,113,126,147,149,65,1,237,61,202,229,37,151,54,49,56,251,76,65,247,216,183,176,14,144,192,96,249,111,255,27,172,183,193,56,220,227,34,127,131,245,183,157,230,60,39,253,4,211,71,205,249,231,34,204,33,96,6,16,252,22,246,165,44,23,72,168,139,37,132,91,30,249,172,0,40,32,47,9,186,44,15,55,177,186,16,228,12,171,207,12, + 171,91,131,133,19,71,67,8,111,120,164,109,184,228,37,151,118,176,4,187,200,239,62,247,32,238,99,18,52,152,61,22,87,126,3,254,185,128,242,137,191,238,47,194,216,2,163,128,236,113,183,2,99,38,0,18,210,145,100,62,67,162,44,195,39,158,60,210,18,87,23,3,254,81,86,159,81,86,55,83,128,92,234,61,202,165,167,230,245,184,34,218,125,196,206,111,0,83,40,119, + 12,125,55,38,193,4,242,222,126,145,179,47,226,167,195,244,169,103,238,178,46,37,203,32,248,213,151,225,249,222,7,98,229,170,30,126,53,36,20,144,151,4,93,170,143,77,57,212,65,133,63,11,153,99,117,202,9,16,75,155,229,234,76,156,63,199,133,145,11,57,174,108,18,226,84,15,59,115,66,185,189,63,238,148,98,51,203,223,231,1,73,142,179,198,229,152,3,78,103,115, + 11,164,229,2,241,115,210,233,139,195,101,210,18,115,147,16,70,46,233,200,67,15,185,132,91,18,144,80,223,25,212,119,6,52,152,156,1,228,18,63,195,194,103,56,127,131,75,107,185,148,77,92,25,36,217,254,71,158,194,124,44,193,44,244,205,50,142,122,240,196,229,157,243,146,220,230,234,55,97,142,226,48,129,113,6,230,24,31,200,158,35,206,240,246,135,133,34,230,55,88, + 25,212,156,191,206,192,252,197,234,98,10,245,27,5,196,234,109,50,72,240,155,44,140,60,48,185,52,6,116,25,12,114,155,75,207,112,226,248,60,100,207,215,63,197,124,44,1,249,216,49,241,137,140,50,142,178,199,61,152,4,86,14,247,239,18,198,41,99,206,204,204,135,89,62,144,61,142,115,222,254,176,80,136,252,228,145,150,154,243,82,9,243,18,252,51,144,13,48,227,2, + 177,116,51,156,127,134,185,27,12,242,105,215,25,46,31,121,96,9,182,54,132,114,143,52,112,125,74,64,92,25,36,57,55,16,71,110,23,238,19,32,203,80,215,97,254,15,128,144,79,245,241,171,33,200,50,253,57,14,66,121,89,206,38,18,252,57,102,231,102,166,155,56,84,161,236,62,200,62,15,84,142,28,167,155,64,239,35,184,31,74,208,231,83,126,31,99,243,45,157,190, + 109,217,24,193,120,97,144,221,159,155,208,159,49,64,252,120,219,20,79,153,124,217,86,136,116,36,97,7,113,246,54,88,91,16,127,93,141,44,180,147,29,223,96,16,151,126,134,75,51,227,2,121,221,47,127,130,235,73,2,18,108,153,19,236,38,9,140,45,106,170,152,130,219,244,128,4,187,247,225,28,185,225,81,230,129,128,178,76,48,129,125,214,177,167,113,95,146,96,226,138, + 204,60,228,214,126,107,160,19,16,115,19,23,102,112,144,71,184,17,1,10,208,105,184,96,10,110,51,0,131,149,79,62,245,58,192,164,233,81,6,53,215,9,15,161,61,37,32,15,125,22,250,204,242,129,236,107,122,139,127,154,168,144,208,255,13,236,217,55,90,96,6,227,169,225,18,62,199,133,91,46,250,27,33,116,80,4,251,26,49,65,176,225,40,234,116,212,102,11,214,240, + 46,52,144,238,168,71,28,217,249,183,56,238,185,187,50,14,87,48,137,48,29,41,116,142,185,45,29,243,144,203,181,90,239,193,217,51,23,136,197,145,71,30,98,238,41,184,167,34,96,72,64,246,184,239,57,62,204,45,220,16,210,24,156,52,66,230,55,132,114,140,22,243,27,39,120,253,140,16,118,25,109,172,159,17,51,196,116,154,3,120,86,99,76,32,124,98,0,247,50,91, + 246,184,51,30,51,196,223,43,123,162,213,105,82,18,242,88,235,20,113,230,180,152,0,196,202,47,216,103,90,151,29,79,51,126,255,222,140,162,109,196,123,188,54,48,188,81,81,246,236,21,214,42,120,79,105,72,82,201,103,142,113,51,252,55,51,140,0,200,103,173,169,98,190,86,67,66,46,249,114,184,95,228,24,20,102,173,187,18,54,198,128,89,196,245,196,168,176,54,34,9, + 253,106,23,236,143,129,220,5,168,59,160,144,107,125,11,239,125,173,144,144,144,143,236,53,133,142,123,59,160,144,250,179,57,213,149,156,224,206,73,210,95,202,48,58,148,158,166,63,135,115,121,144,167,150,240,156,203,210,17,211,223,139,61,138,94,9,200,195,254,62,148,221,39,1,121,228,159,192,51,246,4,168,216,127,203,93,235,80,38,118,57,99,231,214,91,48,39,219,113,183, + 32,204,102,29,198,24,158,255,77,198,117,235,156,245,204,24,158,241,199,36,32,175,241,191,14,227,55,97,200,239,250,91,23,156,223,174,175,1,73,110,249,187,144,38,1,72,114,252,106,189,152,91,83,130,160,191,52,132,115,31,140,65,129,146,75,56,9,246,15,114,105,7,61,40,113,144,144,95,103,118,16,163,50,134,113,12,134,142,116,74,161,157,174,198,2,53,251,227,116,239, + 56,25,12,164,55,66,64,17,243,27,146,229,18,99,12,239,68,198,36,24,23,202,152,132,127,18,88,25,204,217,9,209,56,31,247,0,48,195,104,0,98,250,231,16,63,7,52,60,179,107,49,51,8,74,156,44,121,64,246,245,178,102,113,250,160,60,37,46,221,160,64,73,18,98,109,80,186,164,75,25,194,94,198,144,4,228,209,255,67,17,243,87,96,79,197,131,1,159,56,98, + 249,205,48,103,176,243,11,220,196,185,41,228,26,129,98,100,116,59,222,145,1,147,201,81,96,116,98,206,247,128,196,235,239,126,92,95,18,76,108,199,253,219,166,19,247,240,166,123,187,195,36,194,38,37,48,153,157,166,15,147,18,229,16,127,191,34,92,131,46,148,112,126,105,16,148,24,131,28,37,33,174,196,197,81,64,123,87,80,118,133,67,197,253,66,229,200,226,158,146,245, + 33,23,64,86,72,71,156,238,254,33,172,253,4,122,113,207,233,149,128,184,114,84,206,222,177,31,161,127,37,48,240,188,101,184,80,224,254,86,171,224,1,249,180,103,29,207,178,245,54,65,46,58,251,177,222,239,247,129,2,198,131,201,205,3,21,132,152,156,223,148,100,48,32,94,67,185,26,135,233,18,166,185,196,13,10,101,80,128,253,102,130,144,135,174,2,206,46,21,124,40, + 6,80,240,128,88,253,138,30,249,136,197,23,2,244,19,43,131,60,100,33,33,251,11,18,118,145,132,253,23,5,80,240,40,187,224,97,255,69,96,132,197,141,112,20,66,230,31,113,201,87,112,169,87,209,165,223,70,124,242,23,36,218,127,132,131,92,218,145,130,236,255,112,230,88,222,237,53,231,239,127,120,200,14,207,35,92,130,178,226,124,243,193,206,67,160,92,235,158,15,43, + 11,84,57,134,145,110,79,94,81,166,15,177,180,118,158,26,135,100,126,130,156,150,252,134,198,52,7,241,115,72,30,247,150,8,152,104,3,179,133,181,215,77,28,166,139,223,12,1,69,88,127,61,140,246,120,152,81,103,223,36,121,88,160,140,62,209,145,90,119,153,131,171,136,171,10,232,46,84,89,223,233,62,80,27,214,155,122,128,13,122,194,246,232,33,244,235,33,108,212,99, + 46,87,119,209,163,75,164,91,169,182,142,173,163,27,178,59,37,200,94,91,45,91,252,253,11,155,163,30,212,133,180,141,29,120,198,102,212,89,24,185,140,129,185,15,226,253,231,14,188,27,101,204,34,221,28,11,155,194,154,110,202,5,66,190,41,143,247,38,83,92,184,101,159,193,59,163,117,220,202,39,198,52,226,231,222,36,183,143,238,70,195,62,235,217,19,237,253,146,204,251, + 52,242,193,8,200,111,36,28,191,155,189,139,24,134,187,194,189,191,169,184,161,176,119,60,77,153,119,190,211,104,83,1,212,202,252,115,16,249,219,0,37,164,139,78,144,250,82,92,247,131,26,202,11,1,185,148,161,173,234,81,180,27,241,204,20,36,111,76,152,48,122,87,5,196,1,98,245,51,217,58,103,36,223,49,207,192,78,7,59,45,185,180,135,122,61,246,6,218,4,217, + 250,14,194,29,3,196,141,135,178,128,30,97,124,144,223,124,184,186,39,18,155,97,247,230,148,32,123,236,31,196,120,105,51,100,183,115,205,65,189,59,60,182,221,196,216,12,255,230,152,32,137,254,86,79,130,13,17,24,123,6,251,105,18,24,103,226,158,119,166,156,77,97,176,176,127,106,185,240,176,36,13,236,209,54,192,28,220,115,216,251,157,19,32,73,59,234,18,223,200,164, + 152,235,78,130,110,139,123,166,157,98,126,75,64,195,30,160,198,24,196,121,128,65,80,98,12,2,29,225,58,71,73,56,51,80,198,255,101,23,116,198,190,154,243,109,105,62,15,241,243,75,63,198,65,140,76,76,96,207,126,158,142,121,40,160,173,6,247,161,158,28,37,6,5,228,171,236,195,122,11,12,61,129,247,55,18,144,215,245,118,10,174,27,15,114,111,196,30,185,36,89, + 6,133,24,39,253,182,158,226,178,182,67,156,13,217,139,187,148,254,213,216,139,118,227,98,188,3,104,178,122,113,156,157,119,0,255,247,126,31,251,255,18,144,88,247,213,78,57,198,122,188,191,21,48,193,1,151,112,195,133,177,71,49,159,73,96,184,228,37,187,255,151,161,15,2,200,225,121,49,231,66,150,145,243,240,147,204,124,191,108,177,30,25,91,178,92,217,189,223,65,251, + 74,64,205,118,95,134,118,199,115,108,63,67,59,3,239,92,129,6,40,161,249,144,24,26,211,51,0,42,12,117,61,234,37,73,118,55,218,149,145,101,244,239,70,61,128,138,248,30,70,150,165,33,73,187,140,128,231,85,35,98,60,181,160,219,144,124,94,157,138,225,27,199,133,21,139,253,246,253,106,4,165,143,184,220,55,10,24,63,133,8,168,66,127,102,133,254,204,9,125,155, + 21,250,178,23,215,114,175,4,228,117,189,173,196,28,200,65,33,199,176,122,16,249,98,130,196,178,235,8,111,51,36,94,163,7,163,173,185,211,88,227,83,138,76,249,92,87,20,114,108,117,99,205,219,45,64,110,99,112,167,243,221,71,217,239,48,158,182,74,46,157,219,247,27,85,159,112,226,226,201,195,173,6,96,68,252,86,209,29,12,138,225,254,100,196,244,237,36,138,80,22, + 37,124,15,54,66,218,115,251,181,206,183,51,197,189,30,179,69,42,74,39,246,73,237,189,85,251,55,158,216,239,52,225,28,111,5,12,179,247,134,149,195,222,237,96,199,15,11,178,130,235,176,2,134,5,127,133,177,139,163,210,140,207,59,229,85,176,95,85,177,169,57,236,230,202,25,246,208,83,1,85,176,155,201,42,131,236,242,106,220,111,90,177,48,10,104,127,138,105,236,25, + 17,199,220,118,188,107,166,249,58,116,28,171,231,62,174,46,21,48,204,181,69,213,14,203,99,207,156,201,10,228,144,36,21,214,238,85,86,78,117,190,252,142,216,246,234,212,22,160,69,247,121,242,77,163,198,24,79,156,222,125,236,119,161,170,104,143,17,182,103,184,15,254,125,246,251,108,197,121,199,215,124,87,91,101,232,12,66,60,73,176,157,59,31,160,43,206,251,223,102,156, + 133,51,91,86,8,168,133,60,86,74,101,19,87,30,69,44,155,90,108,47,43,166,252,86,4,157,196,81,193,59,171,10,131,132,184,50,254,47,39,12,113,250,116,110,44,235,94,172,144,163,204,208,57,72,86,135,178,24,59,31,53,109,92,209,158,115,16,94,232,130,109,122,27,48,185,51,44,26,246,152,181,54,67,92,253,173,46,140,229,136,80,132,246,55,217,153,170,114,155,32, + 65,255,108,150,22,65,18,54,91,72,103,53,81,163,243,176,100,26,114,177,69,195,30,177,150,0,165,10,246,141,5,200,77,255,153,72,159,2,100,207,181,251,177,223,182,21,126,14,18,237,219,186,56,94,164,114,101,70,185,20,242,82,1,146,28,191,198,203,176,230,75,1,98,250,199,126,140,253,88,9,200,195,254,34,202,42,198,8,9,229,23,16,86,104,35,36,232,87,177,175, + 167,250,64,1,253,171,98,61,175,70,128,66,204,133,253,49,60,175,26,88,151,24,9,65,41,234,167,152,234,79,17,219,151,130,198,203,114,244,187,11,196,197,147,79,58,53,38,136,211,65,130,125,196,185,45,140,57,43,1,8,229,207,109,195,89,63,184,103,125,208,54,164,135,109,35,249,80,70,154,114,0,20,113,188,88,43,209,94,49,65,92,121,228,162,107,22,225,179,2,218, + 201,201,80,246,128,56,123,42,195,25,197,192,122,212,104,3,228,210,30,19,118,220,218,100,17,117,142,35,108,156,139,31,143,8,9,229,142,135,196,194,59,227,6,222,89,55,24,132,178,26,156,155,4,26,92,218,6,176,144,223,226,32,123,12,174,246,246,39,5,185,216,71,46,182,207,34,237,108,0,228,115,189,206,225,221,253,156,31,171,253,243,27,125,232,119,1,179,140,189,92, + 238,183,19,168,217,167,125,232,35,129,137,50,206,130,184,49,43,55,23,105,120,7,162,37,0,241,215,245,118,236,127,120,96,97,14,176,18,128,154,115,220,201,232,67,31,44,60,219,88,49,65,110,115,108,63,244,248,160,194,6,53,6,200,167,143,251,17,223,207,65,33,239,73,125,200,211,231,1,69,189,223,109,68,219,5,64,17,117,144,15,7,241,204,126,48,38,168,5,253,55, + 32,223,13,49,65,62,122,198,17,63,238,130,134,51,82,26,71,9,103,172,74,28,20,96,255,208,227,56,255,37,1,121,217,213,1,59,82,130,236,241,183,21,99,76,2,66,218,198,149,184,143,128,71,177,71,64,205,241,187,117,33,190,21,202,167,97,29,228,129,6,40,32,191,138,52,106,12,228,170,56,7,34,73,31,210,247,113,244,187,64,49,93,159,6,206,180,24,17,25,247, + 129,98,158,79,52,236,33,104,9,65,18,250,75,231,227,186,245,160,114,62,238,185,54,25,236,115,9,144,56,46,51,24,131,17,24,122,0,215,189,4,20,161,173,75,168,83,255,37,56,159,6,40,68,190,78,238,236,136,129,177,106,68,132,124,116,141,61,137,253,54,9,200,107,60,117,161,239,61,40,93,128,126,21,24,116,9,43,9,144,61,111,127,27,237,47,65,229,2,140,23, + 14,13,122,7,153,30,13,110,10,104,107,11,207,84,86,76,52,46,197,185,117,200,185,155,177,110,244,120,134,35,23,102,55,97,173,21,35,20,102,62,200,161,175,98,128,90,44,107,176,132,190,98,148,56,6,185,176,65,46,188,82,66,63,219,228,48,47,112,238,18,251,214,190,134,115,133,154,7,196,226,201,37,29,5,228,213,2,160,16,249,43,56,215,72,94,247,147,77,184,102, + 35,48,58,210,117,12,51,0,114,155,15,126,130,235,93,2,242,176,63,119,17,238,193,32,203,32,132,169,120,103,170,250,64,44,175,234,225,87,67,66,1,121,73,208,165,122,164,49,243,78,93,108,127,150,213,39,199,213,45,203,213,145,64,214,195,159,229,194,84,159,247,199,89,46,29,113,168,46,246,101,133,114,115,204,182,156,11,228,210,63,57,143,120,10,96,116,15,198,22,48, + 57,70,153,52,176,191,104,112,76,236,193,115,62,207,6,82,198,126,136,241,35,193,4,203,67,208,57,129,124,166,80,62,133,176,153,98,36,183,31,237,14,114,12,21,239,246,84,208,207,36,5,228,239,125,6,231,147,37,32,175,249,122,19,230,144,22,25,196,53,63,200,40,9,16,43,127,144,75,51,200,197,15,114,238,202,29,152,115,71,24,119,224,28,151,205,38,204,107,77,70, + 184,248,144,104,200,175,113,148,4,251,74,187,160,223,254,230,231,46,128,111,92,106,30,223,185,36,15,134,30,193,186,65,2,242,200,63,97,127,103,244,150,112,120,149,69,45,160,158,142,113,38,73,238,114,140,85,144,99,50,203,220,57,23,178,76,170,62,191,87,69,246,248,125,10,227,83,2,74,232,250,211,58,212,182,65,205,49,215,161,30,99,144,81,18,24,116,9,43,113,101, + 80,115,252,221,135,241,37,65,25,121,203,160,130,119,82,21,134,14,191,218,141,190,13,65,182,128,190,101,228,36,200,114,238,126,228,39,193,254,35,152,155,142,72,48,139,185,112,54,0,10,217,247,22,242,88,246,59,192,229,238,28,75,183,124,177,59,40,143,229,226,182,90,100,230,194,46,87,8,101,207,112,110,2,13,184,27,44,172,1,44,150,223,98,178,1,230,46,196,30,61, + 131,36,218,232,200,119,209,254,18,144,87,27,175,71,61,124,32,201,116,86,4,102,118,163,77,24,13,1,106,182,221,110,199,207,167,157,97,97,22,202,152,187,21,207,96,172,44,74,104,46,34,23,42,47,199,253,239,205,184,94,121,238,6,8,47,189,57,216,22,11,207,235,13,236,97,53,92,152,97,52,56,44,164,183,56,230,238,196,88,169,58,4,233,162,4,80,237,119,6,253, + 201,226,167,223,34,180,67,0,36,153,206,138,200,204,121,232,35,31,200,30,199,231,185,199,53,243,90,40,103,238,60,244,167,0,73,246,199,145,111,225,122,151,96,22,122,102,91,132,56,125,115,227,176,111,188,67,177,240,238,207,138,1,106,94,239,101,180,71,11,144,61,239,150,253,211,240,241,51,66,94,10,192,192,57,71,195,3,226,226,41,32,173,17,1,83,195,243,151,11,196, + 217,105,50,191,201,197,153,30,249,76,96,176,114,41,160,254,99,22,158,215,36,32,143,252,26,244,104,46,16,23,71,62,233,180,152,24,68,93,7,5,8,122,75,144,37,230,30,100,225,37,78,106,30,231,92,137,49,132,186,15,73,64,73,205,199,125,152,51,99,34,135,107,34,7,178,140,126,132,245,3,138,211,222,147,162,149,247,196,199,157,223,182,141,10,181,160,123,238,114,156, + 15,194,187,54,163,13,144,135,13,55,35,238,230,22,33,228,159,192,187,195,9,1,10,209,6,134,164,157,148,16,22,158,89,172,22,33,228,191,191,187,195,7,69,185,191,16,80,127,252,173,173,225,1,181,163,254,157,168,75,194,144,159,254,117,72,147,18,4,253,71,176,119,115,68,2,18,175,221,93,88,183,0,138,216,254,170,253,205,118,138,78,179,188,28,214,128,57,129,254,16, + 235,191,105,140,187,105,142,185,157,25,229,104,136,177,120,228,123,104,47,9,8,105,103,81,238,172,7,100,143,141,85,232,167,152,33,183,49,184,42,217,242,195,208,135,51,139,125,9,209,255,6,188,91,182,89,11,55,32,183,249,232,100,204,61,146,144,144,215,28,198,58,16,225,163,144,163,45,66,62,109,99,34,222,228,56,0,93,7,66,210,243,166,14,165,103,175,243,183,196,3, + 192,204,5,127,115,138,103,176,136,181,28,160,86,175,247,79,224,122,77,16,10,208,111,224,221,167,145,0,20,99,253,41,226,53,68,62,104,73,127,51,243,70,111,252,236,34,89,251,87,57,223,210,228,191,167,73,33,48,132,239,61,82,80,127,221,134,62,105,35,36,218,219,141,241,37,9,185,213,183,91,62,191,95,57,20,178,60,98,233,39,225,54,177,55,110,130,137,66,230,184, + 239,237,83,130,99,157,92,208,241,191,158,0,20,131,126,74,216,70,61,164,173,212,74,251,174,112,254,118,188,132,247,228,37,134,14,63,181,169,127,203,248,191,28,35,116,130,140,47,93,210,150,114,204,245,47,135,108,147,34,254,47,182,64,193,7,66,185,133,128,52,5,70,81,162,188,66,140,20,153,125,69,206,77,204,79,66,155,144,71,27,145,68,187,21,60,218,132,2,218,134, + 66,180,93,161,197,190,43,250,216,89,244,169,35,73,142,25,10,72,67,33,235,88,136,216,223,69,159,250,20,125,236,39,201,235,132,98,168,15,69,188,30,139,45,244,119,53,160,221,10,156,93,67,135,177,143,27,3,147,167,210,34,204,125,93,199,220,19,251,176,31,199,115,106,180,251,144,93,182,41,64,30,225,166,36,20,241,126,151,195,158,122,206,3,10,136,207,73,66,33,117, + 145,71,90,74,224,126,175,102,177,110,14,9,241,237,55,8,219,60,232,65,218,30,15,136,229,239,157,193,249,29,9,200,197,238,205,45,254,141,17,241,229,108,69,157,66,144,187,18,117,227,232,253,25,236,147,128,92,116,159,122,101,167,162,97,31,69,19,160,230,90,100,45,214,11,1,148,222,128,53,162,7,36,81,255,41,252,63,197,209,192,51,69,195,131,105,110,254,33,198,17, + 204,33,71,36,32,33,223,28,131,98,160,140,255,203,45,162,215,112,246,137,243,239,19,252,101,151,58,83,130,246,148,99,66,143,152,159,88,93,140,14,60,143,114,144,75,152,145,32,196,233,35,230,38,206,54,10,97,15,249,212,135,132,178,73,192,112,137,51,60,242,27,45,216,79,18,229,145,75,126,35,68,223,144,100,30,138,169,109,141,22,251,155,92,218,217,8,208,67,9,234, + 167,16,220,137,50,238,140,80,142,206,190,193,168,75,66,2,122,204,80,72,244,24,116,233,17,203,208,219,0,73,183,71,119,11,80,236,223,141,214,149,229,237,225,198,214,199,142,211,94,212,86,168,5,253,36,97,63,197,92,127,106,19,122,30,125,24,166,13,243,225,210,7,234,175,69,235,195,200,249,149,120,9,221,254,74,2,248,212,151,34,182,31,197,140,30,178,110,20,115,251, + 81,76,246,81,139,249,169,77,227,131,132,254,166,86,251,171,150,32,108,110,161,8,101,80,179,156,60,67,244,231,37,137,88,23,242,169,7,121,196,147,11,186,132,14,242,65,143,1,106,177,124,106,1,61,106,125,149,24,175,25,123,125,174,132,207,19,133,33,201,125,92,242,42,163,22,255,28,45,133,226,232,79,227,247,24,181,132,127,31,134,66,180,131,22,241,247,99,180,151,248, + 111,215,104,105,219,169,56,191,63,224,121,125,40,201,98,255,77,191,25,1,138,168,63,147,239,78,5,98,250,235,61,148,42,197,101,233,34,254,6,104,61,38,57,21,64,157,251,173,181,130,15,197,0,10,17,243,235,236,62,88,180,101,205,249,109,208,249,61,14,133,133,177,240,2,243,23,25,5,70,145,15,171,113,249,240,251,36,58,123,103,90,116,65,231,169,177,125,21,46,174, + 200,108,43,50,127,221,110,179,67,221,139,242,23,184,242,234,107,28,105,135,105,2,5,78,87,65,200,87,117,161,200,165,43,112,249,171,92,57,83,107,28,121,220,222,124,173,219,65,241,136,19,220,43,213,116,177,219,109,202,229,55,113,167,2,198,120,221,227,55,116,167,2,174,129,105,70,189,201,50,132,131,122,74,178,184,28,253,11,138,33,165,6,74,23,118,41,23,115,84,88, + 88,73,130,10,210,14,180,160,183,24,81,14,48,74,204,142,17,140,193,17,197,233,11,190,239,234,33,231,185,122,76,114,58,196,220,89,23,198,227,148,132,28,96,245,29,113,169,179,21,115,253,234,30,215,73,221,231,250,169,7,200,10,214,76,245,67,237,193,254,109,175,10,244,237,201,43,243,191,61,181,11,20,113,222,181,152,34,133,132,57,24,227,247,85,15,182,64,253,204,116, + 89,137,51,11,43,83,164,136,235,178,216,6,10,9,81,124,137,219,95,144,168,79,181,77,186,171,41,183,115,245,247,164,94,213,4,251,168,126,130,48,149,22,61,237,163,2,125,211,98,248,26,119,92,159,7,151,47,172,191,227,98,0,229,14,128,105,143,240,1,142,41,15,91,167,124,24,88,238,31,159,36,211,46,20,2,158,183,47,10,160,16,49,255,84,27,199,219,148,27,107, + 218,135,189,70,159,134,156,230,40,224,153,173,144,34,35,88,3,79,97,109,60,213,6,134,177,254,174,178,245,119,181,230,248,163,174,111,45,156,17,183,34,112,67,140,223,255,191,161,5,166,176,70,157,74,145,110,172,81,187,83,196,58,37,25,26,111,196,25,98,142,105,151,176,6,48,48,31,27,192,196,222,129,105,239,99,228,157,223,138,29,251,110,167,20,19,200,123,128,149,97, + 180,194,186,5,14,112,110,163,93,108,76,150,3,30,76,236,205,204,51,45,204,135,211,109,96,128,187,135,79,167,160,127,154,179,227,178,195,138,178,23,12,99,46,220,131,121,113,216,67,86,216,184,172,52,169,117,204,239,253,84,154,126,69,112,11,236,62,156,153,167,194,151,161,144,178,127,222,173,28,251,125,115,29,12,20,59,20,13,242,82,188,35,27,14,176,107,152,73,157,229, + 29,14,72,183,59,223,113,204,189,135,229,187,20,122,46,13,192,20,222,83,101,139,14,246,111,179,15,136,228,187,142,217,115,41,87,198,128,93,175,188,243,219,239,205,120,227,38,14,251,111,204,111,114,145,110,172,14,136,115,137,175,64,247,128,210,163,28,184,184,99,30,3,97,234,221,241,211,243,230,14,249,244,39,37,75,143,7,253,151,102,230,49,112,15,50,66,112,0,76,236, + 199,220,241,201,142,121,105,132,204,111,8,140,224,26,24,73,136,1,9,140,245,106,36,14,248,196,77,114,24,96,20,152,76,26,140,61,236,218,31,62,54,207,40,243,227,212,224,199,241,106,111,236,241,108,120,93,7,55,181,143,129,230,247,81,46,116,238,227,54,163,192,136,114,111,94,222,58,77,253,38,179,97,148,249,71,57,251,76,15,70,5,76,206,109,216,101,231,133,247,246, + 92,94,35,192,46,21,247,124,181,77,228,118,117,205,163,194,157,133,204,2,107,11,41,71,239,202,28,99,206,102,11,57,242,46,219,221,177,224,190,162,233,119,1,225,71,231,243,117,204,203,163,140,198,21,88,215,129,163,92,216,81,14,35,226,245,102,196,200,40,119,77,154,28,163,46,97,166,75,220,168,224,55,192,216,163,88,151,74,48,177,27,115,168,205,122,204,167,28,134,164, + 173,134,128,41,196,141,114,24,46,97,163,30,105,12,151,58,27,45,96,186,216,60,234,129,201,217,55,38,217,126,163,66,254,73,134,90,92,150,46,7,83,166,158,46,6,246,116,140,20,185,245,2,172,73,240,187,76,198,9,196,36,152,136,171,188,83,210,69,205,225,26,75,145,28,35,203,185,115,46,100,25,70,26,207,244,235,22,99,226,190,107,2,67,8,55,146,224,116,212,57, + 69,180,53,233,98,110,71,91,115,140,2,163,19,237,210,38,76,166,211,116,97,148,147,163,140,156,238,144,5,234,102,92,95,41,144,101,54,228,56,178,140,28,231,207,113,168,44,111,239,209,78,41,84,15,221,26,165,139,218,155,46,214,25,233,178,232,155,131,171,122,218,254,141,195,221,246,179,238,106,143,125,31,81,222,148,48,237,210,115,211,2,42,246,197,212,52,185,62,93,194, + 252,189,128,150,0,106,2,251,127,106,8,140,136,251,103,70,68,172,54,124,207,219,242,65,179,239,1,155,113,126,20,247,20,13,82,99,148,4,191,230,17,166,69,196,232,79,151,193,125,93,243,104,120,23,170,165,192,32,211,63,40,137,41,177,127,102,74,96,120,236,143,77,112,24,182,252,16,158,231,184,61,189,161,239,119,74,161,173,70,253,18,164,116,49,206,59,251,80,177,223, + 235,92,156,81,84,251,153,233,148,214,201,161,172,28,35,11,84,232,86,83,34,199,108,200,114,54,229,56,219,178,46,126,21,103,28,212,20,209,206,192,115,65,138,148,56,89,226,252,154,139,95,115,137,83,177,39,168,166,73,29,125,184,18,123,59,237,162,222,94,125,113,124,67,200,0,119,180,136,145,18,123,216,59,39,203,126,6,233,77,17,236,105,89,105,146,77,23,109,107,186, + 68,57,187,101,197,128,138,61,91,53,77,58,210,197,192,53,96,164,136,245,178,228,56,202,152,245,193,178,207,160,173,77,15,213,30,131,167,182,151,62,70,191,237,223,208,126,250,56,140,62,127,38,3,226,141,136,104,246,51,96,103,122,88,56,11,103,165,136,134,53,190,150,34,150,125,15,216,154,30,218,105,237,167,204,49,120,62,158,215,192,32,208,50,136,111,51,37,166,191,228, + 65,238,18,135,44,80,113,102,81,109,51,89,166,59,203,236,200,113,100,133,240,126,164,239,7,157,182,188,4,231,203,64,233,2,212,131,161,97,189,165,181,153,220,8,108,3,89,160,110,66,157,82,36,203,236,200,50,155,114,1,100,61,236,238,31,65,219,218,108,114,220,98,190,126,132,103,153,187,247,39,120,255,34,129,106,151,117,7,246,43,88,153,253,12,3,126,35,37,76,216, + 111,216,247,168,13,233,50,186,7,239,234,24,102,0,42,246,84,213,20,201,238,199,152,97,228,60,200,114,50,43,164,215,208,238,90,154,172,75,151,65,188,167,31,100,148,2,24,228,210,13,50,169,158,142,126,72,147,238,116,209,216,239,208,151,36,208,144,86,75,144,146,135,222,65,166,187,36,132,87,238,206,28,247,123,247,86,74,52,170,56,71,25,146,25,206,61,87,197,217,77, + 134,133,242,230,238,132,27,168,62,191,123,175,182,1,3,107,62,35,69,44,140,81,43,85,148,5,10,28,98,186,130,24,214,76,231,145,62,105,155,11,238,122,189,126,139,216,104,59,138,98,236,4,17,203,177,82,166,177,19,215,175,7,115,59,113,13,55,89,133,107,26,28,21,48,66,252,30,171,145,0,106,194,191,87,170,6,96,36,244,123,165,134,44,55,225,222,211,230,51,52, + 154,15,106,155,127,143,83,21,48,236,57,162,59,61,90,249,189,35,53,78,182,166,139,248,91,67,90,155,49,183,225,185,8,108,87,22,220,237,228,200,211,157,82,204,110,193,126,180,4,71,175,200,28,251,251,150,6,39,31,219,162,40,63,188,66,57,38,103,16,54,3,74,56,179,80,74,17,83,252,91,165,188,28,205,252,149,26,206,232,49,118,49,134,37,255,38,117,24,82,71, + 191,235,2,131,40,127,80,208,167,187,196,15,10,108,15,192,12,64,103,229,155,18,105,244,152,49,19,210,99,70,248,45,61,51,102,38,241,62,99,146,195,140,169,156,73,151,114,39,3,210,76,50,70,35,252,182,252,104,12,12,197,244,187,141,67,45,242,240,161,238,112,172,161,197,28,106,49,31,227,210,21,233,83,22,208,125,184,25,247,204,155,83,164,220,252,222,107,45,29,100, + 127,179,168,156,4,10,250,0,223,98,208,109,89,235,96,208,162,249,110,56,196,189,103,15,39,203,73,181,151,178,216,190,32,202,168,79,89,2,61,33,202,129,253,79,210,54,150,19,178,187,28,129,113,172,57,199,83,100,22,123,176,179,49,115,52,128,39,62,222,121,140,178,61,198,148,20,105,105,206,33,142,238,72,236,199,185,219,253,94,40,29,139,252,219,149,46,229,58,172,197, + 174,59,188,252,216,26,114,55,194,119,35,221,110,91,230,157,111,182,236,70,185,21,27,101,241,247,98,108,247,101,130,191,194,165,173,216,28,230,64,121,111,58,220,173,92,1,182,187,172,95,247,67,111,165,214,233,143,178,96,195,188,158,60,242,216,40,88,155,51,42,28,85,165,147,251,182,68,199,162,244,21,31,170,76,238,106,134,229,133,111,220,52,81,22,194,230,219,52,31,188, + 78,79,146,76,74,191,127,144,97,116,229,213,84,169,216,227,226,240,194,184,60,54,86,14,47,192,135,93,214,116,231,133,126,117,233,231,221,243,82,153,247,239,182,251,93,225,198,34,220,21,137,251,223,176,248,221,34,197,35,109,208,189,181,198,16,194,171,12,221,135,225,32,221,74,235,52,245,87,57,134,153,190,170,71,124,53,1,134,37,215,69,213,20,116,14,135,148,213,20,219, + 173,218,70,221,85,143,177,162,115,99,75,119,113,235,30,227,80,143,145,170,168,191,230,92,159,77,169,39,68,53,44,181,238,5,89,99,110,62,174,198,133,241,110,69,200,139,223,27,169,218,40,46,233,196,176,26,43,183,137,226,82,94,83,214,92,224,211,40,46,122,196,180,138,79,157,130,168,121,212,57,40,157,18,162,124,62,111,205,167,109,252,252,74,68,226,44,191,38,217,102, + 178,241,74,140,117,140,171,44,37,6,14,71,47,35,169,121,68,247,160,154,130,78,157,211,93,141,25,253,4,169,131,222,42,53,129,208,249,187,23,214,142,53,230,118,67,9,81,14,243,87,221,242,213,66,166,19,210,234,110,241,53,9,251,100,237,175,133,40,79,34,93,53,0,157,215,87,19,244,207,187,201,61,141,226,204,103,209,234,76,137,141,231,106,8,244,128,178,244,192,122, + 116,75,247,223,62,172,81,246,121,81,243,137,11,162,22,16,198,220,186,120,189,214,146,99,164,182,252,56,54,227,204,197,230,20,209,35,142,55,61,42,53,151,177,210,78,148,54,216,35,234,80,18,108,15,175,114,20,89,253,2,243,97,221,199,151,173,120,216,125,92,56,45,158,51,107,112,47,10,167,197,191,87,173,180,1,94,183,34,198,177,58,132,237,55,37,110,27,61,218,179, + 221,237,83,147,161,217,183,62,125,220,12,175,137,233,197,48,209,205,245,147,239,245,74,62,215,5,121,216,76,194,248,235,246,73,79,238,99,166,198,16,127,127,93,17,210,37,65,13,182,242,254,195,216,19,20,152,204,224,28,65,138,232,7,83,166,217,63,181,116,216,12,27,54,167,132,30,131,253,147,56,15,58,153,18,122,202,250,39,153,13,186,95,255,222,157,46,126,103,65,244, + 54,48,41,121,142,105,50,33,116,123,14,172,181,0,63,135,214,98,96,209,188,76,194,125,166,59,2,116,60,121,110,189,20,55,10,35,201,121,81,241,209,225,21,158,231,238,173,110,237,144,119,137,143,147,188,139,110,89,106,49,234,143,131,90,192,88,18,211,230,37,235,210,140,207,71,168,115,179,255,21,137,241,224,151,87,137,56,54,221,168,69,196,175,108,37,65,187,101,168,37, + 132,221,79,121,143,49,22,87,249,53,73,157,113,160,180,185,29,91,209,147,247,185,214,69,148,136,227,198,139,195,220,115,8,206,235,232,77,20,159,122,136,246,214,90,28,15,110,117,175,181,216,159,34,121,143,251,143,23,249,22,239,13,126,113,97,202,23,245,200,232,142,10,95,190,232,22,210,138,255,254,63,0,0,0,255,255,0,0,0,255,255,99,102,0,0}; + } +} diff --git a/src/Avalonia.Base/Point.cs b/src/Avalonia.Base/Point.cs index fbdf0db800..0c789ff20f 100644 --- a/src/Avalonia.Base/Point.cs +++ b/src/Avalonia.Base/Point.cs @@ -188,7 +188,7 @@ namespace Avalonia } /// - /// Returns a boolean indicating whether the point is equal to the other given point. + /// Returns a boolean indicating whether the point is equal to the other given point (bitwise). /// /// The other point to test equality against. /// True if this point is equal to other; False otherwise. @@ -200,6 +200,18 @@ namespace Avalonia // ReSharper enable CompareOfFloatsByEqualityOperator } + /// + /// Returns a boolean indicating whether the point is equal to the other given point + /// (numerically). + /// + /// The other point to test equality against. + /// True if this point is equal to other; False otherwise. + public bool NearlyEquals(Point other) + { + return MathUtilities.AreClose(_x, other._x) && + MathUtilities.AreClose(_y, other._y); + } + /// /// Checks for equality between a point and an object. /// diff --git a/src/Avalonia.Base/PropertyStore/PriorityValue.cs b/src/Avalonia.Base/PropertyStore/PriorityValue.cs index 112cf6619f..182b2638c4 100644 --- a/src/Avalonia.Base/PropertyStore/PriorityValue.cs +++ b/src/Avalonia.Base/PropertyStore/PriorityValue.cs @@ -121,6 +121,7 @@ namespace Avalonia.PropertyStore public void ClearLocalValue() { + _localValue = default; UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs( _owner, Property, diff --git a/src/Avalonia.Base/Styling/PropertySetterInstance.cs b/src/Avalonia.Base/Styling/PropertySetterInstance.cs index c4e8f47e67..9028224cc1 100644 --- a/src/Avalonia.Base/Styling/PropertySetterInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterInstance.cs @@ -18,7 +18,7 @@ namespace Avalonia.Styling private readonly DirectPropertyBase? _directProperty; private readonly T _value; private IDisposable? _subscription; - private bool _isActive; + private State _state; public PropertySetterInstance( IStyleable target, @@ -40,6 +40,8 @@ namespace Avalonia.Styling _value = value; } + private bool IsActive => _state == State.Active; + public void Start(bool hasActivator) { if (hasActivator) @@ -70,31 +72,35 @@ namespace Avalonia.Styling public void Activate() { - if (!_isActive) + if (!IsActive) { - _isActive = true; + _state = State.Active; PublishNext(); } } public void Deactivate() { - if (_isActive) + if (IsActive) { - _isActive = false; + _state = State.Inactive; PublishNext(); } } public override void Dispose() { + if (_state == State.Disposed) + return; + _state = State.Disposed; + if (_subscription is object) { var sub = _subscription; _subscription = null; sub.Dispose(); } - else if (_isActive) + else if (IsActive) { if (_styledProperty is object) { @@ -114,7 +120,14 @@ namespace Avalonia.Styling private void PublishNext() { - PublishNext(_isActive ? new BindingValue(_value) : default); + PublishNext(IsActive ? new BindingValue(_value) : default); + } + + private enum State + { + Inactive, + Active, + Disposed, } } } diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 4fd21f02f9..bdf8723b81 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -376,7 +376,9 @@ namespace Avalonia if (e.OldValue is IAffectsRender oldValue) { if (sender._affectsRenderWeakSubscriber != null) + { InvalidatedWeakEvent.Unsubscribe(oldValue, sender._affectsRenderWeakSubscriber); + } } if (e.NewValue is IAffectsRender newValue) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index fa437de186..b39ceab1b6 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -49,9 +49,9 @@ namespace Avalonia.Build.Tasks string projectDirectory, string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom, bool skipXamlCompilation, bool debuggerLaunch) { - var typeSystem = new CecilTypeSystem(references - .Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")) - .Concat(new[] { input }), input); + var typeSystem = new CecilTypeSystem( + references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")), + input); var asm = typeSystem.TargetAssemblyDefinition; diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 245592207e..7e6b70a146 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -128,8 +128,8 @@ namespace Avalonia.Controls.Primitives if (_inputTarget != null) { - _inputTarget.PointerEnter += InputTarget_PointerEnter; - _inputTarget.PointerLeave += InputTarget_PointerLeave; + _inputTarget.PointerEntered += InputTarget_PointerEntered; + _inputTarget.PointerExited += InputTarget_PointerExited; _inputTarget.PointerPressed += InputTarget_PointerPressed; _inputTarget.PointerMoved += InputTarget_PointerMoved; _inputTarget.PointerReleased += InputTarget_PointerReleased; @@ -194,8 +194,8 @@ namespace Avalonia.Controls.Primitives if (_inputTarget != null) { - _inputTarget.PointerEnter -= InputTarget_PointerEnter; - _inputTarget.PointerLeave -= InputTarget_PointerLeave; + _inputTarget.PointerEntered -= InputTarget_PointerEntered; + _inputTarget.PointerExited -= InputTarget_PointerExited; _inputTarget.PointerPressed -= InputTarget_PointerPressed; _inputTarget.PointerMoved -= InputTarget_PointerMoved; _inputTarget.PointerReleased -= InputTarget_PointerReleased; @@ -362,7 +362,7 @@ namespace Avalonia.Controls.Primitives } /// - protected override void OnPointerLeave(PointerEventArgs e) + protected override void OnPointerExited(PointerEventArgs e) { // We only want to bother with the color name tool tip if we can provide color names. if (_selectionEllipsePanel != null && @@ -373,7 +373,7 @@ namespace Avalonia.Controls.Primitives UpdatePseudoClasses(); - base.OnPointerLeave(e); + base.OnPointerExited(e); } /// @@ -848,16 +848,16 @@ namespace Avalonia.Controls.Primitives UpdatePseudoClasses(); } - /// - private void InputTarget_PointerEnter(object? sender, PointerEventArgs args) + /// + private void InputTarget_PointerEntered(object? sender, PointerEventArgs args) { _isPointerOver = true; UpdatePseudoClasses(); args.Handled = true; } - /// - private void InputTarget_PointerLeave(object? sender, PointerEventArgs args) + /// + private void InputTarget_PointerExited(object? sender, PointerEventArgs args) { _isPointerOver = false; UpdatePseudoClasses(); diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index 67183781d3..2a51f45896 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs @@ -139,18 +139,18 @@ namespace Avalonia.Controls } } - protected override void OnPointerEnter(PointerEventArgs e) + protected override void OnPointerEntered(PointerEventArgs e) { - base.OnPointerEnter(e); + base.OnPointerEntered(e); if (OwningRow != null) { IsMouseOver = true; } } - protected override void OnPointerLeave(PointerEventArgs e) + protected override void OnPointerExited(PointerEventArgs e) { - base.OnPointerLeave(e); + base.OnPointerExited(e); if (OwningRow != null) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index f3ea48ff80..c415f477d4 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -855,7 +855,7 @@ namespace Avalonia.Controls if (OwningGrid != null && OwningGrid.UseLayoutRounding) { var scale = LayoutHelper.GetLayoutScale(HeaderCell); - var roundSize = LayoutHelper.RoundLayoutSize(new Size(leftEdge + ActualWidth, 1), scale, scale); + var roundSize = LayoutHelper.RoundLayoutSizeUp(new Size(leftEdge + ActualWidth, 1), scale, scale); LayoutRoundedWidth = roundSize.Width - leftEdge; } else diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 915b36687c..d3bd968d62 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -83,9 +83,9 @@ namespace Avalonia.Controls { PointerPressed += DataGridColumnHeader_PointerPressed; PointerReleased += DataGridColumnHeader_PointerReleased; - PointerMoved += DataGridColumnHeader_PointerMove; - PointerEnter += DataGridColumnHeader_PointerEnter; - PointerLeave += DataGridColumnHeader_PointerLeave; + PointerMoved += DataGridColumnHeader_PointerMoved; + PointerEntered += DataGridColumnHeader_PointerEntered; + PointerExited += DataGridColumnHeader_PointerExited; } private void OnAreSeparatorsVisibleChanged(AvaloniaPropertyChangedEventArgs e) @@ -452,7 +452,7 @@ namespace Avalonia.Controls SetDragCursor(mousePosition); } - private void DataGridColumnHeader_PointerEnter(object sender, PointerEventArgs e) + private void DataGridColumnHeader_PointerEntered(object sender, PointerEventArgs e) { if (!IsEnabled) { @@ -464,7 +464,7 @@ namespace Avalonia.Controls UpdatePseudoClasses(); } - private void DataGridColumnHeader_PointerLeave(object sender, PointerEventArgs e) + private void DataGridColumnHeader_PointerExited(object sender, PointerEventArgs e) { if (!IsEnabled) { @@ -506,7 +506,7 @@ namespace Avalonia.Controls UpdatePseudoClasses(); } - private void DataGridColumnHeader_PointerMove(object sender, PointerEventArgs e) + private void DataGridColumnHeader_PointerMoved(object sender, PointerEventArgs e) { if (OwningGrid == null || !IsEnabled) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index b062a14e39..1559763a1b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -607,15 +607,15 @@ namespace Avalonia.Controls } } - protected override void OnPointerEnter(PointerEventArgs e) + protected override void OnPointerEntered(PointerEventArgs e) { - base.OnPointerEnter(e); + base.OnPointerEntered(e); IsMouseOver = true; } - protected override void OnPointerLeave(PointerEventArgs e) + protected override void OnPointerExited(PointerEventArgs e) { IsMouseOver = false; - base.OnPointerLeave(e); + base.OnPointerExited(e); } internal void ApplyCellsState() diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index a3dfa44fc9..9f37e8a6aa 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -375,7 +375,7 @@ namespace Avalonia.Controls ApplyHeaderStatus(); } - protected override void OnPointerEnter(PointerEventArgs e) + protected override void OnPointerEntered(PointerEventArgs e) { if (IsEnabled) { @@ -383,10 +383,10 @@ namespace Avalonia.Controls UpdatePseudoClasses(); } - base.OnPointerEnter(e); + base.OnPointerEntered(e); } - protected override void OnPointerLeave(PointerEventArgs e) + protected override void OnPointerExited(PointerEventArgs e) { if (IsEnabled) { @@ -394,7 +394,7 @@ namespace Avalonia.Controls UpdatePseudoClasses(); } - base.OnPointerLeave(e); + base.OnPointerExited(e); } private void SetIsCheckedNoCallBack(bool value) diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs index 03299bbf35..fe3ba0abf6 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs @@ -158,23 +158,23 @@ namespace Avalonia.Controls.Primitives } } - protected override void OnPointerEnter(PointerEventArgs e) + protected override void OnPointerEntered(PointerEventArgs e) { if (OwningRow != null) { OwningRow.IsMouseOver = true; } - base.OnPointerEnter(e); + base.OnPointerEntered(e); } - protected override void OnPointerLeave(PointerEventArgs e) + protected override void OnPointerExited(PointerEventArgs e) { if (OwningRow != null) { OwningRow.IsMouseOver = false; } - base.OnPointerLeave(e); + base.OnPointerExited(e); } //TODO TabStop diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index bcac6324ba..4c958c83b7 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -189,7 +189,7 @@ namespace Avalonia.Controls.Primitives EventHandler cellMouseLeftButtonDown = Cell_MouseLeftButtonDown; EventHandler cellMouseLeftButtonUp = Cell_MouseLeftButtonUp; - EventHandler cellMouseEnter = Cell_MouseEnter; + EventHandler cellMouseEntered = Cell_MouseEntered; EventHandler cellClick = Cell_Click; for (int i = 1; i < Calendar.RowsPerMonth; i++) @@ -206,7 +206,7 @@ namespace Avalonia.Controls.Primitives cell.SetValue(Grid.ColumnProperty, j); cell.CalendarDayButtonMouseDown += cellMouseLeftButtonDown; cell.CalendarDayButtonMouseUp += cellMouseLeftButtonUp; - cell.PointerEnter += cellMouseEnter; + cell.PointerEntered += cellMouseEntered; cell.Click += cellClick; children.Add(cell); } @@ -222,7 +222,7 @@ namespace Avalonia.Controls.Primitives EventHandler monthCalendarButtonMouseDown = Month_CalendarButtonMouseDown; EventHandler monthCalendarButtonMouseUp = Month_CalendarButtonMouseUp; - EventHandler monthMouseEnter = Month_MouseEnter; + EventHandler monthMouseEntered = Month_MouseEntered; for (int i = 0; i < Calendar.RowsPerYear; i++) { @@ -238,7 +238,7 @@ namespace Avalonia.Controls.Primitives month.SetValue(Grid.ColumnProperty, j); month.CalendarLeftMouseButtonDown += monthCalendarButtonMouseDown; month.CalendarLeftMouseButtonUp += monthCalendarButtonMouseUp; - month.PointerEnter += monthMouseEnter; + month.PointerEntered += monthMouseEntered; children.Add(month); } } @@ -876,7 +876,7 @@ namespace Avalonia.Controls.Primitives } } - internal void Cell_MouseEnter(object? sender, PointerEventArgs e) + internal void Cell_MouseEntered(object? sender, PointerEventArgs e) { if (Owner != null) { @@ -1162,7 +1162,7 @@ namespace Avalonia.Controls.Primitives } } - private void Month_MouseEnter(object? sender, PointerEventArgs e) + private void Month_MouseEntered(object? sender, PointerEventArgs e) { if (_isMouseLeftButtonDownYearView) { diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index cbf9b35a05..05be5ad00d 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -181,26 +181,13 @@ namespace Avalonia.Controls protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - this.UpdateSelectionBoxItem(SelectedItem); + UpdateSelectionBoxItem(SelectedItem); } - // Because the SelectedItem isn't connected to the visual tree public override void InvalidateMirrorTransform() { base.InvalidateMirrorTransform(); - - if (SelectedItem is Control selectedControl) - { - selectedControl.InvalidateMirrorTransform(); - - foreach (var visual in selectedControl.GetVisualDescendants()) - { - if (visual is Control childControl) - { - childControl.InvalidateMirrorTransform(); - } - } - } + UpdateFlowDirection(); } /// @@ -365,6 +352,8 @@ namespace Avalonia.Controls { parent.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen); } + + UpdateFlowDirection(); } private void IsVisibleChanged(bool isVisible) @@ -432,6 +421,8 @@ namespace Avalonia.Controls } }; } + + UpdateFlowDirection(); } else { @@ -439,6 +430,19 @@ namespace Avalonia.Controls } } + private void UpdateFlowDirection() + { + if (SelectionBoxItem is Rectangle rectangle) + { + if ((rectangle.Fill as VisualBrush)?.Visual is Control content) + { + var flowDirection = (((IVisual)content!).VisualParent as Control)?.FlowDirection ?? + FlowDirection.LeftToRight; + rectangle.FlowDirection = flowDirection; + } + } + } + private void SelectFocusedItem() { foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index d6a5fa0727..083182a370 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using Avalonia.Automation.Peers; using Avalonia.Controls.Documents; @@ -10,6 +11,7 @@ using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Rendering; using Avalonia.Styling; +using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -53,21 +55,57 @@ namespace Avalonia.Controls /// Event raised when an element wishes to be scrolled into view. /// public static readonly RoutedEvent RequestBringIntoViewEvent = - RoutedEvent.Register("RequestBringIntoView", RoutingStrategies.Bubble); + RoutedEvent.Register( + "RequestBringIntoView", + RoutingStrategies.Bubble); /// /// Provides event data for the event. /// public static readonly RoutedEvent ContextRequestedEvent = - RoutedEvent.Register(nameof(ContextRequested), + RoutedEvent.Register( + nameof(ContextRequested), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + /// + /// Defines the event. + /// + public static readonly RoutedEvent LoadedEvent = + RoutedEvent.Register( + nameof(Loaded), + RoutingStrategies.Direct); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent UnloadedEvent = + RoutedEvent.Register( + nameof(Unloaded), + RoutingStrategies.Direct); + /// /// Defines the property. /// public static readonly AttachedProperty FlowDirectionProperty = - AvaloniaProperty.RegisterAttached(nameof(FlowDirection), inherits: true); - + AvaloniaProperty.RegisterAttached( + nameof(FlowDirection), + inherits: true); + + // Note the following: + // _loadedQueue : + // Is the queue where any control will be added to indicate that its loaded + // event should be scheduled and called later. + // _loadedProcessingQueue : + // Contains a copied snapshot of the _loadedQueue at the time when processing + // starts and individual events are being fired. This was needed to avoid + // exceptions if new controls were added in the Loaded event itself. + + private static bool _isLoadedProcessing = false; + private static readonly HashSet _loadedQueue = new HashSet(); + private static readonly HashSet _loadedProcessingQueue = new HashSet(); + + private bool _isAttachedToVisualTree = false; + private bool _isLoaded = false; private DataTemplates? _dataTemplates; private IControl? _focusAdorner; private AutomationPeer? _automationPeer; @@ -108,6 +146,15 @@ namespace Avalonia.Controls set => SetValue(ContextFlyoutProperty, value); } + /// + /// Gets a value indicating whether the control is fully constructed in the visual tree + /// and both layout and render are complete. + /// + /// + /// This is set to true while raising the event. + /// + public bool IsLoaded => _isLoaded; + /// /// Gets or sets a user-defined object attached to the control. /// @@ -135,6 +182,35 @@ namespace Avalonia.Controls remove => RemoveHandler(ContextRequestedEvent, value); } + /// + /// Occurs when the control has been fully constructed in the visual tree and both + /// layout and render are complete. + /// + /// + /// This event is guaranteed to occur after the control template is applied and references + /// to objects created after the template is applied are available. This makes it different + /// from OnAttachedToVisualTree which doesn't have these references. This event occurs at the + /// latest possible time in the control creation life-cycle. + /// + public event EventHandler? Loaded + { + add => AddHandler(LoadedEvent, value); + remove => RemoveHandler(LoadedEvent, value); + } + + /// + /// Occurs when the control is removed from the visual tree. + /// + /// + /// This is API symmetrical with and exists for compatibility with other + /// XAML frameworks; however, it behaves the same as OnDetachedFromVisualTree. + /// + public event EventHandler? Unloaded + { + add => AddHandler(UnloadedEvent, value); + remove => RemoveHandler(UnloadedEvent, value); + } + public new IControl? Parent => (IControl?)base.Parent; /// @@ -215,18 +291,124 @@ namespace Avalonia.Controls /// The control that receives the focus adorner. protected virtual IControl? GetTemplateFocusTarget() => this; + private static Action loadedProcessingAction = () => + { + // Copy the loaded queue for processing + // There was a possibility of the "Collection was modified; enumeration operation may not execute." + // exception when only a single hash set was used. This could happen when new controls are added + // within the Loaded callback/event itself. To fix this, two hash sets are used and while one is + // being processed the other accepts adding new controls to process next. + _loadedProcessingQueue.Clear(); + foreach (Control control in _loadedQueue) + { + _loadedProcessingQueue.Add(control); + } + _loadedQueue.Clear(); + + foreach (Control control in _loadedProcessingQueue) + { + control.OnLoadedCore(); + } + + _loadedProcessingQueue.Clear(); + _isLoadedProcessing = false; + + // Restart if any controls were added to the queue while processing + if (_loadedQueue.Count > 0) + { + _isLoadedProcessing = true; + Dispatcher.UIThread.Post(loadedProcessingAction!, DispatcherPriority.Loaded); + } + }; + + /// + /// Schedules to be called for this control. + /// For performance, it will be queued with other controls. + /// + internal void ScheduleOnLoadedCore() + { + if (_isLoaded == false) + { + bool isAdded = _loadedQueue.Add(this); + + if (isAdded && + _isLoadedProcessing == false) + { + _isLoadedProcessing = true; + Dispatcher.UIThread.Post(loadedProcessingAction!, DispatcherPriority.Loaded); + } + } + } + + /// + /// Invoked as the first step of marking the control as loaded and raising the + /// event. + /// + internal void OnLoadedCore() + { + if (_isLoaded == false && + _isAttachedToVisualTree) + { + _isLoaded = true; + OnLoaded(); + } + } + + /// + /// Invoked as the first step of marking the control as unloaded and raising the + /// event. + /// + internal void OnUnloadedCore() + { + if (_isLoaded) + { + // Remove from the loaded event queue here as a failsafe in case the control + // is detached before the dispatcher runs the Loaded jobs. + _loadedQueue.Remove(this); + + _isLoaded = false; + OnUnloaded(); + } + } + + /// + /// Invoked just before the event. + /// + protected virtual void OnLoaded() + { + var eventArgs = new RoutedEventArgs(LoadedEvent); + eventArgs.Source = null; + RaiseEvent(eventArgs); + } + + /// + /// Invoked just before the event. + /// + protected virtual void OnUnloaded() + { + var eventArgs = new RoutedEventArgs(UnloadedEvent); + eventArgs.Source = null; + RaiseEvent(eventArgs); + } + /// protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTreeCore(e); + _isAttachedToVisualTree = true; InitializeIfNeeded(); + + ScheduleOnLoadedCore(); } /// protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTreeCore(e); + _isAttachedToVisualTree = false; + + OnUnloadedCore(); } /// @@ -324,7 +506,9 @@ namespace Avalonia.Controls var keymap = AvaloniaLocator.Current.GetService()?.OpenContextMenu; if (keymap is null) + { return; + } var matches = false; @@ -378,17 +562,12 @@ namespace Avalonia.Controls bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies; bool parentBypassFlowDirectionPolicies = false; - var parent = this.FindAncestorOfType(); + var parent = ((IVisual)this).VisualParent as Control; if (parent != null) { parentFlowDirection = parent.FlowDirection; parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies; } - else if (Parent is Control logicalParent) - { - parentFlowDirection = logicalParent.FlowDirection; - parentBypassFlowDirectionPolicies = logicalParent.BypassFlowDirectionPolicies; - } bool thisShouldBeMirrored = flowDirection == FlowDirection.RightToLeft && !bypassFlowDirectionPolicies; bool parentShouldBeMirrored = parentFlowDirection == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies; diff --git a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs index 6ab2f4c517..9d859a753a 100644 --- a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs +++ b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs @@ -26,7 +26,7 @@ namespace Avalonia.Controls.Converters if (visibility == ScrollBarVisibility.Auto) { - if (extent == viewport) + if (MathUtilities.AreClose(extent, viewport)) { return false; } diff --git a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs index 7f2abb7e98..46c5eaeaaa 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs @@ -194,7 +194,7 @@ namespace Avalonia.Controls if (ClockIdentifier == "12HourClock") { - hr = per == 1 ? hr + 12 : per == 0 && hr == 12 ? 0 : hr; + hr = per == 1 ? (hr == 12) ? 12 : hr + 12 : per == 0 && hr == 12 ? 0 : hr; } Time = new TimeSpan(hr, min, 0); diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs index 784d33ed58..85dad894fd 100644 --- a/src/Avalonia.Controls/GridSplitter.cs +++ b/src/Avalonia.Controls/GridSplitter.cs @@ -349,9 +349,9 @@ namespace Avalonia.Controls } } - protected override void OnPointerEnter(PointerEventArgs e) + protected override void OnPointerEntered(PointerEventArgs e) { - base.OnPointerEnter(e); + base.OnPointerEntered(e); GridResizeDirection direction = GetEffectiveResizeDirection(); diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 58229a1772..11c42f2ef3 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -79,25 +79,33 @@ namespace Avalonia.Controls /// Defines the event. /// public static readonly RoutedEvent ClickEvent = - RoutedEvent.Register(nameof(Click), RoutingStrategies.Bubble); + RoutedEvent.Register( + nameof(Click), + RoutingStrategies.Bubble); /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent PointerEnterItemEvent = - RoutedEvent.Register(nameof(PointerEnterItem), RoutingStrategies.Bubble); + public static readonly RoutedEvent PointerEnteredItemEvent = + RoutedEvent.Register( + nameof(PointerEnteredItem), + RoutingStrategies.Bubble); /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent PointerLeaveItemEvent = - RoutedEvent.Register(nameof(PointerLeaveItem), RoutingStrategies.Bubble); + public static readonly RoutedEvent PointerExitedItemEvent = + RoutedEvent.Register( + nameof(PointerExitedItem), + RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent SubmenuOpenedEvent = - RoutedEvent.Register(nameof(SubmenuOpened), RoutingStrategies.Bubble); + RoutedEvent.Register( + nameof(SubmenuOpened), + RoutingStrategies.Bubble); /// /// The default value for the property. @@ -174,24 +182,24 @@ namespace Avalonia.Controls /// Occurs when the pointer enters a menu item. /// /// - /// A bubbling version of the event for menu items. + /// A bubbling version of the event for menu items. /// - public event EventHandler? PointerEnterItem + public event EventHandler? PointerEnteredItem { - add { AddHandler(PointerEnterItemEvent, value); } - remove { RemoveHandler(PointerEnterItemEvent, value); } + add { AddHandler(PointerEnteredItemEvent, value); } + remove { RemoveHandler(PointerEnteredItemEvent, value); } } /// /// Raised when the pointer leaves a menu item. /// /// - /// A bubbling version of the event for menu items. + /// A bubbling version of the event for menu items. /// - public event EventHandler? PointerLeaveItem + public event EventHandler? PointerExitedItem { - add { AddHandler(PointerLeaveItemEvent, value); } - remove { RemoveHandler(PointerLeaveItemEvent, value); } + add { AddHandler(PointerExitedItemEvent, value); } + remove { RemoveHandler(PointerExitedItemEvent, value); } } /// @@ -437,22 +445,22 @@ namespace Avalonia.Controls } /// - protected override void OnPointerEnter(PointerEventArgs e) + protected override void OnPointerEntered(PointerEventArgs e) { - base.OnPointerEnter(e); + base.OnPointerEntered(e); var point = e.GetCurrentPoint(null); - RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position, + RaiseEvent(new PointerEventArgs(PointerEnteredItemEvent, this, e.Pointer, this.VisualRoot, point.Position, e.Timestamp, point.Properties, e.KeyModifiers)); } /// - protected override void OnPointerLeave(PointerEventArgs e) + protected override void OnPointerExited(PointerEventArgs e) { - base.OnPointerLeave(e); + base.OnPointerExited(e); var point = e.GetCurrentPoint(null); - RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position, + RaiseEvent(new PointerEventArgs(PointerExitedItemEvent, this, e.Pointer, this.VisualRoot, point.Position, e.Timestamp, point.Properties, e.KeyModifiers)); } diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 3a6d06f150..868cce879a 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -53,9 +53,9 @@ namespace Avalonia.Controls.Platform Menu.PointerPressed += PointerPressed; Menu.PointerReleased += PointerReleased; Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); - Menu.AddHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened); - Menu.AddHandler(MenuItem.PointerEnterItemEvent, PointerEnter); - Menu.AddHandler(MenuItem.PointerLeaveItemEvent, PointerLeave); + Menu.AddHandler(Avalonia.Controls.Menu.MenuOpenedEvent, MenuOpened); + Menu.AddHandler(MenuItem.PointerEnteredItemEvent, PointerEntered); + Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved); _root = Menu.VisualRoot; @@ -89,9 +89,9 @@ namespace Avalonia.Controls.Platform Menu.PointerPressed -= PointerPressed; Menu.PointerReleased -= PointerReleased; Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); - Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened); - Menu.RemoveHandler(MenuItem.PointerEnterItemEvent, PointerEnter); - Menu.RemoveHandler(MenuItem.PointerLeaveItemEvent, PointerLeave); + Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, MenuOpened); + Menu.RemoveHandler(MenuItem.PointerEnteredItemEvent, PointerEntered); + Menu.RemoveHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved); if (_root is InputElement inputRoot) @@ -297,7 +297,7 @@ namespace Avalonia.Controls.Platform e.Handled = true; } - protected internal virtual void PointerEnter(object? sender, PointerEventArgs e) + protected internal virtual void PointerEntered(object? sender, PointerEventArgs e) { var item = GetMenuItem(e.Source as IControl); @@ -358,7 +358,7 @@ namespace Avalonia.Controls.Platform } } - protected internal virtual void PointerLeave(object? sender, PointerEventArgs e) + protected internal virtual void PointerExited(object? sender, PointerEventArgs e) { var item = GetMenuItem(e.Source as IControl); diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 12a8dd747d..39888c1239 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -635,8 +635,8 @@ namespace Avalonia.Controls.Presenters if (useLayoutRounding) { - sizeForChild = LayoutHelper.RoundLayoutSize(sizeForChild, scale, scale); - availableSize = LayoutHelper.RoundLayoutSize(availableSize, scale, scale); + sizeForChild = LayoutHelper.RoundLayoutSizeUp(sizeForChild, scale, scale); + availableSize = LayoutHelper.RoundLayoutSizeUp(availableSize, scale, scale); } switch (horizontalContentAlignment) diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index e5c3392faf..e524db5444 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -218,9 +218,9 @@ namespace Avalonia.Controls.Primitives } } - protected override void OnPointerEnter(PointerEventArgs e) + protected override void OnPointerEntered(PointerEventArgs e) { - base.OnPointerEnter(e); + base.OnPointerEntered(e); if (AllowAutoHide) { @@ -228,9 +228,9 @@ namespace Avalonia.Controls.Primitives } } - protected override void OnPointerLeave(PointerEventArgs e) + protected override void OnPointerExited(PointerEventArgs e) { - base.OnPointerLeave(e); + base.OnPointerExited(e); if (AllowAutoHide) { diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 1a69d1218c..db315d3aaf 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -113,7 +113,9 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly AttachedProperty TextAlignmentProperty = - AvaloniaProperty.RegisterAttached(nameof(TextAlignment), + AvaloniaProperty.RegisterAttached( + nameof(TextAlignment), + defaultValue: TextAlignment.Start, inherits: true); /// @@ -748,14 +750,14 @@ namespace Avalonia.Controls { if (textSourceIndex > _text.Length) { - return null; + return new TextEndOfParagraph(); } var runText = _text.Skip(textSourceIndex); if (runText.IsEmpty) { - return null; + return new TextEndOfParagraph(); } return new TextCharacters(runText, _defaultProperties); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 7652b23162..9531f719b9 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)); @@ -196,7 +196,6 @@ namespace Avalonia.Controls private TextBoxTextInputMethodClient _imClient = new TextBoxTextInputMethodClient(); private UndoRedoHelper _undoRedoHelper; private bool _isUndoingRedoing; - private bool _ignoreTextChanges; private bool _canCut; private bool _canCopy; private bool _canPaste; @@ -276,7 +275,7 @@ namespace Avalonia.Controls get => GetValue(IsReadOnlyProperty); set => SetValue(IsReadOnlyProperty, value); } - + public char PasswordChar { get => GetValue(PasswordCharProperty); @@ -368,21 +367,17 @@ namespace Avalonia.Controls get => _text; set { - if (!_ignoreTextChanges) - { - var caretIndex = CaretIndex; - var selectionStart = SelectionStart; - var selectionEnd = SelectionEnd; + var caretIndex = CaretIndex; + var selectionStart = SelectionStart; + var selectionEnd = SelectionEnd; - CaretIndex = CoerceCaretIndex(caretIndex, value); - SelectionStart = CoerceCaretIndex(selectionStart, value); - SelectionEnd = CoerceCaretIndex(selectionEnd, value); - - if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) - { - _undoRedoHelper.Clear(); - SnapshotUndoRedo(); // so we always have an initial state - } + CaretIndex = CoerceCaretIndex(caretIndex, value); + SelectionStart = CoerceCaretIndex(selectionStart, value); + SelectionEnd = CoerceCaretIndex(selectionEnd, value); + if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) + { + _undoRedoHelper.Clear(); + SnapshotUndoRedo(); // so we always have an initial state } } } @@ -736,32 +731,23 @@ namespace Avalonia.Controls { var oldText = _text; - _ignoreTextChanges = true; - - try - { - DeleteSelection(false); - var caretIndex = CaretIndex; - text = Text ?? string.Empty; - SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); - ClearSelection(); - - if (IsUndoEnabled) - { - _undoRedoHelper.DiscardRedo(); - } - - if (_text != oldText) - { - RaisePropertyChanged(TextProperty, oldText, _text); - } + DeleteSelection(false); + var caretIndex = CaretIndex; + text = Text ?? string.Empty; + SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); + ClearSelection(); - CaretIndex = caretIndex + input.Length; + if (IsUndoEnabled) + { + _undoRedoHelper.DiscardRedo(); } - finally + + if (_text != oldText) { - _ignoreTextChanges = false; + RaisePropertyChanged(TextProperty, oldText, _text); } + + CaretIndex = caretIndex + input.Length; } } @@ -1499,15 +1485,7 @@ namespace Avalonia.Controls { if (raiseTextChanged) { - try - { - _ignoreTextChanges = true; - SetAndRaise(TextProperty, ref _text, value); - } - finally - { - _ignoreTextChanges = false; - } + SetAndRaise(TextProperty, ref _text, value); } else { diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index a3100ff296..c6efe10488 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -26,14 +26,14 @@ namespace Avalonia.Controls if (e.OldValue != null) { - control.PointerEnter -= ControlPointerEnter; - control.PointerLeave -= ControlPointerLeave; + control.PointerEntered -= ControlPointerEntered; + control.PointerExited -= ControlPointerExited; } if (e.NewValue != null) { - control.PointerEnter += ControlPointerEnter; - control.PointerLeave += ControlPointerLeave; + control.PointerEntered += ControlPointerEntered; + control.PointerExited += ControlPointerExited; } if (ToolTip.GetIsOpen(control) && e.NewValue != e.OldValue && !(e.NewValue is ToolTip)) @@ -80,7 +80,7 @@ namespace Avalonia.Controls /// /// The event sender. /// The event args. - private void ControlPointerEnter(object? sender, PointerEventArgs e) + private void ControlPointerEntered(object? sender, PointerEventArgs e) { StopTimer(); @@ -101,7 +101,7 @@ namespace Avalonia.Controls /// /// The event sender. /// The event args. - private void ControlPointerLeave(object? sender, PointerEventArgs e) + private void ControlPointerExited(object? sender, PointerEventArgs e) { var control = (Control)sender!; Close(control); diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 12ba143c8a..5d3e51b394 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -169,7 +169,6 @@ namespace Avalonia.Controls } } - [Obsolete("No longer used. Has no effect.")] protected IDisposable BeginAutoSizing() => Disposable.Empty; @@ -186,6 +185,26 @@ namespace Avalonia.Controls } } + /// + protected override void OnClosed(EventArgs e) + { + // Window must manually raise Loaded/Unloaded events as it is a visual root and + // does not raise OnAttachedToVisualTreeCore/OnDetachedFromVisualTreeCore events + OnUnloadedCore(); + + base.OnClosed(e); + } + + /// + protected override void OnOpened(EventArgs e) + { + // Window must manually raise Loaded/Unloaded events as it is a visual root and + // does not raise OnAttachedToVisualTreeCore/OnDetachedFromVisualTreeCore events + ScheduleOnLoadedCore(); + + base.OnOpened(e); + } + protected override void HandleClosed() { _ignoreVisibilityChange = true; diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs index 58807b489e..587898ac6e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs @@ -121,8 +121,8 @@ namespace Avalonia.Diagnostics.Views if (header != null) { - header.PointerEnter += AddAdorner; - header.PointerLeave += RemoveAdorner; + header.PointerEntered += AddAdorner; + header.PointerExited += RemoveAdorner; } item.TemplateApplied -= TreeViewItemTemplateApplied; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 01e0fb1afc..a233dc9693 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -472,10 +472,14 @@ namespace Avalonia.Win32 { GetWindowRect(_hwnd, out var rc); - return new PixelPoint(rc.left, rc.top); + var border = HiddenBorderSize; + return new PixelPoint(rc.left + border.Width, rc.top + border.Height); } set { + var border = HiddenBorderSize; + value = new PixelPoint(value.X - border.Width, value.Y - border.Height); + SetWindowPos( Handle.Handle, IntPtr.Zero, @@ -489,6 +493,24 @@ namespace Avalonia.Win32 private bool HasFullDecorations => _windowProperties.Decorations == SystemDecorations.Full; + private PixelSize HiddenBorderSize + { + get + { + // Windows 10 and 11 add a 7 pixel invisible border on the left/right/bottom of windows for resizing + if (Win32Platform.WindowsVersion.Major < 10 || !HasFullDecorations) + { + return PixelSize.Empty; + } + + DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var clientRect, Marshal.SizeOf(typeof(RECT))); + GetWindowRect(_hwnd, out var frameRect); + var borderWidth = GetSystemMetrics(SystemMetric.SM_CXBORDER); + + return new PixelSize(clientRect.left - frameRect.left - borderWidth, 0); + } + } + public void Move(PixelPoint point) => Position = point; public void SetMinMaxSize(Size minSize, Size maxSize) @@ -907,17 +929,20 @@ namespace Avalonia.Win32 borderCaptionThickness.top = 1; } + //using a default margin of 0 when using WinUiComp removes artefacts when resizing. See issue #8316 + var defaultMargin = _isUsingComposition ? 0 : 1; + MARGINS margins = new MARGINS(); - margins.cxLeftWidth = 1; - margins.cxRightWidth = 1; - margins.cyBottomHeight = 1; + margins.cxLeftWidth = defaultMargin; + margins.cxRightWidth = defaultMargin; + margins.cyBottomHeight = defaultMargin; if (_extendTitleBarHint != -1) { borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling); } - margins.cyTopHeight = _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1; + margins.cyTopHeight = _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : defaultMargin; if (WindowState == WindowState.Maximized) { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs index 954a609315..72162a4d8e 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs @@ -17,6 +17,21 @@ namespace Avalonia.Base.UnitTests Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } + [Fact] + public void ClearValue_Resets_Value_To_Style_value() + { + Class1 target = new Class1(); + + target.SetValue(Class1.FooProperty, "style", BindingPriority.Style); + target.SetValue(Class1.FooProperty, "local"); + + Assert.Equal("local", target.GetValue(Class1.FooProperty)); + + target.ClearValue(Class1.FooProperty); + + Assert.Equal("style", target.GetValue(Class1.FooProperty)); + } + [Fact] public void ClearValue_Raises_PropertyChanged() { diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs index 87df65a080..a5ca2aef4a 100644 --- a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs @@ -189,7 +189,7 @@ namespace Avalonia.Base.UnitTests.Input // Ensure that e.Handled is reset between controls. root.PointerMoved += (s, e) => e.Handled = true; - decorator.PointerEnter += (s, e) => e.Handled = true; + decorator.PointerEntered += (s, e) => e.Handled = true; SetHit(renderer, decorator); impl.Object.Input!(CreateRawPointerMovedArgs(device, root)); @@ -231,7 +231,7 @@ namespace Avalonia.Base.UnitTests.Input } }); - AddEnterLeaveHandlers(HandleEvent, canvas, decorator); + AddEnteredExitedHandlers(HandleEvent, canvas, decorator); // Enter decorator SetHit(renderer, decorator); @@ -246,17 +246,17 @@ namespace Avalonia.Base.UnitTests.Input Assert.Equal( new[] { - ((object?)decorator, "PointerEnter"), - (decorator, "PointerMoved"), - (decorator, "PointerLeave"), - (canvas, "PointerEnter"), - (canvas, "PointerMoved") + ((object?)decorator, nameof(InputElement.PointerEntered)), + (decorator, nameof(InputElement.PointerMoved)), + (decorator, nameof(InputElement.PointerExited)), + (canvas, nameof(InputElement.PointerEntered)), + (canvas, nameof(InputElement.PointerMoved)) }, result); } [Fact] - public void PointerEnter_Leave_Should_Be_Raised_In_Correct_Order() + public void PointerEntered_Exited_Should_Be_Raised_In_Correct_Order() { using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); @@ -289,7 +289,7 @@ namespace Avalonia.Base.UnitTests.Input SetHit(renderer, canvas); impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); - AddEnterLeaveHandlers(HandleEvent, root, canvas, border, decorator); + AddEnteredExitedHandlers(HandleEvent, root, canvas, border, decorator); SetHit(renderer, decorator); impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); @@ -297,16 +297,16 @@ namespace Avalonia.Base.UnitTests.Input Assert.Equal( new[] { - ((object?)canvas, "PointerLeave"), - (decorator, "PointerEnter"), - (border, "PointerEnter"), + ((object?)canvas, nameof(InputElement.PointerExited)), + (decorator, nameof(InputElement.PointerEntered)), + (border, nameof(InputElement.PointerEntered)), }, result); } // https://github.com/AvaloniaUI/Avalonia/issues/7896 [Fact] - public void PointerEnter_Leave_Should_Set_Correct_Position() + public void PointerEntered_Exited_Should_Set_Correct_Position() { using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); @@ -331,7 +331,7 @@ namespace Avalonia.Base.UnitTests.Input } }); - AddEnterLeaveHandlers(HandleEvent, root, canvas); + AddEnteredExitedHandlers(HandleEvent, root, canvas); SetHit(renderer, canvas); impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root, expectedPosition)); @@ -342,10 +342,10 @@ namespace Avalonia.Base.UnitTests.Input Assert.Equal( new[] { - ((object?)canvas, "PointerEnter", expectedPosition), - (root, "PointerEnter", expectedPosition), - (canvas, "PointerLeave", expectedPosition), - (root, "PointerLeave", expectedPosition) + ((object?)canvas, nameof(InputElement.PointerEntered), expectedPosition), + (root, nameof(InputElement.PointerEntered), expectedPosition), + (canvas, nameof(InputElement.PointerExited), expectedPosition), + (root, nameof(InputElement.PointerExited), expectedPosition) }, result); } @@ -415,7 +415,7 @@ namespace Avalonia.Base.UnitTests.Input } }); - AddEnterLeaveHandlers(HandleEvent, root, canvas); + AddEnteredExitedHandlers(HandleEvent, root, canvas); // Init pointer over. SetHit(renderer, canvas); @@ -429,22 +429,22 @@ namespace Avalonia.Base.UnitTests.Input Assert.Equal( new[] { - ((object?)canvas, "PointerEnter", lastClientPosition), - (root, "PointerEnter", lastClientPosition), - (canvas, "PointerLeave", lastClientPosition), - (root, "PointerLeave", lastClientPosition), + ((object?)canvas, nameof(InputElement.PointerEntered), lastClientPosition), + (root, nameof(InputElement.PointerEntered), lastClientPosition), + (canvas, nameof(InputElement.PointerExited), lastClientPosition), + (root, nameof(InputElement.PointerExited), lastClientPosition), }, result); } - private static void AddEnterLeaveHandlers( + private static void AddEnteredExitedHandlers( EventHandler handler, params IInputElement[] controls) { foreach (var c in controls) { - c.PointerEnter += handler; - c.PointerLeave += handler; + c.PointerEntered += handler; + c.PointerExited += handler; c.PointerMoved += handler; } } diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs index 184aada6cd..c4b0795022 100644 --- a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs +++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs @@ -173,37 +173,6 @@ namespace Avalonia.Base.UnitTests.Layout target.Verify(x => x.InvalidateMeasure(root), Times.Once()); } - [Theory] - [InlineData(16, 6, 5.333333333333333)] - [InlineData(18, 10, 4)] - public void UseLayoutRounding_Arranges_Center_Alignment_Correctly_With_Fractional_Scaling( - double containerWidth, - double childWidth, - double expectedX) - { - Border target; - var root = new TestRoot - { - LayoutScaling = 1.5, - UseLayoutRounding = true, - Child = new Decorator - { - Width = containerWidth, - Height = 100, - Child = target = new Border - { - Width = childWidth, - HorizontalAlignment = HorizontalAlignment.Center, - } - } - }; - - root.Measure(new Size(100, 100)); - root.Arrange(new Rect(target.DesiredSize)); - - Assert.Equal(new Rect(expectedX, 0, childWidth, 100), target.Bounds); - } - [Fact] public void LayoutUpdated_Is_Called_At_End_Of_Layout_Pass() { diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_LayoutRounding.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_LayoutRounding.cs new file mode 100644 index 0000000000..77f1a8882d --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_LayoutRounding.cs @@ -0,0 +1,140 @@ +using Avalonia.Controls; +using Avalonia.Layout; +using Avalonia.UnitTests; +using Xunit; +using Xunit.Sdk; + +namespace Avalonia.Base.UnitTests.Layout +{ + public class LayoutableTests_LayoutRounding + { + [Theory] + [InlineData(100, 100)] + [InlineData(101, 101.33333333333333)] + [InlineData(103, 103.33333333333333)] + public void Measure_Adjusts_DesiredSize_Upwards_When_Constraint_Allows(double desiredSize, double expectedSize) + { + var target = new TestLayoutable(new Size(desiredSize, desiredSize)); + var root = CreateRoot(1.5, target); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + Assert.Equal(new Size(expectedSize, expectedSize), target.DesiredSize); + } + + [Fact] + public void Measure_Constrains_Adjusted_DesiredSize_To_Constraint() + { + var target = new TestLayoutable(new Size(101, 101)); + var root = CreateRoot(1.5, target, constraint: new Size(101, 101)); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + // Desired width/height with layout rounding is 101.3333 but constraint is 101,101 so + // layout rounding should be ignored. + Assert.Equal(new Size(101, 101), target.DesiredSize); + } + + [Fact] + public void Measure_Adjusts_DesiredSize_Upwards_When_Margin_Present() + { + var target = new TestLayoutable(new Size(101, 101), margin: 1); + var root = CreateRoot(1.5, target); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + // - 1 pixel margin is rounded up to 1.3333; for both sides it is 2.6666 + // - Size of 101 gets rounded up to 101.3333 + // - Final size = 101.3333 + 2.6666 = 104 + AssertEqual(new Size(104, 104), target.DesiredSize); + } + + [Fact] + public void Arrange_Adjusts_Bounds_Upwards_With_Margin() + { + var target = new TestLayoutable(new Size(101, 101), margin: 1); + var root = CreateRoot(1.5, target); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + // - 1 pixel margin is rounded up to 1.3333 + // - Size of 101 gets rounded up to 101.3333 + AssertEqual(new Point(1.3333333333333333, 1.3333333333333333), target.Bounds.Position); + AssertEqual(new Size(101.33333333333333, 101.33333333333333), target.Bounds.Size); + } + + [Theory] + [InlineData(16, 6, 5.333333333333333)] + [InlineData(18, 10, 4)] + public void Arranges_Center_Alignment_Correctly_With_Fractional_Scaling( + double containerWidth, + double childWidth, + double expectedX) + { + Border target; + var root = new TestRoot + { + LayoutScaling = 1.5, + UseLayoutRounding = true, + Child = new Decorator + { + Width = containerWidth, + Height = 100, + Child = target = new Border + { + Width = childWidth, + HorizontalAlignment = HorizontalAlignment.Center, + } + } + }; + + root.Measure(new Size(100, 100)); + root.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Rect(expectedX, 0, childWidth, 100), target.Bounds); + } + + private static TestRoot CreateRoot( + double scaling, + Control child, + Size? constraint = null) + { + return new TestRoot + { + LayoutScaling = scaling, + UseLayoutRounding = true, + Child = child, + ClientSize = constraint ?? new Size(1000, 1000), + }; + } + + private static void AssertEqual(Point expected, Point actual) + { + if (!expected.NearlyEquals(actual)) + { + throw new EqualException(expected, actual); + } + } + + private static void AssertEqual(Size expected, Size actual) + { + if (!expected.NearlyEquals(actual)) + { + throw new EqualException(expected, actual); + } + } + + private class TestLayoutable : Control + { + private Size _desiredSize; + + public TestLayoutable(Size desiredSize, double margin = 0) + { + _desiredSize = desiredSize; + Margin = new Thickness(margin); + } + + protected override Size MeasureOverride(Size availableSize) => _desiredSize; + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs index 7ad2e71d77..4d1941f7c5 100644 --- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs +++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs @@ -16,10 +16,12 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting Directory.CreateDirectory("Generated"); } + var trie = GenerateBreakTypeTrie(); + + UnicodeDataGenerator.GenerateTrieClass("GraphemeBreak", trie); + using (var stream = File.Create("Generated\\GraphemeBreak.trie")) { - var trie = GenerateBreakTypeTrie(); - trie.Save(stream); } } diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs index e2877abd83..ab6b06c200 100644 --- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs +++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs @@ -58,17 +58,69 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting { PairedBracketTypes = biDiPairedBracketTypeEntries, BiDiClasses = biDiClassEntries }; + + var trie = biDiTrieBuilder.Freeze(); + + GenerateTrieClass("BiDi", trie); using (var stream = File.Create("Generated\\BiDi.trie")) { - var trie = biDiTrieBuilder.Freeze(); - trie.Save(stream); return trie; } } + public static void GenerateTrieClass(string name, UnicodeTrie trie) + { + var stream = new MemoryStream(); + + trie.Save(stream); + + using (var fileStream = File.Create($"Generated\\{name}.trie.cs")) + using (var writer = new StreamWriter(fileStream)) + { + writer.WriteLine("using System;"); + writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); + writer.WriteLine("{"); + writer.WriteLine($" internal static class {name}Trie"); + writer.WriteLine(" {"); + writer.WriteLine(" public static ReadOnlySpan Data => new byte[]"); + writer.WriteLine(" {"); + + stream.Position = 0; + + writer.Write(" "); + + while (true) + { + var b = stream.ReadByte(); + + if(b == -1) + { + break; + } + + writer.Write(b.ToString()); + + writer.Write(','); + + if (stream.Position % 100 == 0) + { + writer.Write(Environment.NewLine); + + writer.Write(" "); + + continue; + } + } + + writer.WriteLine(" };"); + writer.WriteLine(" }"); + writer.WriteLine("}"); + } + } + public static UnicodeTrie GenerateUnicodeDataTrie(out UnicodeDataEntries dataEntries, out Dictionary unicodeData) { var generalCategoryEntries = @@ -105,10 +157,12 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting LineBreakClasses = lineBreakClassEntries }; + var trie = unicodeDataTrieBuilder.Freeze(); + + GenerateTrieClass("UnicodeData", trie); + using (var stream = File.Create("Generated\\UnicodeData.trie")) { - var trie = unicodeDataTrieBuilder.Freeze(); - trie.Save(stream); return trie; diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs index 1323ddfbd1..10da018eeb 100644 --- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs +++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs @@ -102,7 +102,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting using (var stream = typeof(UnicodeEnumsGenerator).Assembly.GetManifestResourceStream( - "Avalonia.Visuals.UnitTests.Media.TextFormatting.BreakPairTable.txt")) + "Avalonia.Base.UnitTests.Media.TextFormatting.BreakPairTable.txt")) using (var reader = new StreamReader(stream)) { while (!reader.EndOfStream) diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index 01afe85b8b..879de9fca5 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -349,6 +349,39 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph } } + [Fact] + public void MirrorTransform_For_Control_With_RenderTransform_Should_Be_Correct() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border border; + var tree = new TestRoot + { + Width = 400, + Height = 200, + Child = border = new Border + { + HorizontalAlignment = HorizontalAlignment.Left, + Background = Brushes.Red, + Width = 100, + RenderTransform = new ScaleTransform(0.5, 1), + FlowDirection = FlowDirection.RightToLeft + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var expectedTransform = new Matrix(-1, 0, 0, 1, 100, 0) * Matrix.CreateScale(0.5, 1) * Matrix.CreateTranslation(25, 0); + var borderNode = scene.FindNode(border); + Assert.Equal(expectedTransform, borderNode.Transform); + } + } + [Fact] public void Should_Update_Border_Background_Node() { @@ -805,6 +838,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph Canvas canvas; var tree = new TestRoot { + ClientSize = new Size(100, 100), Child = decorator = new Decorator { Margin = new Thickness(0, 10, 0, 0), diff --git a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs index ed4c78aa3e..c684466200 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs @@ -150,13 +150,43 @@ namespace Avalonia.Base.UnitTests.Styling Assert.Equal(BindingPriority.StyleTrigger, control.GetDiagnostic(TextBlock.TagProperty).Priority); } - private IBinding CreateMockBinding(AvaloniaProperty property) + [Fact] + public void Disposing_Setter_Should_Preserve_LocalValue() { - var subject = new Subject(); - var descriptor = InstancedBinding.OneWay(subject); - var binding = Mock.Of(x => - x.Initiate(It.IsAny(), property, null, false) == descriptor); - return binding; + var control = new Canvas(); + var setter = new Setter(TextBlock.TagProperty, "foo"); + + var instance = setter.Instance(control); + instance.Start(true); + instance.Activate(); + + control.Tag = "bar"; + + instance.Dispose(); + + Assert.Equal("bar", control.Tag); + } + + [Fact] + public void Disposing_Binding_Setter_Should_Preserve_LocalValue() + { + var control = new Canvas(); + var source = new { Foo = "foo" }; + var setter = new Setter(TextBlock.TagProperty, new Binding + { + Source = source, + Path = nameof(source.Foo), + }); + + var instance = setter.Instance(control); + instance.Start(true); + instance.Activate(); + + control.Tag = "bar"; + + instance.Dispose(); + + Assert.Equal("bar", control.Tag); } private class TestConverter : IValueConverter diff --git a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs index 3493dd0f53..51b52d6130 100644 --- a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using Avalonia.Controls; +using Avalonia.Threading; using Avalonia.UnitTests; using BenchmarkDotNet.Attributes; @@ -37,6 +38,21 @@ namespace Avalonia.Benchmarks.Layout _root.Child = calendar; _root.LayoutManager.ExecuteLayoutPass(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + } + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public void CreateCalendarWithLoaded() + { + using var subscription = Control.LoadedEvent.AddClassHandler((c, s) => { }); + + var calendar = new Calendar(); + + _root.Child = calendar; + + _root.LayoutManager.ExecuteLayoutPass(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); } [Benchmark] @@ -48,6 +64,7 @@ namespace Avalonia.Benchmarks.Layout _root.Child = button; _root.LayoutManager.ExecuteLayoutPass(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); } [Benchmark] @@ -59,6 +76,7 @@ namespace Avalonia.Benchmarks.Layout _root.Child = textBox; _root.LayoutManager.ExecuteLayoutPass(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); } public void Dispose() diff --git a/tests/Avalonia.Controls.UnitTests/BorderTests.cs b/tests/Avalonia.Controls.UnitTests/BorderTests.cs index ab33eaaff9..7af7d1cee2 100644 --- a/tests/Avalonia.Controls.UnitTests/BorderTests.cs +++ b/tests/Avalonia.Controls.UnitTests/BorderTests.cs @@ -1,6 +1,8 @@ +using Avalonia.Layout; using Avalonia.Media; using Avalonia.Rendering; using Avalonia.UnitTests; +using Avalonia.VisualTree; using Moq; using Xunit; @@ -60,5 +62,68 @@ namespace Avalonia.Controls.UnitTests renderer.Verify(x => x.AddDirty(target), Times.Once); } + + public class UseLayoutRounding + { + [Fact] + public void Measure_Rounds_Padding() + { + var target = new Border + { + Padding = new Thickness(1), + Child = new Canvas + { + Width = 101, + Height = 101, + } + }; + + var root = CreatedRoot(1.5, target); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + // - 1 pixel padding is rounded up to 1.3333; for both sides it is 2.6666 + // - Size of 101 gets rounded up to 101.3333 + // - Desired size = 101.3333 + 2.6666 = 104 + Assert.Equal(new Size(104, 104), target.DesiredSize); + } + + [Fact] + public void Measure_Rounds_BorderThickness() + { + var target = new Border + { + BorderThickness = new Thickness(1), + Child = new Canvas + { + Width = 101, + Height = 101, + } + }; + + var root = CreatedRoot(1.5, target); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + // - 1 pixel border thickness is rounded up to 1.3333; for both sides it is 2.6666 + // - Size of 101 gets rounded up to 101.3333 + // - Desired size = 101.3333 + 2.6666 = 104 + Assert.Equal(new Size(104, 104), target.DesiredSize); + } + + private static TestRoot CreatedRoot( + double scaling, + Control child, + Size? constraint = null) + { + return new TestRoot + { + LayoutScaling = scaling, + UseLayoutRounding = true, + Child = child, + ClientSize = constraint ?? new Size(1000, 1000), + }; + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 2e3623cc3c..42bdffd908 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -150,7 +150,7 @@ namespace Avalonia.Controls.UnitTests target.Click += (s, e) => clicked = true; - RaisePointerEnter(target); + RaisePointerEntered(target); RaisePointerMove(target, pt); RaisePointerPressed(target, 1, MouseButton.Left, pt); @@ -182,10 +182,10 @@ namespace Avalonia.Controls.UnitTests target.Click += (s, e) => clicked = true; - RaisePointerEnter(target); + RaisePointerEntered(target); RaisePointerMove(target, new Point(50,50)); RaisePointerPressed(target, 1, MouseButton.Left, new Point(50, 50)); - RaisePointerLeave(target); + RaisePointerExited(target); Assert.Equal(_helper.Captured, target); @@ -224,7 +224,7 @@ namespace Avalonia.Controls.UnitTests target.Click += (s, e) => clicked = true; - RaisePointerEnter(target); + RaisePointerEntered(target); RaisePointerMove(target, pt); RaisePointerPressed(target, 1, MouseButton.Left, pt); @@ -422,12 +422,12 @@ namespace Avalonia.Controls.UnitTests _helper.Up(button, mouseButton, pt); } - private void RaisePointerEnter(Button button) + private void RaisePointerEntered(Button button) { _helper.Enter(button); } - private void RaisePointerLeave(Button button) + private void RaisePointerExited(Button button) { _helper.Leave(button); } diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 98695fe88e..aa32af7e51 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -8,7 +8,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Media; -using Avalonia.Threading; +using Avalonia.VisualTree; using Avalonia.UnitTests; using Xunit; @@ -336,5 +336,104 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, count); } } + + [Fact] + public void FlowDirection_Of_RectangleContent_Shuold_Be_LeftToRight() + { + var items = new[] + { + new ComboBoxItem() + { + Content = new Control() + } + }; + var target = new ComboBox + { + FlowDirection = FlowDirection.RightToLeft, + Items = items, + Template = GetTemplate() + }; + + var root = new TestRoot(target); + target.ApplyTemplate(); + target.SelectedIndex = 0; + + var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; + + Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection); + } + + [Fact] + public void FlowDirection_Of_RectangleContent_Updated_After_InvalidateMirrorTransform() + { + var parentContent = new Decorator() + { + Child = new Control() + }; + var items = new[] + { + new ComboBoxItem() + { + Content = parentContent.Child + } + }; + var target = new ComboBox + { + Items = items, + Template = GetTemplate() + }; + + var root = new TestRoot(target); + target.ApplyTemplate(); + target.SelectedIndex = 0; + + var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; + Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection); + + parentContent.FlowDirection = FlowDirection.RightToLeft; + target.FlowDirection = FlowDirection.RightToLeft; + + Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection); + } + + [Fact] + public void FlowDirection_Of_RectangleContent_Updated_After_OpenPopup() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parentContent = new Decorator() + { + Child = new Control() + }; + var items = new[] + { + new ComboBoxItem() + { + Content = parentContent.Child + } + }; + var target = new ComboBox + { + FlowDirection = FlowDirection.RightToLeft, + Items = items, + Template = GetTemplate() + }; + + var root = new TestRoot(target); + target.ApplyTemplate(); + target.SelectedIndex = 0; + + var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle; + Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection); + + parentContent.FlowDirection = FlowDirection.RightToLeft; + + var popup = target.GetVisualDescendants().OfType().First(); + popup.PlacementTarget = new Window(); + popup.Open(); + + Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/DecoratorTests.cs b/tests/Avalonia.Controls.UnitTests/DecoratorTests.cs index 65749efbf9..fe58cd4c7f 100644 --- a/tests/Avalonia.Controls.UnitTests/DecoratorTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DecoratorTests.cs @@ -1,6 +1,7 @@ using System.Collections.Specialized; using System.Linq; using Avalonia.LogicalTree; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Controls.UnitTests @@ -116,5 +117,45 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Size(16, 16), target.DesiredSize); } + + public class UseLayoutRounding + { + [Fact] + public void Measure_Rounds_Padding() + { + var target = new Decorator + { + Padding = new Thickness(1), + Child = new Canvas + { + Width = 101, + Height = 101, + } + }; + + var root = CreatedRoot(1.5, target); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + // - 1 pixel padding is rounded up to 1.3333; for both sides it is 2.6666 + // - Size of 101 gets rounded up to 101.3333 + // - Desired size = 101.3333 + 2.6666 = 104 + Assert.Equal(new Size(104, 104), target.DesiredSize); + } + + private static TestRoot CreatedRoot( + double scaling, + Control child, + Size? constraint = null) + { + return new TestRoot + { + LayoutScaling = scaling, + UseLayoutRounding = true, + Child = child, + ClientSize = constraint ?? new Size(1000, 1000), + }; + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs b/tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs new file mode 100644 index 0000000000..6c43103ecb --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/FlowDirectionTests.cs @@ -0,0 +1,57 @@ +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class FlowDirectionTests + { + [Fact] + public void HasMirrorTransform_Should_Be_True() + { + var target = new Control + { + FlowDirection = FlowDirection.RightToLeft, + }; + + Assert.True(target.HasMirrorTransform); + } + + [Fact] + public void HasMirrorTransform_Of_LTR_Children_Should_Be_True_For_RTL_Parent() + { + Control child; + var target = new Decorator + { + FlowDirection = FlowDirection.RightToLeft, + Child = child = new Control() + }; + + child.FlowDirection = FlowDirection.LeftToRight; + + Assert.True(target.HasMirrorTransform); + Assert.True(child.HasMirrorTransform); + } + + [Fact] + public void HasMirrorTransform_Of_Children_Is_Updated_After_Parent_Changeed() + { + Control child; + var target = new Decorator + { + FlowDirection = FlowDirection.LeftToRight, + Child = child = new Control() + { + FlowDirection = FlowDirection.LeftToRight, + } + }; + + Assert.False(target.HasMirrorTransform); + Assert.False(child.HasMirrorTransform); + + target.FlowDirection = FlowDirection.RightToLeft; + + Assert.True(target.HasMirrorTransform); + Assert.True(child.HasMirrorTransform); + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index af54be61f7..d1fa522206 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -179,34 +179,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int() - { - using (Start()) - { - var source = new Class1(); - var target = new MaskedTextBox - { - DataContext = source, - Template = CreateTemplate(), - }; - - target.ApplyTemplate(); - target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay)); - - Assert.Equal("0", target.Text); - - target.CaretIndex = 1; - target.RaiseEvent(new TextInputEventArgs - { - RoutedEvent = InputElement.TextInputEvent, - Text = "2", - }); - - Assert.Equal("02", target.Text); - } - } - [Fact] public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection() { diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index d22a67f389..192734581d 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -174,7 +174,7 @@ namespace Avalonia.Controls.UnitTests.Platform } [Fact] - public void PointerEnter_Opens_Item_When_Old_Item_Is_Open() + public void PointerEntered_Opens_Item_When_Old_Item_Is_Open() { var target = new DefaultMenuInteractionHandler(false); var menu = new Mock(); @@ -187,11 +187,11 @@ namespace Avalonia.Controls.UnitTests.Platform x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu.Object); - var e = CreateArgs(MenuItem.PointerEnterItemEvent, nextItem); + var e = CreateArgs(MenuItem.PointerEnteredItemEvent, nextItem); menu.SetupGet(x => x.SelectedItem).Returns(item); - target.PointerEnter(nextItem, e); + target.PointerEntered(nextItem, e); Mock.Get(item).Verify(x => x.Close()); menu.VerifySet(x => x.SelectedItem = nextItem); @@ -202,31 +202,31 @@ namespace Avalonia.Controls.UnitTests.Platform } [Fact] - public void PointerLeave_Deselects_Item_When_Menu_Not_Open() + public void PointerExited_Deselects_Item_When_Menu_Not_Open() { var target = new DefaultMenuInteractionHandler(false); var menu = new Mock(); var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu.Object); - var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item); + var e = CreateArgs(MenuItem.PointerExitedItemEvent, item); menu.SetupGet(x => x.SelectedItem).Returns(item); - target.PointerLeave(item, e); + target.PointerExited(item, e); menu.VerifySet(x => x.SelectedItem = null); Assert.False(e.Handled); } [Fact] - public void PointerLeave_Doesnt_Deselect_Item_When_Menu_Open() + public void PointerExited_Doesnt_Deselect_Item_When_Menu_Open() { var target = new DefaultMenuInteractionHandler(false); var menu = new Mock(); var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu.Object); - var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item); + var e = CreateArgs(MenuItem.PointerExitedItemEvent, item); menu.SetupGet(x => x.IsOpen).Returns(true); menu.SetupGet(x => x.SelectedItem).Returns(item); - target.PointerLeave(item, e); + target.PointerExited(item, e); menu.VerifySet(x => x.SelectedItem = null, Times.Never); Assert.False(e.Handled); @@ -382,31 +382,31 @@ namespace Avalonia.Controls.UnitTests.Platform } [Fact] - public void PointerEnter_Selects_Item() + public void PointerEntered_Selects_Item() { var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); - var e = CreateArgs(MenuItem.PointerEnterItemEvent, item); + var e = CreateArgs(MenuItem.PointerEnteredItemEvent, item); - target.PointerEnter(item, e); + target.PointerEntered(item, e); Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item); Assert.False(e.Handled); } [Fact] - public void PointerEnter_Opens_Submenu_After_Delay() + public void PointerEntered_Opens_Submenu_After_Delay() { var timer = new TestTimer(); var target = new DefaultMenuInteractionHandler(false, null, timer.RunOnce); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true); - var e = CreateArgs(MenuItem.PointerEnterItemEvent, item); + var e = CreateArgs(MenuItem.PointerEnteredItemEvent, item); - target.PointerEnter(item, e); + target.PointerEntered(item, e); Mock.Get(item).Verify(x => x.Open(), Times.Never); timer.Pulse(); @@ -416,7 +416,7 @@ namespace Avalonia.Controls.UnitTests.Platform } [Fact] - public void PointerEnter_Closes_Sibling_Submenu_After_Delay() + public void PointerEntered_Closes_Sibling_Submenu_After_Delay() { var timer = new TestTimer(); var target = new DefaultMenuInteractionHandler(false, null, timer.RunOnce); @@ -424,11 +424,11 @@ namespace Avalonia.Controls.UnitTests.Platform var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); var sibling = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsSubMenuOpen == true); - var e = CreateArgs(MenuItem.PointerEnterItemEvent, item); + var e = CreateArgs(MenuItem.PointerEnteredItemEvent, item); Mock.Get(parentItem).SetupGet(x => x.SubItems).Returns(new[] { item, sibling }); - target.PointerEnter(item, e); + target.PointerEntered(item, e); Mock.Get(sibling).Verify(x => x.Close(), Times.Never); timer.Pulse(); @@ -438,48 +438,48 @@ namespace Avalonia.Controls.UnitTests.Platform } [Fact] - public void PointerLeave_Deselects_Item() + public void PointerExited_Deselects_Item() { var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); - var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item); + var e = CreateArgs(MenuItem.PointerExitedItemEvent, item); Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(item); - target.PointerLeave(item, e); + target.PointerExited(item, e); Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null); Assert.False(e.Handled); } [Fact] - public void PointerLeave_Doesnt_Deselect_Sibling() + public void PointerExited_Doesnt_Deselect_Sibling() { var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); var sibling = Mock.Of(x => x.Parent == parentItem); - var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item); + var e = CreateArgs(MenuItem.PointerExitedItemEvent, item); Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(sibling); - target.PointerLeave(item, e); + target.PointerExited(item, e); Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null, Times.Never); Assert.False(e.Handled); } [Fact] - public void PointerLeave_Doesnt_Deselect_Item_If_Pointer_Over_Submenu() + public void PointerExited_Doesnt_Deselect_Item_If_Pointer_Over_Submenu() { var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true); - var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item); + var e = CreateArgs(MenuItem.PointerExitedItemEvent, item); - target.PointerLeave(item, e); + target.PointerExited(item, e); Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null, Times.Never); Assert.False(e.Handled); @@ -510,11 +510,11 @@ namespace Avalonia.Controls.UnitTests.Platform var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true); var childItem = Mock.Of(x => x.Parent == item); - var enter = CreateArgs(MenuItem.PointerEnterItemEvent, item); - var leave = CreateArgs(MenuItem.PointerLeaveItemEvent, item); + var enter = CreateArgs(MenuItem.PointerEnteredItemEvent, item); + var leave = CreateArgs(MenuItem.PointerExitedItemEvent, item); // Pointer enters item; item is selected. - target.PointerEnter(item, enter); + target.PointerEntered(item, enter); Assert.True(timer.ActionIsQueued); Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item); Mock.Get(parentItem).Invocations.Clear(); @@ -526,13 +526,13 @@ namespace Avalonia.Controls.UnitTests.Platform Mock.Get(item).Invocations.Clear(); // Pointer briefly exits item, but submenu remains open. - target.PointerLeave(item, leave); + target.PointerExited(item, leave); Mock.Get(item).Verify(x => x.Close(), Times.Never); Mock.Get(item).Invocations.Clear(); // Pointer enters child item; is selected. enter.Source = childItem; - target.PointerEnter(childItem, enter); + target.PointerEntered(childItem, enter); Mock.Get(item).VerifySet(x => x.SelectedItem = childItem); Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item); Mock.Get(item).Invocations.Clear(); diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs index ed44fbfc32..e82050528f 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs @@ -1,5 +1,6 @@ using Avalonia.Controls.Presenters; using Avalonia.Layout; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Controls.UnitTests.Presenters @@ -232,5 +233,68 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Equal(new Rect(32, 32, 0, 0), content.Bounds); } + + public class UseLayoutRounding + { + [Fact] + public void Measure_Rounds_Padding() + { + var target = new ContentPresenter + { + Padding = new Thickness(1), + Content = new Canvas + { + Width = 101, + Height = 101, + } + }; + + var root = CreatedRoot(1.5, target); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + // - 1 pixel padding is rounded up to 1.3333; for both sides it is 2.6666 + // - Size of 101 gets rounded up to 101.3333 + // - Desired size = 101.3333 + 2.6666 = 104 + Assert.Equal(new Size(104, 104), target.DesiredSize); + } + + [Fact] + public void Measure_Rounds_BorderThickness() + { + var target = new ContentPresenter + { + BorderThickness = new Thickness(1), + Content = new Canvas + { + Width = 101, + Height = 101, + } + }; + + var root = CreatedRoot(1.5, target); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + // - 1 pixel border thickness is rounded up to 1.3333; for both sides it is 2.6666 + // - Size of 101 gets rounded up to 101.3333 + // - Desired size = 101.3333 + 2.6666 = 104 + Assert.Equal(new Size(104, 104), target.DesiredSize); + } + + private static TestRoot CreatedRoot( + double scaling, + Control child, + Size? constraint = null) + { + return new TestRoot + { + LayoutScaling = scaling, + UseLayoutRounding = true, + Child = child, + ClientSize = constraint ?? new Size(1000, 1000), + }; + } + } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs index 59276a94d0..f4001a8ca1 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.Measure(new Size(100, 100)); target.Arrange(new Rect(0, 0, 100, 100)); - Assert.Equal(new Rect(33, 0, 33, 12), thumb.Bounds); + Assert.Equal(new Rect(33, 0, 34, 12), thumb.Bounds); } [Fact] @@ -92,7 +92,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.Measure(new Size(100, 100)); target.Arrange(new Rect(0, 0, 100, 100)); - Assert.Equal(new Rect(0, 33, 12, 33), thumb.Bounds); + Assert.Equal(new Rect(0, 33, 12, 34), thumb.Bounds); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index f15da8e0c5..23a330c96f 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -180,34 +180,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int() - { - using (UnitTestApplication.Start(Services)) - { - var source = new Class1(); - var target = new TextBox - { - DataContext = source, - Template = CreateTemplate(), - }; - - target.ApplyTemplate(); - target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay)); - - Assert.Equal("0", target.Text); - - target.CaretIndex = 1; - target.RaiseEvent(new TextInputEventArgs - { - RoutedEvent = InputElement.TextInputEvent, - Text = "2", - }); - - Assert.Equal("02", target.Text); - } - } - [Fact] public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection() { diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index 8d9a4aa599..e4d177f7ca 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -12,6 +12,7 @@ using Moq; using Xunit; using Avalonia.Input.Raw; using Factory = System.Func, Avalonia.Controls.Window, Avalonia.AvaloniaObject>; +using Avalonia.Threading; namespace Avalonia.Controls.UnitTests.Utils { @@ -60,115 +61,6 @@ namespace Avalonia.Controls.UnitTests.Utils } } - [Fact] - public void HotKeyManager_Should_Release_Reference_When_Control_Detached() - { - using (AvaloniaLocator.EnterScope()) - { - var styler = new Mock(); - - AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()) - .Bind().ToConstant(styler.Object); - - var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control); - - WeakReference reference = null; - - var tl = new Window(); - - new Action(() => - { - var button = new Button(); - reference = new WeakReference(button, true); - tl.Content = button; - tl.Template = CreateWindowTemplate(); - tl.ApplyTemplate(); - tl.Presenter.ApplyTemplate(); - HotKeyManager.SetHotKey(button, gesture1); - - // Detach the button from the logical tree, so there is no reference to it - tl.Content = null; - tl.ApplyTemplate(); - })(); - - - // The button should be collected since it's detached from the listbox - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - Assert.Null(reference?.Target); - } - } - - [Fact] - public void HotKeyManager_Should_Release_Reference_When_Control_In_Item_Template_Detached() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var styler = new Mock(); - - AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()) - .Bind().ToConstant(styler.Object); - - var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control); - - var weakReferences = new List(); - var tl = new Window { SizeToContent = SizeToContent.WidthAndHeight, IsVisible = true }; - var lm = tl.LayoutManager; - - var keyGestures = new AvaloniaList { gesture1 }; - var listBox = new ListBox - { - Width = 100, - Height = 100, - VirtualizationMode = ItemVirtualizationMode.None, - // Create a button with binding to the KeyGesture in the template and add it to references list - ItemTemplate = new FuncDataTemplate(typeof(KeyGesture), (o, scope) => - { - var keyGesture = o as KeyGesture; - var button = new Button - { - DataContext = keyGesture, [!Button.HotKeyProperty] = new Binding("") - }; - weakReferences.Add(new WeakReference(button, true)); - return button; - }) - }; - // Add the listbox and render it - tl.Content = listBox; - lm.ExecuteInitialLayoutPass(); - listBox.Items = keyGestures; - lm.ExecuteLayoutPass(); - - // Let the button detach when clearing the source items - keyGestures.Clear(); - lm.ExecuteLayoutPass(); - - // Add it again to double check,and render - keyGestures.Add(gesture1); - lm.ExecuteLayoutPass(); - - keyGestures.Clear(); - lm.ExecuteLayoutPass(); - - // The button should be collected since it's detached from the listbox - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - Assert.True(weakReferences.Count > 0); - foreach (var weakReference in weakReferences) - { - Assert.Null(weakReference.Target); - } - } - } - [Theory] [MemberData(nameof(ElementsFactory), parameters: true)] public void HotKeyManager_Should_Use_CommandParameter(string factoryName, Factory factory) diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index bb520c16aa..8c05f2a0a7 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -3,7 +3,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Disposables; + +using Avalonia.Collections; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; using Avalonia.Data; @@ -67,6 +70,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } @@ -100,6 +106,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } @@ -141,6 +150,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } @@ -179,6 +191,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); dotMemory.Check(memory => @@ -216,6 +231,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } @@ -261,6 +279,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); dotMemory.Check(memory => @@ -351,6 +372,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } @@ -384,6 +408,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } @@ -421,6 +448,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } @@ -496,6 +526,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } @@ -536,9 +569,12 @@ namespace Avalonia.LeakTests initialMenuCount = memory.GetObjects(where => where.Type.Is()).ObjectsCount; initialMenuItemCount = memory.GetObjects(where => where.Type.Is()).ObjectsCount; }); - + AttachShowAndDetachContextMenu(window); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + Mock.Get(window.PlatformImpl).Invocations.Clear(); dotMemory.Check(memory => Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -580,10 +616,13 @@ namespace Avalonia.LeakTests initialMenuCount = memory.GetObjects(where => where.Type.Is()).ObjectsCount; initialMenuItemCount = memory.GetObjects(where => where.Type.Is()).ObjectsCount; }); - + BuildAndShowContextMenu(window); BuildAndShowContextMenu(window); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + Mock.Get(window.PlatformImpl).Invocations.Clear(); dotMemory.Check(memory => Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -623,6 +662,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -657,6 +699,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } @@ -725,14 +770,128 @@ namespace Avalonia.LeakTests Assert.Empty(lb.ItemContainerGenerator.Containers); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } } + [Fact] + public void HotKeyManager_Should_Release_Reference_When_Control_Detached() + { + using (Start()) + { + Func run = () => + { + var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control); + var tl = new Window + { + Content = new ItemsRepeater(), + }; + + tl.Show(); + + var button = new Button(); + tl.Content = button; + tl.Template = CreateWindowTemplate(); + tl.ApplyTemplate(); + tl.Presenter.ApplyTemplate(); + HotKeyManager.SetHotKey(button, gesture1); + + // Detach the button from the logical tree, so there is no reference to it + tl.Content = null; + tl.ApplyTemplate(); + + return tl; + }; + + var result = run(); + + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is