Browse Source

Merge branch 'master' into nonrx-expressionnode

pull/1694/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
e2de92f573
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .gitignore
  2. 5
      build/System.Memory.props
  3. 2
      packages.cake
  4. 18
      src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
  5. 1
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  6. 659
      src/Avalonia.Visuals/Media/PathMarkupParser.cs
  7. 3
      src/Windows/Avalonia.Win32/ClipboardImpl.cs
  8. 24
      tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
  9. 2
      tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs

8
.gitignore

@ -176,5 +176,9 @@ nuget
Avalonia.XBuild.sln
project.lock.json
.idea/*
**/obj-Skia/*
**/obj-Direct2D1/*
##################
## BenchmarkDotNet
##################
BenchmarkDotNet.Artifacts/

5
build/System.Memory.props

@ -0,0 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.0" />
</ItemGroup>
</Project>

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

18
src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs

@ -202,6 +202,7 @@ namespace Avalonia.Styling
{
readonly IList<string> _match;
IAvaloniaReadOnlyList<string> _classes;
bool _value;
public ClassObserver(IAvaloniaReadOnlyList<string> classes, IList<string> 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<bool> 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;
}
}
}

1
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -8,4 +8,5 @@
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\System.Memory.props" />
</Project>

659
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
/// </summary>
public class PathMarkupParser : IDisposable
{
private static readonly string s_separatorPattern;
private static readonly Dictionary<char, Command> s_commands =
new Dictionary<char, Command>
{
@ -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();
}
/// <summary>
/// Initializes a new instance of the <see cref="PathMarkupParser"/> class.
/// </summary>
@ -76,18 +67,6 @@ namespace Avalonia.Media
Close
}
/// <summary>
/// Parses the specified path data and writes the result to the geometryContext of this instance.
/// </summary>
/// <param name="pathData">The path data.</param>
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<CommandToken> 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<CommandToken> commandTokens)
/// <summary>
/// Parses the specified path data and writes the result to the geometryContext of this instance.
/// </summary>
/// <param name="pathData">The path data.</param>
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<char> 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<char> 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<char> 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<char> 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<char> 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<char> 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<char> 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<char> 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<char> 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<char> 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<char> span)
{
private const string ArgumentExpression = @"-?[0-9]*\.?\d+";
private CommandToken(Command command, bool isRelative, IEnumerable<string> arguments)
{
Command = command;
IsRelative = isRelative;
Arguments = new List<string>(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<char> remaining, out ReadOnlySpan<char> argument)
{
remaining = SkipWhitespace(remaining);
if (remaining.IsEmpty)
{
get
{
if (CurrentPosition == 0 && Arguments.Count > 0)
{
return true;
}
return CurrentPosition < Arguments.Count - 1;
}
argument = ReadOnlySpan<char>.Empty;
return false;
}
private int CurrentPosition { get; set; }
private List<string> 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<string>();
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<char>.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<char> ReadSeparator(ReadOnlySpan<char> 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<char> SkipWhitespace(ReadOnlySpan<char> 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<char> 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<char> 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<char> 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<char> 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<char> 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<char> 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;
}
}
}

3
src/Windows/Avalonia.Win32/ClipboardImpl.cs

@ -57,6 +57,9 @@ namespace Avalonia.Win32
}
await OpenClipboard();
UnmanagedMethods.EmptyClipboard();
try
{
var hGlobal = Marshal.StringToHGlobalUni(text);

24
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<bool>();
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
{
}

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

Loading…
Cancel
Save