diff --git a/.gitignore b/.gitignore
index a9a8fd36b4..583a2b8a2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -176,5 +176,9 @@ nuget
Avalonia.XBuild.sln
project.lock.json
.idea/*
-**/obj-Skia/*
-**/obj-Direct2D1/*
+
+
+##################
+## BenchmarkDotNet
+##################
+BenchmarkDotNet.Artifacts/
diff --git a/build/System.Memory.props b/build/System.Memory.props
new file mode 100644
index 0000000000..f3253f8882
--- /dev/null
+++ b/build/System.Memory.props
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/packages.cake b/packages.cake
index 7e7e722c82..79147b5f99 100644
--- a/packages.cake
+++ b/packages.cake
@@ -253,7 +253,7 @@ public class Packages
}
.Deps(new string[]{null, "netcoreapp2.0"},
"System.ValueTuple", "System.ComponentModel.TypeConverter", "System.ComponentModel.Primitives",
- "System.Runtime.Serialization.Primitives", "System.Xml.XmlDocument", "System.Xml.ReaderWriter")
+ "System.Runtime.Serialization.Primitives", "System.Xml.XmlDocument", "System.Xml.ReaderWriter", "System.Memory")
.ToArray(),
Files = coreLibrariesNuSpecContent
.Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
diff --git a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
index 93d4e14f27..94c0b75c6e 100644
--- a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
+++ b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
@@ -202,6 +202,7 @@ namespace Avalonia.Styling
{
readonly IList _match;
IAvaloniaReadOnlyList _classes;
+ bool _value;
public ClassObserver(IAvaloniaReadOnlyList classes, IList match)
{
@@ -210,18 +211,29 @@ namespace Avalonia.Styling
}
protected override void Deinitialize() => _classes.CollectionChanged -= ClassesChanged;
- protected override void Initialize() => _classes.CollectionChanged += ClassesChanged;
+
+ protected override void Initialize()
+ {
+ _value = GetResult();
+ _classes.CollectionChanged += ClassesChanged;
+ }
protected override void Subscribed(IObserver observer, bool first)
{
- observer.OnNext(GetResult());
+ observer.OnNext(_value);
}
private void ClassesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Move)
{
- PublishNext(GetResult());
+ var value = GetResult();
+
+ if (value != _value)
+ {
+ PublishNext(GetResult());
+ _value = value;
+ }
}
}
diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
index 0f003a4018..c34752a3ef 100644
--- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj
+++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
@@ -8,4 +8,5 @@
+
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs
index a322d404bf..384c96a7d6 100644
--- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs
+++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs
@@ -5,9 +5,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
using Avalonia.Platform;
namespace Avalonia.Media
@@ -17,7 +14,6 @@ namespace Avalonia.Media
///
public class PathMarkupParser : IDisposable
{
- private static readonly string s_separatorPattern;
private static readonly Dictionary s_commands =
new Dictionary
{
@@ -37,14 +33,9 @@ namespace Avalonia.Media
private IGeometryContext _geometryContext;
private Point _currentPoint;
private Point? _previousControlPoint;
- private bool? _isOpen;
+ private bool _isOpen;
private bool _isDisposed;
- static PathMarkupParser()
- {
- s_separatorPattern = CreatesSeparatorPattern();
- }
-
///
/// Initializes a new instance of the class.
///
@@ -76,18 +67,6 @@ namespace Avalonia.Media
Close
}
- ///
- /// Parses the specified path data and writes the result to the geometryContext of this instance.
- ///
- /// The path data.
- public void Parse(string pathData)
- {
- var normalizedPathData = NormalizeWhiteSpaces(pathData);
- var tokens = ParseTokens(normalizedPathData);
-
- CreateGeometry(tokens);
- }
-
void IDisposable.Dispose()
{
Dispose(true);
@@ -108,66 +87,6 @@ namespace Avalonia.Media
_isDisposed = true;
}
- private static string NormalizeWhiteSpaces(string s)
- {
- int length = s.Length,
- index = 0,
- i = 0;
- var source = s.ToCharArray();
- var skip = false;
-
- for (; i < length; i++)
- {
- var c = source[i];
-
- if (char.IsWhiteSpace(c))
- {
- if (skip)
- {
- continue;
- }
-
- source[index++] = c;
-
- skip = true;
-
- continue;
- }
-
- skip = false;
-
- source[index++] = c;
- }
-
- if (char.IsWhiteSpace(source[index - 1]))
- {
- index--;
- }
-
- return char.IsWhiteSpace(source[0]) ? new string(source, 1, index) : new string(source, 0, index);
- }
-
- private static string CreatesSeparatorPattern()
- {
- var stringBuilder = new StringBuilder();
-
- foreach (var command in s_commands.Keys)
- {
- stringBuilder.Append(command);
-
- stringBuilder.Append(char.ToLower(command));
- }
-
- return @"(?=[" + stringBuilder + "])";
- }
-
- private static IEnumerable ParseTokens(string s)
- {
- var expressions = Regex.Split(s, s_separatorPattern).Where(t => !string.IsNullOrEmpty(t));
-
- return expressions.Select(CommandToken.Parse);
- }
-
private static Point MirrorControlPoint(Point controlPoint, Point center)
{
var dir = controlPoint - center;
@@ -175,76 +94,78 @@ namespace Avalonia.Media
return center + -dir;
}
- private void CreateGeometry(IEnumerable commandTokens)
+ ///
+ /// Parses the specified path data and writes the result to the geometryContext of this instance.
+ ///
+ /// The path data.
+ public void Parse(string pathData)
{
+ var span = pathData.AsSpan();
_currentPoint = new Point();
- foreach (var commandToken in commandTokens)
+ while(!span.IsEmpty)
{
- try
- {
- while (true)
- {
- switch (commandToken.Command)
- {
- case Command.None:
- break;
- case Command.FillRule:
- SetFillRule(commandToken);
- break;
- case Command.Move:
- AddMove(commandToken);
- break;
- case Command.Line:
- AddLine(commandToken);
- break;
- case Command.HorizontalLine:
- AddHorizontalLine(commandToken);
- break;
- case Command.VerticalLine:
- AddVerticalLine(commandToken);
- break;
- case Command.CubicBezierCurve:
- AddCubicBezierCurve(commandToken);
- break;
- case Command.QuadraticBezierCurve:
- AddQuadraticBezierCurve(commandToken);
- break;
- case Command.SmoothCubicBezierCurve:
- AddSmoothCubicBezierCurve(commandToken);
- break;
- case Command.SmoothQuadraticBezierCurve:
- AddSmoothQuadraticBezierCurve(commandToken);
- break;
- case Command.Arc:
- AddArc(commandToken);
- break;
- case Command.Close:
- CloseFigure();
- break;
- default:
- throw new NotSupportedException("Unsupported command");
- }
-
- if (commandToken.HasImplicitCommands)
- {
- continue;
- }
-
- break;
- }
- }
- catch (InvalidDataException)
+ if(!ReadCommand(ref span, out var command, out var relative))
{
- break;
+ return;
}
- catch (NotSupportedException)
+
+ bool initialCommand = true;
+
+ do
{
- break;
- }
+ if (!initialCommand)
+ {
+ span = ReadSeparator(span);
+ }
+
+ switch (command)
+ {
+ case Command.None:
+ break;
+ case Command.FillRule:
+ SetFillRule(ref span);
+ break;
+ case Command.Move:
+ AddMove(ref span, relative);
+ break;
+ case Command.Line:
+ AddLine(ref span, relative);
+ break;
+ case Command.HorizontalLine:
+ AddHorizontalLine(ref span, relative);
+ break;
+ case Command.VerticalLine:
+ AddVerticalLine(ref span, relative);
+ break;
+ case Command.CubicBezierCurve:
+ AddCubicBezierCurve(ref span, relative);
+ break;
+ case Command.QuadraticBezierCurve:
+ AddQuadraticBezierCurve(ref span, relative);
+ break;
+ case Command.SmoothCubicBezierCurve:
+ AddSmoothCubicBezierCurve(ref span, relative);
+ break;
+ case Command.SmoothQuadraticBezierCurve:
+ AddSmoothQuadraticBezierCurve(ref span, relative);
+ break;
+ case Command.Arc:
+ AddArc(ref span, relative);
+ break;
+ case Command.Close:
+ CloseFigure();
+ break;
+ default:
+ throw new NotSupportedException("Unsupported command");
+ }
+
+ initialCommand = false;
+ } while (PeekArgument(span));
+
}
- if (_isOpen != null)
+ if (_isOpen)
{
_geometryContext.EndFigure(false);
}
@@ -252,7 +173,7 @@ namespace Avalonia.Media
private void CreateFigure()
{
- if (_isOpen != null)
+ if (_isOpen)
{
_geometryContext.EndFigure(false);
}
@@ -262,62 +183,72 @@ namespace Avalonia.Media
_isOpen = true;
}
- private void SetFillRule(CommandToken commandToken)
+ private void SetFillRule(ref ReadOnlySpan span)
{
- var fillRule = commandToken.ReadFillRule();
+ if (!ReadArgument(ref span, out var fillRule) || fillRule.Length != 1)
+ {
+ throw new InvalidDataException("Invalid fill rule.");
+ }
+
+ FillRule rule;
- _geometryContext.SetFillRule(fillRule);
+ switch (fillRule[0])
+ {
+ case '0':
+ rule = FillRule.EvenOdd;
+ break;
+ case '1':
+ rule = FillRule.NonZero;
+ break;
+ default:
+ throw new InvalidDataException("Invalid fill rule");
+ }
+
+ _geometryContext.SetFillRule(rule);
}
private void CloseFigure()
{
- if (_isOpen == true)
+ if (_isOpen)
{
_geometryContext.EndFigure(true);
}
_previousControlPoint = null;
- _isOpen = null;
+ _isOpen = false;
}
- private void AddMove(CommandToken commandToken)
+ private void AddMove(ref ReadOnlySpan span, bool relative)
{
- var currentPoint = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ var currentPoint = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
_currentPoint = currentPoint;
CreateFigure();
- if (!commandToken.HasImplicitCommands)
+ while (PeekArgument(span))
{
- return;
- }
+ span = ReadSeparator(span);
+ AddLine(ref span, relative);
- while (commandToken.HasImplicitCommands)
- {
- AddLine(commandToken);
-
- if (commandToken.IsRelative)
+ if (!relative)
{
- continue;
+ _currentPoint = currentPoint;
+ CreateFigure();
}
-
- _currentPoint = currentPoint;
-
- CreateFigure();
}
}
- private void AddLine(CommandToken commandToken)
+ private void AddLine(ref ReadOnlySpan span, bool relative)
{
- _currentPoint = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ _currentPoint = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
- if (_isOpen == null)
+ if (!_isOpen)
{
CreateFigure();
}
@@ -325,13 +256,13 @@ namespace Avalonia.Media
_geometryContext.LineTo(_currentPoint);
}
- private void AddHorizontalLine(CommandToken commandToken)
+ private void AddHorizontalLine(ref ReadOnlySpan span, bool relative)
{
- _currentPoint = commandToken.IsRelative
- ? new Point(_currentPoint.X + commandToken.ReadDouble(), _currentPoint.Y)
- : _currentPoint.WithX(commandToken.ReadDouble());
+ _currentPoint = relative
+ ? new Point(_currentPoint.X + ReadDouble(ref span), _currentPoint.Y)
+ : _currentPoint.WithX(ReadDouble(ref span));
- if (_isOpen == null)
+ if (!_isOpen)
{
CreateFigure();
}
@@ -339,13 +270,13 @@ namespace Avalonia.Media
_geometryContext.LineTo(_currentPoint);
}
- private void AddVerticalLine(CommandToken commandToken)
+ private void AddVerticalLine(ref ReadOnlySpan span, bool relative)
{
- _currentPoint = commandToken.IsRelative
- ? new Point(_currentPoint.X, _currentPoint.Y + commandToken.ReadDouble())
- : _currentPoint.WithY(commandToken.ReadDouble());
+ _currentPoint = relative
+ ? new Point(_currentPoint.X, _currentPoint.Y + ReadDouble(ref span))
+ : _currentPoint.WithY(ReadDouble(ref span));
- if (_isOpen == null)
+ if (!_isOpen)
{
CreateFigure();
}
@@ -353,23 +284,27 @@ namespace Avalonia.Media
_geometryContext.LineTo(_currentPoint);
}
- private void AddCubicBezierCurve(CommandToken commandToken)
+ private void AddCubicBezierCurve(ref ReadOnlySpan span, bool relative)
{
- var point1 = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ var point1 = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
- var point2 = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ span = ReadSeparator(span);
+
+ var point2 = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
_previousControlPoint = point2;
- var point3 = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ span = ReadSeparator(span);
+
+ var point3 = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
- if (_isOpen == null)
+ if (!_isOpen)
{
CreateFigure();
}
@@ -379,19 +314,21 @@ namespace Avalonia.Media
_currentPoint = point3;
}
- private void AddQuadraticBezierCurve(CommandToken commandToken)
+ private void AddQuadraticBezierCurve(ref ReadOnlySpan span, bool relative)
{
- var start = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ var start = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
_previousControlPoint = start;
- var end = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ span = ReadSeparator(span);
+
+ var end = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
- if (_isOpen == null)
+ if (!_isOpen)
{
CreateFigure();
}
@@ -401,22 +338,24 @@ namespace Avalonia.Media
_currentPoint = end;
}
- private void AddSmoothCubicBezierCurve(CommandToken commandToken)
+ private void AddSmoothCubicBezierCurve(ref ReadOnlySpan span, bool relative)
{
- var point2 = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ var point2 = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
- var end = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ span = ReadSeparator(span);
+
+ var end = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
if (_previousControlPoint != null)
{
_previousControlPoint = MirrorControlPoint((Point)_previousControlPoint, _currentPoint);
}
- if (_isOpen == null)
+ if (!_isOpen)
{
CreateFigure();
}
@@ -428,18 +367,18 @@ namespace Avalonia.Media
_currentPoint = end;
}
- private void AddSmoothQuadraticBezierCurve(CommandToken commandToken)
+ private void AddSmoothQuadraticBezierCurve(ref ReadOnlySpan span, bool relative)
{
- var end = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ var end = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
if (_previousControlPoint != null)
{
_previousControlPoint = MirrorControlPoint((Point)_previousControlPoint, _currentPoint);
}
- if (_isOpen == null)
+ if (!_isOpen)
{
CreateFigure();
}
@@ -449,21 +388,27 @@ namespace Avalonia.Media
_currentPoint = end;
}
- private void AddArc(CommandToken commandToken)
+ private void AddArc(ref ReadOnlySpan span, bool relative)
{
- var size = commandToken.ReadSize();
+ var size = ReadSize(ref span);
+
+ span = ReadSeparator(span);
- var rotationAngle = commandToken.ReadDouble();
+ var rotationAngle = ReadDouble(ref span);
+ span = ReadSeparator(span);
+ var isLargeArc = ReadBool(ref span);
- var isLargeArc = commandToken.ReadBool();
+ span = ReadSeparator(span);
- var sweepDirection = commandToken.ReadBool() ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
+ var sweepDirection = ReadBool(ref span) ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
+
+ span = ReadSeparator(span);
- var end = commandToken.IsRelative
- ? commandToken.ReadRelativePoint(_currentPoint)
- : commandToken.ReadPoint();
+ var end = relative
+ ? ReadRelativePoint(ref span, _currentPoint)
+ : ReadPoint(ref span);
- if (_isOpen == null)
+ if (!_isOpen)
{
CreateFigure();
}
@@ -475,210 +420,134 @@ namespace Avalonia.Media
_previousControlPoint = null;
}
- private class CommandToken
+ private static bool PeekArgument(ReadOnlySpan span)
{
- private const string ArgumentExpression = @"-?[0-9]*\.?\d+";
-
- private CommandToken(Command command, bool isRelative, IEnumerable arguments)
- {
- Command = command;
-
- IsRelative = isRelative;
-
- Arguments = new List(arguments);
- }
-
- public Command Command { get; }
+ span = SkipWhitespace(span);
- public bool IsRelative { get; }
+ return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || char.IsDigit(span[0]));
+ }
- public bool HasImplicitCommands
+ private static bool ReadArgument(ref ReadOnlySpan remaining, out ReadOnlySpan argument)
+ {
+ remaining = SkipWhitespace(remaining);
+ if (remaining.IsEmpty)
{
- get
- {
- if (CurrentPosition == 0 && Arguments.Count > 0)
- {
- return true;
- }
-
- return CurrentPosition < Arguments.Count - 1;
- }
+ argument = ReadOnlySpan.Empty;
+ return false;
}
- private int CurrentPosition { get; set; }
-
- private List Arguments { get; }
-
- public static CommandToken Parse(string s)
- {
- using (var reader = new StringReader(s))
- {
- var command = Command.None;
-
- var isRelative = false;
-
- if (!ReadCommand(reader, ref command, ref isRelative))
- {
- throw new InvalidDataException("No path command declared.");
- }
-
- var commandArguments = reader.ReadToEnd();
-
- var argumentMatches = Regex.Matches(commandArguments, ArgumentExpression);
-
- var arguments = new List();
-
- foreach (Match match in argumentMatches)
- {
- arguments.Add(match.Value);
- }
-
- return new CommandToken(command, isRelative, arguments);
- }
- }
-
- public FillRule ReadFillRule()
+ var valid = false;
+ int i = 0;
+ if (remaining[i] == '-')
{
- if (CurrentPosition == Arguments.Count)
- {
- throw new InvalidDataException("Invalid fill rule");
- }
-
- var value = Arguments[CurrentPosition];
-
- CurrentPosition++;
-
- switch (value)
- {
- case "0":
- {
- return FillRule.EvenOdd;
- }
-
- case "1":
- {
- return FillRule.NonZero;
- }
-
- default:
- throw new InvalidDataException("Invalid fill rule");
- }
+ i++;
}
+ for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true;
- public bool ReadBool()
+ if (i < remaining.Length && remaining[i] == '.')
{
- if (CurrentPosition == Arguments.Count)
- {
- throw new InvalidDataException("Invalid boolean value");
- }
-
- var value = Arguments[CurrentPosition];
-
- CurrentPosition++;
-
- switch (value)
- {
- case "1":
- {
- return true;
- }
-
- case "0":
- {
- return false;
- }
-
- default:
- throw new InvalidDataException("Invalid boolean value");
- }
+ valid = false;
+ i++;
}
+ for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true;
- public double ReadDouble()
+ if (!valid)
{
- if (CurrentPosition == Arguments.Count)
- {
- throw new InvalidDataException("Invalid double value");
- }
-
- var value = Arguments[CurrentPosition];
-
- CurrentPosition++;
-
- return double.Parse(value, CultureInfo.InvariantCulture);
+ argument = ReadOnlySpan.Empty;
+ return false;
}
+ argument = remaining.Slice(0, i);
+ remaining = remaining.Slice(i);
+ return true;
+ }
- public Size ReadSize()
- {
- var width = ReadDouble();
-
- var height = ReadDouble();
-
- return new Size(width, height);
- }
- public Point ReadPoint()
+ private static ReadOnlySpan ReadSeparator(ReadOnlySpan span)
+ {
+ span = SkipWhitespace(span);
+ if (!span.IsEmpty && span[0] == ',')
{
- var x = ReadDouble();
-
- var y = ReadDouble();
-
- return new Point(x, y);
+ span = span.Slice(1);
}
+ return span;
+ }
- public Point ReadRelativePoint(Point origin)
- {
- var x = ReadDouble();
-
- var y = ReadDouble();
-
- return new Point(origin.X + x, origin.Y + y);
- }
+ private static ReadOnlySpan SkipWhitespace(ReadOnlySpan span)
+ {
+ int i = 0;
+ for (; i < span.Length && char.IsWhiteSpace(span[i]); i++) ;
+ return span.Slice(i);
+ }
- private static bool ReadCommand(TextReader reader, ref Command command, ref bool relative)
+ private bool ReadBool(ref ReadOnlySpan span)
+ {
+ if (!ReadArgument(ref span, out var boolValue) || boolValue.Length != 1)
{
- ReadWhitespace(reader);
-
- var i = reader.Peek();
-
- if (i == -1)
- {
+ throw new InvalidDataException("Invalid bool rule.");
+ }
+
+ switch (boolValue[0])
+ {
+ case '0':
return false;
- }
+ case '1':
+ return true;
+ default:
+ throw new InvalidDataException("Invalid bool rule");
+ }
+ }
- var c = (char)i;
+ private double ReadDouble(ref ReadOnlySpan span)
+ {
+ if (!ReadArgument(ref span, out var doubleValue))
+ {
+ throw new InvalidDataException("Invalid double value");
+ }
- if (!s_commands.TryGetValue(char.ToUpperInvariant(c), out var next))
- {
- throw new InvalidDataException("Unexpected path command '" + c + "'.");
- }
+ return double.Parse(doubleValue.ToString(), CultureInfo.InvariantCulture);
+ }
- command = next;
+ private Size ReadSize(ref ReadOnlySpan span)
+ {
+ var width = ReadDouble(ref span);
+ span = ReadSeparator(span);
+ var height = ReadDouble(ref span);
+ return new Size(width, height);
+ }
- relative = char.IsLower(c);
+ private Point ReadPoint(ref ReadOnlySpan span)
+ {
+ var x = ReadDouble(ref span);
+ span = ReadSeparator(span);
+ var y = ReadDouble(ref span);
+ return new Point(x, y);
+ }
- reader.Read();
+ private Point ReadRelativePoint(ref ReadOnlySpan span, Point origin)
+ {
+ var x = ReadDouble(ref span);
+ span = ReadSeparator(span);
+ var y = ReadDouble(ref span);
+ return new Point(origin.X + x, origin.Y + y);
+ }
- return true;
+ private bool ReadCommand(ref ReadOnlySpan span, out Command command, out bool relative)
+ {
+ span = SkipWhitespace(span);
+ if (span.IsEmpty)
+ {
+ command = default;
+ relative = false;
+ return false;
}
-
- private static void ReadWhitespace(TextReader reader)
+ var c = span[0];
+ if (!s_commands.TryGetValue(char.ToUpperInvariant(c), out command))
{
- int i;
-
- while ((i = reader.Peek()) != -1)
- {
- var c = (char)i;
-
- if (char.IsWhiteSpace(c))
- {
- reader.Read();
- }
- else
- {
- break;
- }
- }
+ throw new InvalidDataException("Unexpected path command '" + c + "'.");
}
+ relative = char.IsLower(c);
+ span = span.Slice(1);
+ return true;
}
}
}
\ No newline at end of file
diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs
index 3dae8e37d9..a908c9e1e2 100644
--- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs
+++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs
@@ -57,6 +57,9 @@ namespace Avalonia.Win32
}
await OpenClipboard();
+
+ UnmanagedMethods.EmptyClipboard();
+
try
{
var hGlobal = Marshal.StringToHGlobalUni(text);
diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
index b41c21fbf4..75599925b7 100644
--- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
+++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
@@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -8,6 +9,7 @@ using Moq;
using Avalonia.Controls;
using Avalonia.Styling;
using Xunit;
+using System.Collections.Generic;
namespace Avalonia.Styling.UnitTests
{
@@ -117,6 +119,28 @@ namespace Avalonia.Styling.UnitTests
Assert.False(await activator.Take(1));
}
+ [Fact]
+ public void Only_Notifies_When_Result_Changes()
+ {
+ // Test for #1698
+ var control = new Control1
+ {
+ Classes = new Classes { "foo" },
+ };
+
+ var target = default(Selector).Class("foo");
+ var activator = target.Match(control).ObservableResult;
+ var result = new List();
+
+ using (activator.Subscribe(x => result.Add(x)))
+ {
+ control.Classes.Add("bar");
+ control.Classes.Remove("foo");
+ }
+
+ Assert.Equal(new[] { true, false }, result);
+ }
+
public class Control1 : TestControlBase
{
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs
index 35ec38789e..5074d306fd 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs
@@ -69,7 +69,7 @@ namespace Avalonia.Visuals.UnitTests.Media
using (var context = new PathGeometryContext(pathGeometry))
using (var parser = new PathMarkupParser(context))
{
- parser.Parse("F 1M0,0");
+ parser.Parse("F 1M0,0");
Assert.Equal(FillRule.NonZero, pathGeometry.FillRule);
}