Browse Source

Merge branch 'feature/ApplicationExitMode' of https://github.com/Gillibald/Avalonia into feature/ApplicationExitMode

pull/1662/head
Benedikt Schroeder 8 years ago
parent
commit
178ca30fdd
  1. 21
      src/Avalonia.Visuals/Media/PathGeometry.cs
  2. 789
      src/Avalonia.Visuals/Media/PathMarkupParser.cs
  3. 11
      src/Avalonia.Visuals/Media/StreamGeometry.cs
  4. 2
      src/Avalonia.Visuals/Media/StreamGeometryContext.cs
  5. 66
      src/Avalonia.Visuals/Platform/IGeometryContext.cs
  6. 54
      src/Avalonia.Visuals/Platform/IStreamGeometryContextImpl.cs
  7. 85
      src/Avalonia.Visuals/Platform/PathGeometryContext.cs
  8. 313
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  9. 228
      src/Windows/Avalonia.Win32/SystemDialogImpl.cs
  10. 5
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  11. 45
      tests/Avalonia.Benchmarks/Visuals/Media/PathMarkupParserTests.cs
  12. 142
      tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs

21
src/Avalonia.Visuals/Media/PathGeometry.cs

@ -5,6 +5,7 @@ using System;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Visuals.Platform;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -28,7 +29,7 @@ namespace Avalonia.Media
static PathGeometry() static PathGeometry()
{ {
FiguresProperty.Changed.AddClassHandler<PathGeometry>((s, e) => FiguresProperty.Changed.AddClassHandler<PathGeometry>((s, e) =>
s.OnFiguresChanged(e.NewValue as PathFigures)); s.OnFiguresChanged(e.NewValue as PathFigures));
} }
@ -40,6 +41,24 @@ namespace Avalonia.Media
Figures = new PathFigures(); Figures = new PathFigures();
} }
/// <summary>
/// Parses the specified path data to a <see cref="PathGeometry"/>.
/// </summary>
/// <param name="pathData">The s.</param>
/// <returns></returns>
public static new PathGeometry Parse(string pathData)
{
var pathGeometry = new PathGeometry();
using (var context = new PathGeometryContext(pathGeometry))
using (var parser = new PathMarkupParser(context))
{
parser.Parse(pathData);
}
return pathGeometry;
}
/// <summary> /// <summary>
/// Gets or sets the figures. /// Gets or sets the figures.
/// </summary> /// </summary>

789
src/Avalonia.Visuals/Media/PathMarkupParser.cs

@ -5,50 +5,61 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using Avalonia.Platform;
namespace Avalonia.Media namespace Avalonia.Media
{ {
/// <summary> /// <summary>
/// Parses a path markup string. /// Parses a path markup string.
/// </summary> /// </summary>
public class PathMarkupParser public class PathMarkupParser : IDisposable
{ {
private static readonly Dictionary<char, Command> Commands = new Dictionary<char, Command> private static readonly string s_separatorPattern;
{ private static readonly Dictionary<char, Command> s_commands =
{ 'F', Command.FillRule }, new Dictionary<char, Command>
{ 'M', Command.Move }, {
{ 'L', Command.Line }, { 'F', Command.FillRule },
{ 'H', Command.HorizontalLine }, { 'M', Command.Move },
{ 'V', Command.VerticalLine }, { 'L', Command.Line },
{ 'Q', Command.QuadraticBezierCurve }, { 'H', Command.HorizontalLine },
{ 'T', Command.SmoothQuadraticBezierCurve }, { 'V', Command.VerticalLine },
{ 'C', Command.CubicBezierCurve }, { 'Q', Command.QuadraticBezierCurve },
{ 'S', Command.SmoothCubicBezierCurve }, { 'T', Command.SmoothQuadraticBezierCurve },
{ 'A', Command.Arc }, { 'C', Command.CubicBezierCurve },
{ 'Z', Command.Close }, { 'S', Command.SmoothCubicBezierCurve },
}; { 'A', Command.Arc },
{ 'Z', Command.Close },
private static readonly Dictionary<char, FillRule> FillRules = new Dictionary<char, FillRule> };
private IGeometryContext _geometryContext;
private Point _currentPoint;
private Point? _previousControlPoint;
private bool? _isOpen;
private bool _isDisposed;
static PathMarkupParser()
{ {
{'0', FillRule.EvenOdd }, s_separatorPattern = CreatesSeparatorPattern();
{'1', FillRule.NonZero } }
};
private readonly StreamGeometryContext _context;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PathMarkupParser"/> class. /// Initializes a new instance of the <see cref="PathMarkupParser"/> class.
/// </summary> /// </summary>
/// <param name="context">The context for the geometry.</param> /// <param name="geometryContext">The geometry context.</param>
public PathMarkupParser(StreamGeometryContext context) /// <exception cref="ArgumentNullException">geometryContext</exception>
public PathMarkupParser(IGeometryContext geometryContext)
{ {
_context = context; if (geometryContext == null)
{
throw new ArgumentNullException(nameof(geometryContext));
}
_geometryContext = geometryContext;
} }
/// <summary>
/// Defines the command currently being processed.
/// </summary>
private enum Command private enum Command
{ {
None, None,
@ -62,358 +73,568 @@ namespace Avalonia.Media
SmoothCubicBezierCurve, SmoothCubicBezierCurve,
SmoothQuadraticBezierCurve, SmoothQuadraticBezierCurve,
Arc, Arc,
Close, Close
} }
/// <summary> /// <summary>
/// Parses the specified markup string. /// Parses the specified path data and writes the result to the geometryContext of this instance.
/// </summary> /// </summary>
/// <param name="s">The markup string.</param> /// <param name="pathData">The path data.</param>
public void Parse(string s) public void Parse(string pathData)
{
var tokens = ParseTokens(pathData);
CreateGeometry(tokens);
}
void IDisposable.Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{ {
bool openFigure = false; if (_isDisposed)
{
return;
}
using (StringReader reader = new StringReader(s)) if (disposing)
{ {
Command command = Command.None; _geometryContext = null;
Point point = new Point(); }
bool relative = false;
Point? previousControlPoint = null; _isDisposed = true;
}
private static string CreatesSeparatorPattern()
{
var stringBuilder = new StringBuilder();
while (ReadCommand(reader, ref command, ref relative)) foreach (var command in s_commands.Keys)
{
stringBuilder.Append(command);
stringBuilder.Append(char.ToLower(command));
}
return @"(?=[" + stringBuilder + "])";
}
private static IEnumerable<CommandToken> ParseTokens(string s)
{
return Regex.Split(s, s_separatorPattern).Where(t => !string.IsNullOrEmpty(t)).Select(CommandToken.Parse);
}
private static Point MirrorControlPoint(Point controlPoint, Point center)
{
var dir = controlPoint - center;
return center + -dir;
}
private void CreateGeometry(IEnumerable<CommandToken> commandTokens)
{
_currentPoint = new Point();
foreach (var commandToken in commandTokens)
{
try
{ {
switch (command) while (true)
{ {
case Command.FillRule: switch (commandToken.Command)
_context.SetFillRule(ReadFillRule(reader)); {
previousControlPoint = null; case Command.None:
break; break;
case Command.FillRule:
case Command.Move: SetFillRule(commandToken);
if (openFigure) break;
{ case Command.Move:
_context.EndFigure(false); AddMove(commandToken);
} break;
case Command.Line:
point = ReadPoint(reader, point, relative); AddLine(commandToken);
_context.BeginFigure(point, true); break;
openFigure = true; case Command.HorizontalLine:
previousControlPoint = null; AddHorizontalLine(commandToken);
break; break;
case Command.VerticalLine:
case Command.Line: AddVerticalLine(commandToken);
point = ReadPoint(reader, point, relative);
_context.LineTo(point);
previousControlPoint = null;
break;
case Command.HorizontalLine:
if (!relative)
{
point = point.WithX(ReadDouble(reader));
}
else
{
point = new Point(point.X + ReadDouble(reader), point.Y);
}
_context.LineTo(point);
previousControlPoint = null;
break;
case Command.VerticalLine:
if (!relative)
{
point = point.WithY(ReadDouble(reader));
}
else
{
point = new Point(point.X, point.Y + ReadDouble(reader));
}
_context.LineTo(point);
previousControlPoint = null;
break;
case Command.QuadraticBezierCurve:
{
Point handle = ReadPoint(reader, point, relative);
previousControlPoint = handle;
ReadSeparator(reader);
point = ReadPoint(reader, point, relative);
_context.QuadraticBezierTo(handle, point);
break; break;
} case Command.CubicBezierCurve:
AddCubicBezierCurve(commandToken);
case Command.SmoothQuadraticBezierCurve:
{
Point end = ReadPoint(reader, point, relative);
if(previousControlPoint != null)
previousControlPoint = MirrorControlPoint((Point)previousControlPoint, point);
_context.QuadraticBezierTo(previousControlPoint ?? point, end);
point = end;
break; break;
} case Command.QuadraticBezierCurve:
AddQuadraticBezierCurve(commandToken);
case Command.CubicBezierCurve:
{
Point point1 = ReadPoint(reader, point, relative);
ReadSeparator(reader);
Point point2 = ReadPoint(reader, point, relative);
previousControlPoint = point2;
ReadSeparator(reader);
point = ReadPoint(reader, point, relative);
_context.CubicBezierTo(point1, point2, point);
break; break;
} case Command.SmoothCubicBezierCurve:
AddSmoothCubicBezierCurve(commandToken);
case Command.SmoothCubicBezierCurve:
{
Point point2 = ReadPoint(reader, point, relative);
ReadSeparator(reader);
Point end = ReadPoint(reader, point, relative);
if(previousControlPoint != null)
previousControlPoint = MirrorControlPoint((Point)previousControlPoint, point);
_context.CubicBezierTo(previousControlPoint ?? point, point2, end);
previousControlPoint = point2;
point = end;
break; break;
} case Command.SmoothQuadraticBezierCurve:
AddSmoothQuadraticBezierCurve(commandToken);
case Command.Arc:
{
Size size = ReadSize(reader);
ReadSeparator(reader);
double rotationAngle = ReadDouble(reader);
ReadSeparator(reader);
bool isLargeArc = ReadBool(reader);
ReadSeparator(reader);
SweepDirection sweepDirection = ReadBool(reader) ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
ReadSeparator(reader);
point = ReadPoint(reader, point, relative);
_context.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection);
previousControlPoint = null;
break; break;
} case Command.Arc:
AddArc(commandToken);
break;
case Command.Close:
CloseFigure();
break;
default:
throw new NotSupportedException("Unsupported command");
}
case Command.Close: if (commandToken.HasImplicitCommands)
_context.EndFigure(true); {
openFigure = false; continue;
previousControlPoint = null; }
break;
default: break;
throw new NotSupportedException("Unsupported command");
} }
} }
catch (InvalidDataException)
if (openFigure) {
break;
}
catch (NotSupportedException)
{ {
_context.EndFigure(false); break;
} }
} }
if (_isOpen != null)
{
_geometryContext.EndFigure(false);
}
} }
private Point MirrorControlPoint(Point controlPoint, Point center) private void CreateFigure()
{ {
Point dir = (controlPoint - center); if (_isOpen != null)
return center + -dir; {
_geometryContext.EndFigure(false);
}
_geometryContext.BeginFigure(_currentPoint);
_isOpen = true;
} }
private static bool ReadCommand( private void SetFillRule(CommandToken commandToken)
StringReader reader,
ref Command command,
ref bool relative)
{ {
ReadWhitespace(reader); var fillRule = commandToken.ReadFillRule();
int i = reader.Peek(); _geometryContext.SetFillRule(fillRule);
}
if (i == -1) private void CloseFigure()
{
if (_isOpen == true)
{ {
return false; _geometryContext.EndFigure(true);
} }
else
_previousControlPoint = null;
_isOpen = null;
}
private void AddMove(CommandToken commandToken)
{
var currentPoint = commandToken.IsRelative
? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
_currentPoint = currentPoint;
CreateFigure();
if (!commandToken.HasImplicitCommands)
{
return;
}
while (commandToken.HasImplicitCommands)
{ {
char c = (char)i; AddLine(commandToken);
Command next = Command.None;
if (!Commands.TryGetValue(char.ToUpperInvariant(c), out next)) if (commandToken.IsRelative)
{ {
if ((char.IsDigit(c) || c == '.' || c == '+' || c == '-') && continue;
(command != Command.None))
{
return true;
}
else
{
throw new InvalidDataException("Unexpected path command '" + c + "'.");
}
} }
command = next; _currentPoint = currentPoint;
relative = char.IsLower(c);
reader.Read(); CreateFigure();
return true;
} }
} }
private static FillRule ReadFillRule(StringReader reader) private void AddLine(CommandToken commandToken)
{ {
int i = reader.Read(); _currentPoint = commandToken.IsRelative
if (i == -1) ? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
if (_isOpen == null)
{ {
throw new InvalidDataException("Invalid fill rule"); CreateFigure();
} }
char c = (char)i;
FillRule rule;
if (!FillRules.TryGetValue(c, out rule)) _geometryContext.LineTo(_currentPoint);
}
private void AddHorizontalLine(CommandToken commandToken)
{
_currentPoint = commandToken.IsRelative
? new Point(_currentPoint.X + commandToken.ReadDouble(), _currentPoint.Y)
: _currentPoint.WithX(commandToken.ReadDouble());
if (_isOpen == null)
{ {
throw new InvalidDataException("Invalid fill rule"); CreateFigure();
} }
return rule; _geometryContext.LineTo(_currentPoint);
} }
private static double ReadDouble(StringReader reader) private void AddVerticalLine(CommandToken commandToken)
{ {
ReadWhitespace(reader); _currentPoint = commandToken.IsRelative
? new Point(_currentPoint.X, _currentPoint.Y + commandToken.ReadDouble())
: _currentPoint.WithY(commandToken.ReadDouble());
// TODO: Handle Infinity, NaN and scientific notation. if (_isOpen == null)
StringBuilder b = new StringBuilder();
bool readSign = false;
bool readPoint = false;
bool readExponent = false;
int i;
while ((i = reader.Peek()) != -1)
{ {
char c = char.ToUpperInvariant((char)i); CreateFigure();
}
if (((c == '+' || c == '-') && !readSign) || _geometryContext.LineTo(_currentPoint);
(c == '.' && !readPoint) || }
(c == 'E' && !readExponent) ||
char.IsDigit(c))
{
if (b.Length != 0 && !readExponent && c == '-')
break;
b.Append(c);
reader.Read();
if (!readSign) private void AddCubicBezierCurve(CommandToken commandToken)
{ {
readSign = c == '+' || c == '-'; var point1 = commandToken.IsRelative
} ? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
if (!readPoint) var point2 = commandToken.IsRelative
{ ? commandToken.ReadRelativePoint(_currentPoint)
readPoint = c == '.'; : commandToken.ReadPoint();
}
if (c == 'E') _previousControlPoint = point2;
{
readSign = false; var point3 = commandToken.IsRelative
readExponent = true; ? commandToken.ReadRelativePoint(_currentPoint)
} : commandToken.ReadPoint();
}
else if (_isOpen == null)
{ {
break; CreateFigure();
}
} }
return double.Parse(b.ToString(), CultureInfo.InvariantCulture); _geometryContext.CubicBezierTo(point1, point2, point3);
_currentPoint = point3;
} }
private static Point ReadPoint(StringReader reader, Point current, bool relative) private void AddQuadraticBezierCurve(CommandToken commandToken)
{ {
if (!relative) var start = commandToken.IsRelative
? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
_previousControlPoint = start;
var end = commandToken.IsRelative
? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
if (_isOpen == null)
{ {
current = new Point(); CreateFigure();
} }
ReadWhitespace(reader); _geometryContext.QuadraticBezierTo(start, end);
double x = current.X + ReadDouble(reader);
ReadSeparator(reader); _currentPoint = end;
double y = current.Y + ReadDouble(reader);
return new Point(x, y);
} }
private static Size ReadSize(StringReader reader) private void AddSmoothCubicBezierCurve(CommandToken commandToken)
{ {
ReadWhitespace(reader); var point2 = commandToken.IsRelative
double x = ReadDouble(reader); ? commandToken.ReadRelativePoint(_currentPoint)
ReadSeparator(reader); : commandToken.ReadPoint();
double y = ReadDouble(reader);
return new Size(x, y); var end = commandToken.IsRelative
? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
if (_previousControlPoint != null)
{
_previousControlPoint = MirrorControlPoint((Point)_previousControlPoint, _currentPoint);
}
if (_isOpen == null)
{
CreateFigure();
}
_geometryContext.CubicBezierTo(_previousControlPoint ?? _currentPoint, point2, end);
_previousControlPoint = point2;
_currentPoint = end;
} }
private static bool ReadBool(StringReader reader) private void AddSmoothQuadraticBezierCurve(CommandToken commandToken)
{ {
return ReadDouble(reader) != 0; var end = commandToken.IsRelative
? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
if (_previousControlPoint != null)
{
_previousControlPoint = MirrorControlPoint((Point)_previousControlPoint, _currentPoint);
}
if (_isOpen == null)
{
CreateFigure();
}
_geometryContext.QuadraticBezierTo(_previousControlPoint ?? _currentPoint, end);
_currentPoint = end;
} }
private static Point ReadRelativePoint(StringReader reader, Point lastPoint) private void AddArc(CommandToken commandToken)
{ {
ReadWhitespace(reader); var size = commandToken.ReadSize();
double x = ReadDouble(reader);
ReadSeparator(reader); var rotationAngle = commandToken.ReadDouble();
double y = ReadDouble(reader);
return new Point(lastPoint.X + x, lastPoint.Y + y); var isLargeArc = commandToken.ReadBool();
var sweepDirection = commandToken.ReadBool() ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
var end = commandToken.IsRelative
? commandToken.ReadRelativePoint(_currentPoint)
: commandToken.ReadPoint();
if (_isOpen == null)
{
CreateFigure();
}
_geometryContext.ArcTo(end, size, rotationAngle, isLargeArc, sweepDirection);
_currentPoint = end;
_previousControlPoint = null;
} }
private static void ReadSeparator(StringReader reader) private class CommandToken
{ {
int i; private const string ArgumentExpression = @"-?[0-9]*\.?\d+";
bool readComma = false;
while ((i = reader.Peek()) != -1) private CommandToken(Command command, bool isRelative, IEnumerable<string> arguments)
{ {
char c = (char)i; Command = command;
IsRelative = isRelative;
Arguments = new List<string>(arguments);
}
public Command Command { get; }
if (char.IsWhiteSpace(c)) public bool IsRelative { get; }
public bool HasImplicitCommands
{
get
{ {
reader.Read(); if (CurrentPosition == 0 && Arguments.Count > 0)
{
return true;
}
return CurrentPosition < Arguments.Count - 1;
} }
else if (c == ',') }
private int CurrentPosition { get; set; }
private List<string> Arguments { get; }
public static CommandToken Parse(string s)
{
using (var reader = new StringReader(s))
{ {
if (readComma) var command = Command.None;
var isRelative = false;
if (!ReadCommand(reader, ref command, ref isRelative))
{ {
throw new InvalidDataException("Unexpected ','."); throw new InvalidDataException("No path command declared.");
} }
readComma = true; var commandArguments = reader.ReadToEnd();
reader.Read();
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()
{
if (CurrentPosition == Arguments.Count)
{
throw new InvalidDataException("Invalid fill rule");
} }
else
var value = Arguments[CurrentPosition];
CurrentPosition++;
switch (value)
{ {
break; case "0":
{
return FillRule.EvenOdd;
}
case "1":
{
return FillRule.NonZero;
}
default:
throw new InvalidDataException("Invalid fill rule");
} }
} }
}
private static void ReadWhitespace(StringReader reader) public bool ReadBool()
{ {
int 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");
}
}
public double ReadDouble()
{
if (CurrentPosition == Arguments.Count)
{
throw new InvalidDataException("Invalid double value");
}
var value = Arguments[CurrentPosition];
CurrentPosition++;
return double.Parse(value, CultureInfo.InvariantCulture);
}
public Size ReadSize()
{
var width = ReadDouble();
var height = ReadDouble();
return new Size(width, height);
}
public Point ReadPoint()
{
var x = ReadDouble();
var y = ReadDouble();
return new Point(x, y);
}
public Point ReadRelativePoint(Point origin)
{
var x = ReadDouble();
var y = ReadDouble();
return new Point(origin.X + x, origin.Y + y);
}
while ((i = reader.Peek()) != -1) private static bool ReadCommand(TextReader reader, ref Command command, ref bool relative)
{ {
char c = (char)i; ReadWhitespace(reader);
if (char.IsWhiteSpace(c)) var i = reader.Peek();
if (i == -1)
{ {
reader.Read(); return false;
} }
else
var c = (char)i;
if (!s_commands.TryGetValue(char.ToUpperInvariant(c), out var next))
{ {
break; throw new InvalidDataException("Unexpected path command '" + c + "'.");
}
command = next;
relative = char.IsLower(c);
reader.Read();
return true;
}
private static void ReadWhitespace(TextReader reader)
{
int i;
while ((i = reader.Peek()) != -1)
{
var c = (char)i;
if (char.IsWhiteSpace(c))
{
reader.Read();
}
else
{
break;
}
} }
} }
} }

11
src/Avalonia.Visuals/Media/StreamGeometry.cs

@ -35,14 +35,15 @@ namespace Avalonia.Media
/// <returns>A <see cref="StreamGeometry"/>.</returns> /// <returns>A <see cref="StreamGeometry"/>.</returns>
public static new StreamGeometry Parse(string s) public static new StreamGeometry Parse(string s)
{ {
StreamGeometry result = new StreamGeometry(); var streamGeometry = new StreamGeometry();
using (StreamGeometryContext ctx = result.Open()) using (var context = streamGeometry.Open())
{ using (var parser = new PathMarkupParser(context))
PathMarkupParser parser = new PathMarkupParser(ctx); {
parser.Parse(s); parser.Parse(s);
return result;
} }
return streamGeometry;
} }
/// <inheritdoc/> /// <inheritdoc/>

2
src/Avalonia.Visuals/Media/StreamGeometryContext.cs

@ -15,7 +15,7 @@ namespace Avalonia.Media
/// <see cref="StreamGeometry.Open"/>. /// <see cref="StreamGeometry.Open"/>.
/// </remarks> /// </remarks>
/// TODO: This class is just a wrapper around IStreamGeometryContextImpl: is it needed? /// TODO: This class is just a wrapper around IStreamGeometryContextImpl: is it needed?
public class StreamGeometryContext : IDisposable public class StreamGeometryContext : IGeometryContext
{ {
private readonly IStreamGeometryContextImpl _impl; private readonly IStreamGeometryContextImpl _impl;

66
src/Avalonia.Visuals/Platform/IGeometryContext.cs

@ -0,0 +1,66 @@
// 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 Avalonia.Media;
namespace Avalonia.Platform
{
/// <summary>
/// Describes a geometry using drawing commands.
/// </summary>
public interface IGeometryContext : IDisposable
{
/// <summary>
/// Draws an arc to the specified point.
/// </summary>
/// <param name="point">The destination point.</param>
/// <param name="size">The radii of an oval whose perimeter is used to draw the angle.</param>
/// <param name="rotationAngle">The rotation angle of the oval that specifies the curve.</param>
/// <param name="isLargeArc">true to draw the arc greater than 180 degrees; otherwise, false.</param>
/// <param name="sweepDirection">
/// A value that indicates whether the arc is drawn in the Clockwise or Counterclockwise direction.
/// </param>
void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection);
/// <summary>
/// Begins a new figure.
/// </summary>
/// <param name="startPoint">The starting point for the figure.</param>
/// <param name="isFilled">Whether the figure is filled.</param>
void BeginFigure(Point startPoint, bool isFilled = true);
/// <summary>
/// Draws a Bezier curve to the specified point.
/// </summary>
/// <param name="point1">The first control point used to specify the shape of the curve.</param>
/// <param name="point2">The second control point used to specify the shape of the curve.</param>
/// <param name="point3">The destination point for the end of the curve.</param>
void CubicBezierTo(Point point1, Point point2, Point point3);
/// <summary>
/// Draws a quadratic Bezier curve to the specified point
/// </summary>
/// <param name="control">Control point</param>
/// <param name="endPoint">DestinationPoint</param>
void QuadraticBezierTo(Point control, Point endPoint);
/// <summary>
/// Draws a line to the specified point.
/// </summary>
/// <param name="point">The destination point.</param>
void LineTo(Point point);
/// <summary>
/// Ends the figure started by <see cref="BeginFigure(Point, bool)"/>.
/// </summary>
/// <param name="isClosed">Whether the figure is closed.</param>
void EndFigure(bool isClosed);
/// <summary>
/// Sets the fill rule.
/// </summary>
/// <param name="fillRule">The fill rule.</param>
void SetFillRule(FillRule fillRule);
}
}

54
src/Avalonia.Visuals/Platform/IStreamGeometryContextImpl.cs

@ -1,62 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Media;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
/// <summary> /// <summary>
/// Describes a geometry using drawing commands. /// Describes a geometry using drawing commands.
/// </summary> /// </summary>
public interface IStreamGeometryContextImpl : IDisposable public interface IStreamGeometryContextImpl : IGeometryContext
{ {
/// <summary>
/// Draws an arc to the specified point.
/// </summary>
/// <param name="point">The destination point.</param>
/// <param name="size">The radii of an oval whose perimeter is used to draw the angle.</param>
/// <param name="rotationAngle">The rotation angle of the oval that specifies the curve.</param>
/// <param name="isLargeArc">true to draw the arc greater than 180 degrees; otherwise, false.</param>
/// <param name="sweepDirection">
/// A value that indicates whether the arc is drawn in the Clockwise or Counterclockwise direction.
/// </param>
void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection);
/// <summary>
/// Begins a new figure.
/// </summary>
/// <param name="startPoint">The starting point for the figure.</param>
/// <param name="isFilled">Whether the figure is filled.</param>
void BeginFigure(Point startPoint, bool isFilled);
/// <summary>
/// Draws a Bezier curve to the specified point.
/// </summary>
/// <param name="point1">The first control point used to specify the shape of the curve.</param>
/// <param name="point2">The second control point used to specify the shape of the curve.</param>
/// <param name="point3">The destination point for the end of the curve.</param>
void CubicBezierTo(Point point1, Point point2, Point point3);
/// <summary>
/// Draws a quadratic Bezier curve to the specified point
/// </summary>
/// <param name="control">Control point</param>
/// <param name="endPoint">DestinationPoint</param>
void QuadraticBezierTo(Point control, Point endPoint);
/// <summary>
/// Draws a line to the specified point.
/// </summary>
/// <param name="point">The destination point.</param>
void LineTo(Point point);
/// <summary>
/// Ends the figure started by <see cref="BeginFigure(Point, bool)"/>.
/// </summary>
/// <param name="isClosed">Whether the figure is closed.</param>
void EndFigure(bool isClosed);
void SetFillRule(FillRule fillRule);
} }
} }

85
src/Avalonia.Visuals/Platform/PathGeometryContext.cs

@ -0,0 +1,85 @@
// 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 Avalonia.Media;
using Avalonia.Platform;
using System;
namespace Avalonia.Visuals.Platform
{
public class PathGeometryContext : IGeometryContext
{
private PathFigure _currentFigure;
private PathGeometry _pathGeometry;
public PathGeometryContext(PathGeometry pathGeometry)
{
_pathGeometry = pathGeometry ?? throw new ArgumentNullException(nameof(pathGeometry));
}
public void Dispose()
{
_pathGeometry = null;
}
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
var arcSegment = new ArcSegment
{
Size = size,
RotationAngle = rotationAngle,
IsLargeArc = isLargeArc,
SweepDirection = sweepDirection,
Point = point
};
_currentFigure.Segments.Add(arcSegment);
}
public void BeginFigure(Point startPoint, bool isFilled)
{
_currentFigure = new PathFigure { StartPoint = startPoint, IsClosed = false, IsFilled = isFilled };
_pathGeometry.Figures.Add(_currentFigure);
}
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
var bezierSegment = new BezierSegment { Point1 = point1, Point2 = point2, Point3 = point3 };
_currentFigure.Segments.Add(bezierSegment);
}
public void QuadraticBezierTo(Point control, Point endPoint)
{
var quadraticBezierSegment = new QuadraticBezierSegment { Point1 = control, Point2 = endPoint };
_currentFigure.Segments.Add(quadraticBezierSegment);
}
public void LineTo(Point point)
{
var lineSegment = new LineSegment
{
Point = point
};
_currentFigure.Segments.Add(lineSegment);
}
public void EndFigure(bool isClosed)
{
if (_currentFigure != null)
{
_currentFigure.IsClosed = isClosed;
}
_currentFigure = null;
}
public void SetFillRule(FillRule fillRule)
{
_pathGeometry.FillRule = fillRule;
}
}
}

313
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1194,16 +1194,6 @@ namespace Avalonia.Win32.Interop
public IntPtr hIconSm; public IntPtr hIconSm;
} }
[Flags]
public enum OpenFileNameFlags
{
OFN_ALLOWMULTISELECT = 0x00000200,
OFN_EXPLORER = 0x00080000,
OFN_HIDEREADONLY = 0x00000004,
OFN_NOREADONLYRETURN = 0x00008000,
OFN_OVERWRITEPROMPT = 0x00000002
}
public enum HRESULT : uint public enum HRESULT : uint
{ {
S_FALSE = 0x0001, S_FALSE = 0x0001,
@ -1223,7 +1213,7 @@ namespace Avalonia.Win32.Interop
public const uint SIGDN_FILESYSPATH = 0x80058000; public const uint SIGDN_FILESYSPATH = 0x80058000;
[Flags] [Flags]
internal enum FOS : uint public enum FOS : uint
{ {
FOS_OVERWRITEPROMPT = 0x00000002, FOS_OVERWRITEPROMPT = 0x00000002,
FOS_STRICTFILETYPES = 0x00000004, FOS_STRICTFILETYPES = 0x00000004,
@ -1247,135 +1237,246 @@ namespace Avalonia.Win32.Interop
FOS_DEFAULTNOMINIMODE = 0x20000000 FOS_DEFAULTNOMINIMODE = 0x20000000
} }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public static class ShellIds
public struct OpenFileName
{ {
public int lStructSize; public static readonly Guid OpenFileDialog = Guid.Parse("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7");
public IntPtr hwndOwner; public static readonly Guid SaveFileDialog = Guid.Parse("C0B4E2F3-BA21-4773-8DBA-335EC946EB8B");
public IntPtr hInstance; public static readonly Guid IFileDialog = Guid.Parse("42F85136-DB7E-439C-85F1-E4075D135FC8");
public IntPtr lpstrFilter; public static readonly Guid IShellItem = Guid.Parse("43826D1E-E718-42EE-BC55-A1E261C37BFE");
public IntPtr lpstrCustomFilter; }
public int nMaxCustFilter;
public int nFilterIndex;
public IntPtr lpstrFile;
public int nMaxFile;
public IntPtr lpstrFileTitle;
public int nMaxFileTitle;
public IntPtr lpstrInitialDir;
public IntPtr lpstrTitle;
public OpenFileNameFlags Flags;
private readonly ushort Unused;
private readonly ushort Unused2;
public IntPtr lpstrDefExt;
public IntPtr lCustData;
public IntPtr lpfnHook;
public IntPtr lpTemplateName;
public IntPtr reservedPtr;
public int reservedInt;
public int flagsEx;
}
}
[ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IFileDialog public interface IFileDialog
{ {
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
[PreserveSig()] [PreserveSig()]
uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetFileTypes([In] uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec); uint SetFileTypes(uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] COMDLG_FILTERSPEC[] rgFilterSpec);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetFileTypeIndex([In] uint iFileType); uint SetFileTypeIndex([In] uint iFileType);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetFileTypeIndex(out uint piFileType); uint GetFileTypeIndex(out uint piFileType);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie); uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint Unadvise([In] uint dwCookie); uint Unadvise([In] uint dwCookie);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetOptions([In] uint fos); uint SetOptions([In] uint fos);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetOptions(out uint fos); uint GetOptions(out uint fos);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); uint GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap); uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint Close([MarshalAs(UnmanagedType.Error)] uint hr); uint Close([MarshalAs(UnmanagedType.Error)] uint hr);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetClientGuid([In] ref Guid guid); uint SetClientGuid([In] ref Guid guid);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint ClearClientData(); uint ClearClientData();
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter);
} }
[ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFileOpenDialog
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
[PreserveSig()]
uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
internal interface IShellItem uint SetFileTypes([In] uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec);
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); void SetFileTypeIndex([In] uint iFileType);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName); void GetFileTypeIndex(out uint piFileType);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs); uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); void Unadvise([In] uint dwCookie);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetOptions([In] uint fos);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetOptions(out uint fos);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void Close([MarshalAs(UnmanagedType.Error)] int hr);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetClientGuid([In] ref Guid guid);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void ClearClientData();
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetResults([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppenum);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppsai);
}
[ComImport, Guid("B63EA76D-1F85-456F-A19C-48159EFA858B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellItemArray
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid rbhid,
[In] ref Guid riid, out IntPtr ppvOut);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetPropertyStore([In] int Flags, [In] ref Guid riid, out IntPtr ppv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetPropertyDescriptionList([In] ref PROPERTYKEY keyType, [In] ref Guid riid, out IntPtr ppv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetAttributes([In] SIATTRIBFLAGS dwAttribFlags, [In] uint sfgaoMask, out uint psfgaoAttribs);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetCount(out uint pdwNumItems);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetItemAt([In] uint dwIndex, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems);
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct PROPERTYKEY
{
public Guid fmtid;
public uint pid;
}
public enum SIATTRIBFLAGS
{
SIATTRIBFLAGS_AND = 1,
SIATTRIBFLAGS_APPCOMPAT = 3,
SIATTRIBFLAGS_OR = 2
}
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellItem
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct COMDLG_FILTERSPEC
{
[MarshalAs(UnmanagedType.LPWStr)]
public string pszName;
[MarshalAs(UnmanagedType.LPWStr)]
public string pszSpec;
}
} }
[Flags] [Flags]
internal enum DropEffect : int internal enum DropEffect : int
{ {

228
src/Windows/Avalonia.Win32/SystemDialogImpl.cs

@ -1,152 +1,94 @@
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
class SystemDialogImpl : ISystemDialogImpl class SystemDialogImpl : ISystemDialogImpl
{ {
static char[] ToChars(string s)
{
if (s == null)
return null;
var chars = new char[s.Length];
for (int c = 0; c < s.Length; c++)
chars[c] = s[c];
return chars;
}
public unsafe Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) public unsafe Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
{ {
var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero;
return Task.Factory.StartNew(() => return Task.Factory.StartNew(() =>
{ {
var filters = new StringBuilder(); var result = new string[0];
foreach (var filter in dialog.Filters)
{
var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e));
filters.Append(filter.Name);
filters.Append(" (");
filters.Append(extMask);
filters.Append(")");
filters.Append('\0');
filters.Append(extMask);
filters.Append('\0');
}
if (filters.Length == 0)
filters.Append("All files\0*.*\0");
filters.Append('\0');
var filterBuffer = new char[filters.Length]; Guid clsid = dialog is OpenFileDialog ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog;
filters.CopyTo(0, filterBuffer, 0, filterBuffer.Length); Guid iid = UnmanagedMethods.ShellIds.IFileDialog;
UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out var unk);
var defExt = ToChars((dialog as SaveFileDialog)?.DefaultExtension); var frm = (UnmanagedMethods.IFileDialog)unk;
var fileBuffer = new char[256];
dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length);
string userSelectedExt = string.Empty; var openDialog = dialog as OpenFileDialog;
uint options;
frm.GetOptions(out options);
options |= (uint)(UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT);
if (openDialog?.AllowMultiple == true)
options |= (uint)UnmanagedMethods.FOS.FOS_ALLOWMULTISELECT;
frm.SetOptions(options);
var title = ToChars(dialog.Title); var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? "";
var initialDir = ToChars(dialog.InitialDirectory); frm.SetDefaultExtension(defaultExtension);
frm.SetFileName(dialog.InitialFileName ?? "");
frm.SetTitle(dialog.Title);
fixed (char* pFileBuffer = fileBuffer) var filters = new List<UnmanagedMethods.COMDLG_FILTERSPEC>();
fixed (char* pFilterBuffer = filterBuffer) foreach (var filter in dialog.Filters)
fixed (char* pDefExt = defExt)
fixed (char* pInitDir = initialDir)
fixed (char* pTitle = title)
{ {
var ofn = new UnmanagedMethods.OpenFileName() var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e));
{ filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = filter.Name, pszSpec = extMask });
hwndOwner = hWnd,
hInstance = IntPtr.Zero,
lCustData = IntPtr.Zero,
nFilterIndex = 0,
Flags =
UnmanagedMethods.OpenFileNameFlags.OFN_EXPLORER |
UnmanagedMethods.OpenFileNameFlags.OFN_HIDEREADONLY,
nMaxCustFilter = 0,
nMaxFile = fileBuffer.Length - 1,
nMaxFileTitle = 0,
lpTemplateName = IntPtr.Zero,
lpfnHook = IntPtr.Zero,
lpstrCustomFilter = IntPtr.Zero,
lpstrDefExt = new IntPtr(pDefExt),
lpstrFile = new IntPtr(pFileBuffer),
lpstrFileTitle = IntPtr.Zero,
lpstrFilter = new IntPtr(pFilterBuffer),
lpstrInitialDir = new IntPtr(pInitDir),
lpstrTitle = new IntPtr(pTitle),
};
ofn.lStructSize = Marshal.SizeOf(ofn);
if ((dialog as OpenFileDialog)?.AllowMultiple == true)
ofn.Flags |= UnmanagedMethods.OpenFileNameFlags.OFN_ALLOWMULTISELECT;
if (dialog is SaveFileDialog)
ofn.Flags |= UnmanagedMethods.OpenFileNameFlags.OFN_NOREADONLYRETURN |
UnmanagedMethods.OpenFileNameFlags.OFN_OVERWRITEPROMPT;
var pofn = &ofn;
// We should save the current directory to restore it later.
var currentDirectory = Environment.CurrentDirectory;
var res = dialog is OpenFileDialog
? UnmanagedMethods.GetOpenFileName(new IntPtr(pofn))
: UnmanagedMethods.GetSaveFileName(new IntPtr(pofn));
// Restore the old current directory, since GetOpenFileName and GetSaveFileName change it after they're called
Environment.CurrentDirectory = currentDirectory;
if (!res)
return null;
if (dialog?.Filters.Count > 0)
userSelectedExt = dialog.Filters[ofn.nFilterIndex - 1].Extensions.FirstOrDefault();
} }
var cStart = 0; if (filters.Count == 0)
string dir = null; filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = "All files", pszSpec = "*.*" });
var files = new List<string>();
for (var c = 0; c < fileBuffer.Length; c++) frm.SetFileTypes((uint)filters.Count, filters.ToArray());
frm.SetFileTypeIndex(0);
if (dialog.InitialDirectory != null)
{ {
if (fileBuffer[c] == 0) UnmanagedMethods.IShellItem directoryShellItem;
Guid riid = UnmanagedMethods.ShellIds.IShellItem;
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.InitialDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK)
{ {
//Encountered double zero char frm.SetFolder(directoryShellItem);
if (cStart == c) frm.SetDefaultFolder(directoryShellItem);
break;
var s = new string(fileBuffer, cStart, c - cStart);
if (dir == null)
dir = s;
else
files.Add(s);
cStart = c + 1;
} }
} }
if (files.Count == 0)
if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK)
{ {
if (dialog is SaveFileDialog) if (openDialog?.AllowMultiple == true)
{ {
if (string.IsNullOrWhiteSpace(Path.GetExtension(dir)) && UnmanagedMethods.IShellItemArray shellItemArray;
!string.IsNullOrWhiteSpace(userSelectedExt) && ((UnmanagedMethods.IFileOpenDialog)frm).GetResults(out shellItemArray);
!userSelectedExt.Contains("*")) uint count;
dir = Path.ChangeExtension(dir, userSelectedExt); shellItemArray.GetCount(out count);
result = new string[count];
for (uint i = 0; i < count; i++)
{
UnmanagedMethods.IShellItem shellItem;
shellItemArray.GetItemAt(i, out shellItem);
result[i] = GetAbsoluteFilePath(shellItem);
}
}
else
{
UnmanagedMethods.IShellItem shellItem;
if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK)
{
result = new string[] { GetAbsoluteFilePath(shellItem) };
}
} }
return new[] { dir };
} }
return files.Select(f => Path.Combine(dir, f)).ToArray(); return result;
}); });
} }
@ -157,11 +99,11 @@ namespace Avalonia.Win32
string result = string.Empty; string result = string.Empty;
var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero;
var clsid = Guid.Parse("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7"); Guid clsid = UnmanagedMethods.ShellIds.OpenFileDialog;
var iid = Guid.Parse("42F85136-DB7E-439C-85F1-E4075D135FC8"); Guid iid = UnmanagedMethods.ShellIds.IFileDialog;
UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out var unk); UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out var unk);
var frm = (IFileDialog)unk; var frm = (UnmanagedMethods.IFileDialog)unk;
uint options; uint options;
frm.GetOptions(out options); frm.GetOptions(out options);
options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT); options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT);
@ -169,8 +111,8 @@ namespace Avalonia.Win32
if (dialog.InitialDirectory != null) if (dialog.InitialDirectory != null)
{ {
IShellItem directoryShellItem; UnmanagedMethods.IShellItem directoryShellItem;
var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem Guid riid = UnmanagedMethods.ShellIds.IShellItem;
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.InitialDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.InitialDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK)
{ {
frm.SetFolder(directoryShellItem); frm.SetFolder(directoryShellItem);
@ -179,8 +121,8 @@ namespace Avalonia.Win32
if (dialog.DefaultDirectory != null) if (dialog.DefaultDirectory != null)
{ {
IShellItem directoryShellItem; UnmanagedMethods.IShellItem directoryShellItem;
var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem Guid riid = UnmanagedMethods.ShellIds.IShellItem;
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.DefaultDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.DefaultDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK)
{ {
frm.SetDefaultFolder(directoryShellItem); frm.SetDefaultFolder(directoryShellItem);
@ -189,29 +131,35 @@ namespace Avalonia.Win32
if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK) if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK)
{ {
IShellItem shellItem; UnmanagedMethods.IShellItem shellItem;
if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK)
{ {
IntPtr pszString; result = GetAbsoluteFilePath(shellItem);
if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == (uint)UnmanagedMethods.HRESULT.S_OK)
{
if (pszString != IntPtr.Zero)
{
try
{
result = Marshal.PtrToStringAuto(pszString);
}
finally
{
Marshal.FreeCoTaskMem(pszString);
}
}
}
} }
} }
return result; return result;
}); });
} }
private string GetAbsoluteFilePath(UnmanagedMethods.IShellItem shellItem)
{
IntPtr pszString;
if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == (uint)UnmanagedMethods.HRESULT.S_OK)
{
if (pszString != IntPtr.Zero)
{
try
{
return Marshal.PtrToStringAuto(pszString);
}
finally
{
Marshal.FreeCoTaskMem(pszString);
}
}
}
return "";
}
} }
} }

5
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks> <TargetFramework>netcoreapp2.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
@ -15,7 +16,7 @@
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" /> <ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.10.8" /> <PackageReference Include="BenchmarkDotNet" Version="0.10.14" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Properties\" /> <Folder Include="Properties\" />

45
tests/Avalonia.Benchmarks/Visuals/Media/PathMarkupParserTests.cs

@ -0,0 +1,45 @@
// 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 Avalonia.Media;
using Avalonia.UnitTests;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Visuals.Media
{
[MemoryDiagnoser]
public class PathMarkupParserTests : IDisposable
{
private IDisposable _app;
public PathMarkupParserTests()
{
_app = UnitTestApplication.Start(TestServices.StyledWindow);
}
public void Dispose()
{
_app.Dispose();
}
[Benchmark]
public void Parse_Large_Path()
{
const string PathData = "F1 M 16.6309 18.6563C 17.1309 8.15625 29.8809 14.1563 29.8809 14.1563C 30.8809 11.1563 34.1308 11.4063" +
" 34.1308 11.4063C 33.5 12 34.6309 13.1563 34.6309 13.1563C 32.1309 13.1562 31.1309 14.9062 31.1309 14.9" +
"062C 41.1309 23.9062 32.6309 27.9063 32.6309 27.9062C 24.6309 24.9063 21.1309 22.1562 16.6309 18.6563 Z" +
" M 16.6309 19.9063C 21.6309 24.1563 25.1309 26.1562 31.6309 28.6562C 31.6309 28.6562 26.3809 39.1562 18" +
".3809 36.1563C 18.3809 36.1563 18 38 16.3809 36.9063C 15 36 16.3809 34.9063 16.3809 34.9063C 16.3809 34" +
".9063 10.1309 30.9062 16.6309 19.9063 Z ";
var streamGeometry = new StreamGeometry();
using (var context = streamGeometry.Open())
using (var parser = new PathMarkupParser(context))
{
parser.Parse(PathData);
}
}
}
}

142
tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs

@ -2,8 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Visuals.Platform;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Visuals.UnitTests.Media namespace Avalonia.Visuals.UnitTests.Media
@ -13,48 +12,133 @@ namespace Avalonia.Visuals.UnitTests.Media
[Fact] [Fact]
public void Parses_Move() public void Parses_Move()
{ {
using (AvaloniaLocator.EnterScope()) var pathGeometry = new PathGeometry();
{ using (var context = new PathGeometryContext(pathGeometry))
var result = new Mock<IStreamGeometryContextImpl>(); using (var parser = new PathMarkupParser(context))
{
var parser = PrepareParser(result);
parser.Parse("M10 10"); parser.Parse("M10 10");
result.Verify(x => x.BeginFigure(new Point(10, 10), true)); var figure = pathGeometry.Figures[0];
Assert.Equal(new Point(10, 10), figure.StartPoint);
} }
} }
[Fact] [Fact]
public void Parses_Line() public void Parses_Line()
{ {
using (AvaloniaLocator.EnterScope()) var pathGeometry = new PathGeometry();
using (var context = new PathGeometryContext(pathGeometry))
using (var parser = new PathMarkupParser(context))
{ {
var result = new Mock<IStreamGeometryContextImpl>(); parser.Parse("M0 0L10 10");
var parser = PrepareParser(result); var figure = pathGeometry.Figures[0];
parser.Parse("M0 0L10 10"); var segment = figure.Segments[0];
Assert.IsType<LineSegment>(segment);
result.Verify(x => x.LineTo(new Point(10, 10))); var lineSegment = (LineSegment)segment;
Assert.Equal(new Point(10, 10), lineSegment.Point);
} }
} }
[Fact] [Fact]
public void Parses_Close() public void Parses_Close()
{ {
using (AvaloniaLocator.EnterScope()) var pathGeometry = new PathGeometry();
using (var context = new PathGeometryContext(pathGeometry))
using (var parser = new PathMarkupParser(context))
{ {
var result = new Mock<IStreamGeometryContextImpl>(); parser.Parse("M0 0L10 10z");
var parser = PrepareParser(result); var figure = pathGeometry.Figures[0];
parser.Parse("M0 0L10 10z"); Assert.True(figure.IsClosed);
}
}
result.Verify(x => x.EndFigure(true)); [Fact]
public void Parses_FillMode_Before_Move()
{
var pathGeometry = new PathGeometry();
using (var context = new PathGeometryContext(pathGeometry))
using (var parser = new PathMarkupParser(context))
{
parser.Parse("F 1M0,0");
Assert.Equal(FillRule.NonZero, pathGeometry.FillRule);
} }
} }
[Theory]
[InlineData("M0 0 10 10 20 20")]
[InlineData("M0,0 10,10 20,20")]
[InlineData("M0,0,10,10,20,20")]
public void Parses_Implicit_Line_Command_After_Move(string pathData)
{
var pathGeometry = new PathGeometry();
using (var context = new PathGeometryContext(pathGeometry))
using (var parser = new PathMarkupParser(context))
{
parser.Parse(pathData);
var figure = pathGeometry.Figures[0];
var segment = figure.Segments[0];
Assert.IsType<LineSegment>(segment);
var lineSegment = (LineSegment)segment;
Assert.Equal(new Point(10, 10), lineSegment.Point);
figure = pathGeometry.Figures[1];
segment = figure.Segments[0];
Assert.IsType<LineSegment>(segment);
lineSegment = (LineSegment)segment;
Assert.Equal(new Point(20, 20), lineSegment.Point);
}
}
[Theory]
[InlineData("m0 0 10 10 20 20")]
[InlineData("m0,0 10,10 20,20")]
[InlineData("m0,0,10,10,20,20")]
public void Parses_Implicit_Line_Command_After_Relative_Move(string pathData)
{
var pathGeometry = new PathGeometry();
using (var context = new PathGeometryContext(pathGeometry))
using (var parser = new PathMarkupParser(context))
{
parser.Parse(pathData);
var figure = pathGeometry.Figures[0];
var segment = figure.Segments[0];
Assert.IsType<LineSegment>(segment);
var lineSegment = (LineSegment)segment;
Assert.Equal(new Point(10, 10), lineSegment.Point);
segment = figure.Segments[1];
Assert.IsType<LineSegment>(segment);
lineSegment = (LineSegment)segment;
Assert.Equal(new Point(30, 30), lineSegment.Point);
}
}
[Theory] [Theory]
[InlineData("F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z")] // issue #1107 [InlineData("F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z")] // issue #1107
[InlineData("M0 0L10 10z")] [InlineData("M0 0L10 10z")]
@ -75,29 +159,19 @@ namespace Avalonia.Visuals.UnitTests.Media
".3809 36.1563C 18.3809 36.1563 18 38 16.3809 36.9063C 15 36 16.3809 34.9063 16.3809 34.9063C 16.3809 34" + ".3809 36.1563C 18.3809 36.1563 18 38 16.3809 36.9063C 15 36 16.3809 34.9063 16.3809 34.9063C 16.3809 34" +
".9063 10.1309 30.9062 16.6309 19.9063 Z ")] ".9063 10.1309 30.9062 16.6309 19.9063 Z ")]
[InlineData( [InlineData(
"F1M16,12C16,14.209 14.209,16 12,16 9.791,16 8,14.209 8,12 8,11.817 8.03,11.644 8.054,11.467L6.585,10 4,10 " + "F1M16,12C16,14.209 14.209,16 12,16 9.791,16 8,14.209 8,12 8,11.817 8.03,11.644 8.054,11.467L6.585,10 4,10 " +
"4,6.414 2.5,7.914 0,5.414 0,3.586 3.586,0 4.414,0 7.414,3 7.586,3 9,1.586 11.914,4.5 10.414,6 " + "4,6.414 2.5,7.914 0,5.414 0,3.586 3.586,0 4.414,0 7.414,3 7.586,3 9,1.586 11.914,4.5 10.414,6 " +
"12.461,8.046C14.45,8.278,16,9.949,16,12")] "12.461,8.046C14.45,8.278,16,9.949,16,12")]
public void Should_Parse(string pathData) public void Should_Parse(string pathData)
{ {
using (AvaloniaLocator.EnterScope()) var pathGeometry = new PathGeometry();
using (var context = new PathGeometryContext(pathGeometry))
using (var parser = new PathMarkupParser(context))
{ {
var parser = PrepareParser();
parser.Parse(pathData); parser.Parse(pathData);
Assert.True(true); Assert.True(true);
} }
} }
private static PathMarkupParser PrepareParser(Mock<IStreamGeometryContextImpl> implMock = null)
{
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>()
.ToConstant(Mock.Of<IPlatformRenderInterface>());
return new PathMarkupParser(
new StreamGeometryContext(implMock != null ? implMock.Object : Mock.Of<IStreamGeometryContextImpl>()));
}
} }
} }
Loading…
Cancel
Save