Browse Source

Merge branch 'master' into make-mono-great-again

pull/2233/head
Nikita Tsukanov 7 years ago
committed by GitHub
parent
commit
7d6473be3d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      azure-pipelines.yml
  2. 12
      nukebuild/BuildParameters.cs
  3. 129
      parameters.cake
  4. 1
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  5. 2
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  6. 11
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  7. 2
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  8. 2
      src/Avalonia.Controls/Viewbox.cs
  9. 2
      src/Avalonia.Input/Cursors.cs
  10. 6
      src/Avalonia.Input/DataFormats.cs
  11. 10
      src/Avalonia.Input/DragDrop.cs
  12. 20
      src/Avalonia.Styling/Styling/ChildSelector.cs
  13. 16
      src/Avalonia.Styling/Styling/DescendentSelector.cs
  14. 7
      src/Avalonia.Styling/Styling/IStyle.cs
  15. 12
      src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs
  16. 39
      src/Avalonia.Styling/Styling/Selector.cs
  17. 86
      src/Avalonia.Styling/Styling/SelectorMatch.cs
  18. 24
      src/Avalonia.Styling/Styling/Style.cs
  19. 45
      src/Avalonia.Styling/Styling/Styles.cs
  20. 5
      src/Avalonia.Styling/Styling/TemplateSelector.cs
  21. 19
      src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
  22. 6
      src/Avalonia.Visuals/Matrix.cs
  23. 4
      src/Avalonia.Visuals/Media/GradientStop.cs
  24. 2
      src/Avalonia.Visuals/Media/Typeface.cs
  25. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  26. 8
      src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
  27. 89
      src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs
  28. 6
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  29. 17
      src/Windows/Avalonia.Win32/WindowImpl.cs
  30. 13
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
  31. 41
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
  32. 31
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs
  33. 8
      tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
  34. 23
      tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
  35. 10
      tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
  36. 40
      tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs
  37. 6
      tests/Avalonia.Styling.UnitTests/SelectorTests_Name.cs
  38. 15
      tests/Avalonia.Styling.UnitTests/SelectorTests_OfType.cs
  39. 11
      tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs
  40. 31
      tests/Avalonia.Styling.UnitTests/SelectorTests_Template.cs
  41. 4
      tools/packages.config

4
azure-pipelines.yml

@ -51,7 +51,9 @@ jobs:
- task: CmdLine@2
displayName: 'Install CastXML'
inputs:
script: brew install castxml
script: |
brew update
brew install castxml
- task: CmdLine@2
displayName: 'Install Nuke'

12
nukebuild/BuildParameters.cs

@ -14,13 +14,13 @@ using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
public partial class Build
{
[Parameter("configuration")]
public string NukeArgConfiguration { get; set; }
public string Configuration { get; set; }
[Parameter("skip-tests")]
public bool NukeArgSkipTests { get; set; }
public bool SkipTests { get; set; }
[Parameter("force-nuget-version")]
public string NukeArgForceNugetVersion { get; set; }
public string ForceNugetVersion { get; set; }
public class BuildParameters
{
@ -64,8 +64,8 @@ public partial class Build
public BuildParameters(Build b)
{
// ARGUMENTS
Configuration = b.NukeArgConfiguration ?? "Release";
SkipTests = b.NukeArgSkipTests;
Configuration = b.Configuration ?? "Release";
SkipTests = b.SkipTests;
// CONFIGURATION
MainRepo = "https://github.com/AvaloniaUI/Avalonia";
@ -102,7 +102,7 @@ public partial class Build
IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch;
// VERSION
Version = b.NukeArgForceNugetVersion ?? GetVersion();
Version = b.ForceNugetVersion ?? GetVersion();
if (IsRunningOnAzure)
{

129
parameters.cake

@ -1,129 +0,0 @@
using System.Xml.Linq;
using System.Linq;
public class Parameters
{
public string Configuration { get; private set; }
public bool SkipTests { get; private set; }
public string MainRepo { get; private set; }
public string MasterBranch { get; private set; }
public string ReleasePlatform { get; private set; }
public string ReleaseConfiguration { get; private set; }
public string ReleaseBranchPrefix { get; private set; }
public string MSBuildSolution { get; private set; }
public bool IsLocalBuild { get; private set; }
public bool IsRunningOnUnix { get; private set; }
public bool IsRunningOnWindows { get; private set; }
public bool IsRunningOnAppVeyor { get; private set; }
public bool IsRunningOnAzure { get; private set; }
public bool IsPullRequest { get; private set; }
public bool IsMainRepo { get; private set; }
public bool IsMasterBranch { get; private set; }
public bool IsReleaseBranch { get; private set; }
public bool IsTagged { get; private set; }
public bool IsReleasable { get; private set; }
public bool IsMyGetRelease { get; private set; }
public bool IsNuGetRelease { get; private set; }
public bool PublishTestResults { get; private set; }
public string Version { get; private set; }
public DirectoryPath ArtifactsDir { get; private set; }
public DirectoryPath NugetRoot { get; private set; }
public DirectoryPath ZipRoot { get; private set; }
public DirectoryPath BinRoot { get; private set; }
public DirectoryPath TestResultsRoot { get; private set; }
public string DirSuffix { get; private set; }
public DirectoryPathCollection BuildDirs { get; private set; }
public string FileZipSuffix { get; private set; }
public FilePath ZipCoreArtifacts { get; private set; }
public FilePath ZipNuGetArtifacts { get; private set; }
public DirectoryPath ZipSourceControlCatalogDesktopDirs { get; private set; }
public FilePath ZipTargetControlCatalogDesktopDirs { get; private set; }
public Parameters(ICakeContext context)
{
var buildSystem = context.BuildSystem();
// ARGUMENTS
Configuration = context.Argument("configuration", "Release");
SkipTests = context.HasArgument("skip-tests");
// CONFIGURATION
MainRepo = "https://github.com/AvaloniaUI/Avalonia";
MasterBranch = "master";
ReleaseBranchPrefix = "refs/heads/release/";
ReleaseConfiguration = "Release";
MSBuildSolution = "./dirs.proj";
// PARAMETERS
IsLocalBuild = buildSystem.IsLocalBuild;
IsRunningOnUnix = context.IsRunningOnUnix();
IsRunningOnWindows = context.IsRunningOnWindows();
IsRunningOnAppVeyor = buildSystem.AppVeyor.IsRunningOnAppVeyor;
IsRunningOnAzure = buildSystem.IsRunningOnVSTS || buildSystem.IsRunningOnTFS || context.EnvironmentVariable("LOGNAME") == "vsts";
IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest;
IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, context.EnvironmentVariable("BUILD_REPOSITORY_URI"));
IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, context.EnvironmentVariable("BUILD_SOURCEBRANCHNAME"));
IsReleaseBranch = (context.EnvironmentVariable("BUILD_SOURCEBRANCH")??"").StartsWith(ReleaseBranchPrefix, StringComparison.OrdinalIgnoreCase);
IsTagged = buildSystem.AppVeyor.Environment.Repository.Tag.IsTag
&& !string.IsNullOrWhiteSpace(buildSystem.AppVeyor.Environment.Repository.Tag.Name);
IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
IsMyGetRelease = !IsTagged && IsReleasable;
IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch;
// VERSION
Version = context.Argument("force-nuget-version", GetVersion());
if (IsRunningOnAppVeyor)
{
string tagVersion = null;
if (IsTagged)
{
var tag = buildSystem.AppVeyor.Environment.Repository.Tag.Name;
var nugetReleasePrefix = "nuget-release-";
IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix);
if(IsNuGetRelease)
tagVersion = tag.Substring(nugetReleasePrefix.Length);
}
if(tagVersion != null)
{
Version = tagVersion;
}
else
{
// Use AssemblyVersion with Build as version
Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta";
}
}
else if (IsRunningOnAzure)
{
if(!IsNuGetRelease)
{
// Use AssemblyVersion with Build as version
Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta";
}
PublishTestResults = true;
}
// DIRECTORIES
ArtifactsDir = (DirectoryPath)context.Directory("./artifacts");
NugetRoot = ArtifactsDir.Combine("nuget");
ZipRoot = ArtifactsDir.Combine("zip");
BinRoot = ArtifactsDir.Combine("bin");
TestResultsRoot = ArtifactsDir.Combine("test-results");
BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");
DirSuffix = Configuration;
FileZipSuffix = Version + ".zip";
ZipCoreArtifacts = ZipRoot.CombineWithFilePath("Avalonia-" + FileZipSuffix);
ZipNuGetArtifacts = ZipRoot.CombineWithFilePath("Avalonia-NuGet-" + FileZipSuffix);
ZipSourceControlCatalogDesktopDirs = (DirectoryPath)context.Directory("./samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461");
ZipTargetControlCatalogDesktopDirs = ZipRoot.CombineWithFilePath("ControlCatalog.Desktop-" + FileZipSuffix);
}
private static string GetVersion()
{
var xdoc = XDocument.Load("./build/SharedVersion.props");
return xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value;
}
}

1
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -147,6 +147,7 @@ namespace Avalonia.Data.Core
private void StopListening()
{
StopListeningCore();
_listening = false;
}
private BindingNotification TargetNullNotification()

2
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -52,7 +52,7 @@ namespace Avalonia.Controls.Primitives
internal Calendar Owner { get; set; }
internal CalendarDayButton CurrentButton { get; set; }
public static StyledProperty<IBrush> HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner<CalendarItem>();
public static readonly StyledProperty<IBrush> HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner<CalendarItem>();
public IBrush HeaderBackground
{
get { return GetValue(HeaderBackgroundProperty); }

11
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -517,7 +517,7 @@ namespace Avalonia.Controls.Presenters
if (index >= 0 && index < ItemCount)
{
if (index < FirstIndex)
if (index <= FirstIndex)
{
newOffset = index;
}
@ -525,10 +525,6 @@ namespace Avalonia.Controls.Presenters
{
newOffset = index - Math.Ceiling(ViewportValue - 1);
}
else if (OffsetValue + ViewportValue >= ItemCount)
{
newOffset = OffsetValue - 1;
}
if (newOffset != -1)
{
@ -546,6 +542,11 @@ namespace Avalonia.Controls.Presenters
{
layoutManager.ExecuteLayoutPass();
if (newOffset != -1 && newOffset != OffsetValue)
{
OffsetValue = newOffset;
}
if (panel.ScrollDirection == Orientation.Vertical)
{
if (container.Bounds.Y < panel.Bounds.Y || container.Bounds.Bottom > panel.Bounds.Bottom)

2
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.Primitives
// TODO: Need to track position of adorned elements and move the adorner if they move.
public class AdornerLayer : Panel, ICustomSimpleHitTest
{
public static AttachedProperty<Visual> AdornedElementProperty =
public static readonly AttachedProperty<Visual> AdornedElementProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Visual>("AdornedElement");
private static readonly AttachedProperty<AdornedElementInfo> s_adornedElementInfoProperty =

2
src/Avalonia.Controls/Viewbox.cs

@ -12,7 +12,7 @@ namespace Avalonia.Controls
/// <summary>
/// The stretch property
/// </summary>
public static AvaloniaProperty<Stretch> StretchProperty =
public static readonly AvaloniaProperty<Stretch> StretchProperty =
AvaloniaProperty.RegisterDirect<Viewbox, Stretch>(nameof(Stretch),
v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform);

2
src/Avalonia.Input/Cursors.cs

@ -47,7 +47,7 @@ namespace Avalonia.Input
public class Cursor
{
public static Cursor Default = new Cursor(StandardCursorType.Arrow);
public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow);
internal Cursor(IPlatformHandle platformCursor)
{

6
src/Avalonia.Input/DataFormats.cs

@ -5,11 +5,11 @@
/// <summary>
/// Dataformat for plaintext
/// </summary>
public static string Text = nameof(Text);
public static readonly string Text = nameof(Text);
/// <summary>
/// Dataformat for one or more filenames
/// </summary>
public static string FileNames = nameof(FileNames);
public static readonly string FileNames = nameof(FileNames);
}
}
}

10
src/Avalonia.Input/DragDrop.cs

@ -9,21 +9,21 @@ namespace Avalonia.Input
/// <summary>
/// Event which is raised, when a drag-and-drop operation enters the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DragEnterEvent = RoutedEvent.Register<DragEventArgs>("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop));
public static readonly RoutedEvent<DragEventArgs> DragEnterEvent = RoutedEvent.Register<DragEventArgs>("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation leaves the element.
/// </summary>
public static RoutedEvent<RoutedEventArgs> DragLeaveEvent = RoutedEvent.Register<RoutedEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
public static readonly RoutedEvent<RoutedEventArgs> DragLeaveEvent = RoutedEvent.Register<RoutedEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation is updated while over the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DragOverEvent = RoutedEvent.Register<DragEventArgs>("DragOver", RoutingStrategies.Bubble, typeof(DragDrop));
public static readonly RoutedEvent<DragEventArgs> DragOverEvent = RoutedEvent.Register<DragEventArgs>("DragOver", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation should complete over the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DropEvent = RoutedEvent.Register<DragEventArgs>("Drop", RoutingStrategies.Bubble, typeof(DragDrop));
public static readonly RoutedEvent<DragEventArgs> DropEvent = RoutedEvent.Register<DragEventArgs>("Drop", RoutingStrategies.Bubble, typeof(DragDrop));
public static AvaloniaProperty<bool> AllowDropProperty = AvaloniaProperty.RegisterAttached<Interactive, bool>("AllowDrop", typeof(DragDrop), inherits: true);
public static readonly AvaloniaProperty<bool> AllowDropProperty = AvaloniaProperty.RegisterAttached<Interactive, bool>("AllowDrop", typeof(DragDrop), inherits: true);
/// <summary>
/// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation.

20
src/Avalonia.Styling/Styling/ChildSelector.cs

@ -24,6 +24,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override bool InTemplate => _parent.InTemplate;
/// <inheritdoc/>
public override bool IsCombinator => true;
/// <inheritdoc/>
public override Type TargetType => null;
@ -43,11 +46,24 @@ namespace Avalonia.Styling
if (controlParent != null)
{
return _parent.Match((IStyleable)controlParent, subscribe);
var parentMatch = _parent.Match((IStyleable)controlParent, subscribe);
if (parentMatch.Result == SelectorMatchResult.Sometimes)
{
return parentMatch;
}
else if (parentMatch.IsMatch)
{
return SelectorMatch.AlwaysThisInstance;
}
else
{
return SelectorMatch.NeverThisInstance;
}
}
else
{
return SelectorMatch.False;
return SelectorMatch.NeverThisInstance;
}
}

16
src/Avalonia.Styling/Styling/DescendentSelector.cs

@ -22,6 +22,9 @@ namespace Avalonia.Styling
_parent = parent;
}
/// <inheritdoc/>
public override bool IsCombinator => true;
/// <inheritdoc/>
public override bool InTemplate => _parent.InTemplate;
@ -51,16 +54,13 @@ namespace Avalonia.Styling
{
var match = _parent.Match((IStyleable)c, subscribe);
if (match.ImmediateResult != null)
if (match.Result == SelectorMatchResult.Sometimes)
{
if (match.ImmediateResult == true)
{
return SelectorMatch.True;
}
descendantMatches.Add(match.Activator);
}
else
else if (match.IsMatch)
{
descendantMatches.Add(match.ObservableResult);
return SelectorMatch.AlwaysThisInstance;
}
}
}
@ -71,7 +71,7 @@ namespace Avalonia.Styling
}
else
{
return SelectorMatch.False;
return SelectorMatch.NeverThisInstance;
}
}

7
src/Avalonia.Styling/Styling/IStyle.cs

@ -17,7 +17,12 @@ namespace Avalonia.Styling
/// <param name="container">
/// The control that contains this style. May be null.
/// </param>
void Attach(IStyleable control, IStyleHost container);
/// <returns>
/// True if the style can match a control of type <paramref name="control"/>
/// (even if it does not match this control specifically); false if the style
/// can never match.
/// </returns>
bool Attach(IStyleable control, IStyleHost container);
void Detach();
}

12
src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs

@ -30,6 +30,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override bool InTemplate => _previous?.InTemplate ?? false;
/// <inheritdoc/>
public override bool IsCombinator => false;
/// <summary>
/// Gets the name of the control to match.
/// </summary>
@ -72,17 +75,14 @@ namespace Avalonia.Styling
/// <inheritdoc/>
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
{
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(control, _property))
{
return SelectorMatch.False;
}
else if (subscribe)
if (subscribe)
{
return new SelectorMatch(control.GetObservable(_property).Select(v => Equals(v ?? string.Empty, _value)));
}
else
{
return new SelectorMatch((control.GetValue(_property) ?? string.Empty).Equals(_value));
var result = (control.GetValue(_property) ?? string.Empty).Equals(_value);
return result ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
}
}

39
src/Avalonia.Styling/Styling/Selector.cs

@ -17,6 +17,14 @@ namespace Avalonia.Styling
/// </summary>
public abstract bool InTemplate { get; }
/// <summary>
/// Gets a value indicating whether this selector is a combinator.
/// </summary>
/// <remarks>
/// A combinator is a selector such as Child or Descendent which links simple selectors.
/// </remarks>
public abstract bool IsCombinator { get; }
/// <summary>
/// Gets the target type of the selector, if available.
/// </summary>
@ -33,25 +41,32 @@ namespace Avalonia.Styling
/// <returns>A <see cref="SelectorMatch"/>.</returns>
public SelectorMatch Match(IStyleable control, bool subscribe = true)
{
List<IObservable<bool>> inputs = new List<IObservable<bool>>();
Selector selector = this;
var inputs = new List<IObservable<bool>>();
var selector = this;
var alwaysThisType = true;
var hitCombinator = false;
while (selector != null)
{
if (selector.InTemplate && control.TemplatedParent == null)
{
return SelectorMatch.False;
}
hitCombinator |= selector.IsCombinator;
var match = selector.Evaluate(control, subscribe);
if (match.ImmediateResult == false)
if (!match.IsMatch)
{
return hitCombinator ? SelectorMatch.NeverThisInstance : match;
}
else if (selector.InTemplate && control.TemplatedParent == null)
{
return SelectorMatch.NeverThisInstance;
}
else if (match.Result == SelectorMatchResult.AlwaysThisInstance)
{
return match;
alwaysThisType = false;
}
else if (match.ObservableResult != null)
else if (match.Result == SelectorMatchResult.Sometimes)
{
inputs.Add(match.ObservableResult);
inputs.Add(match.Activator);
}
selector = selector.MovePrevious();
@ -63,7 +78,9 @@ namespace Avalonia.Styling
}
else
{
return SelectorMatch.True;
return alwaysThisType && !hitCombinator ?
SelectorMatch.AlwaysThisType :
SelectorMatch.AlwaysThisInstance;
}
}

86
src/Avalonia.Styling/Styling/SelectorMatch.cs

@ -5,51 +5,95 @@ using System;
namespace Avalonia.Styling
{
/// <summary>
/// Describes how a <see cref="SelectorMatch"/> matches a control and its type.
/// </summary>
public enum SelectorMatchResult
{
/// <summary>
/// The selector never matches this type.
/// </summary>
NeverThisType,
/// <summary>
/// The selector never matches this instance, but can match this type.
/// </summary>
NeverThisInstance,
/// <summary>
/// The selector always matches this type.
/// </summary>
AlwaysThisType,
/// <summary>
/// The selector always matches this instance, but doesn't always match this type.
/// </summary>
AlwaysThisInstance,
/// <summary>
/// The selector matches this instance based on the <see cref="SelectorMatch.Activator"/>.
/// </summary>
Sometimes,
}
/// <summary>
/// Holds the result of a <see cref="Selector"/> match.
/// </summary>
/// <remarks>
/// There are two types of selectors - ones whose match can never change for a particular
/// control (such as <see cref="Selectors.OfType(Selector, Type)"/>) and ones whose result can
/// change over time (such as <see cref="Selectors.Class(Selector, string)"/>. For the first
/// category of selectors, the value of <see cref="ImmediateResult"/> will be set but for the
/// second, <see cref="ImmediateResult"/> will be null and <see cref="ObservableResult"/> will
/// hold an observable which tracks the match.
/// A selector match describes whether and how a <see cref="Selector"/> matches a control, and
/// in addition whether the selector can ever match a control of the same type.
/// </remarks>
public class SelectorMatch
{
public static readonly SelectorMatch False = new SelectorMatch(false);
/// <summary>
/// A selector match with the result of <see cref="SelectorMatchResult.NeverThisType"/>.
/// </summary>
public static readonly SelectorMatch NeverThisType = new SelectorMatch(SelectorMatchResult.NeverThisType);
public static readonly SelectorMatch True = new SelectorMatch(true);
/// <summary>
/// A selector match with the result of <see cref="SelectorMatchResult.NeverThisInstance"/>.
/// </summary>
public static readonly SelectorMatch NeverThisInstance = new SelectorMatch(SelectorMatchResult.NeverThisInstance);
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class.
/// A selector match with the result of <see cref="SelectorMatchResult.AlwaysThisType"/>.
/// </summary>
/// <param name="match">The immediate match value.</param>
public SelectorMatch(bool match)
{
ImmediateResult = match;
}
public static readonly SelectorMatch AlwaysThisType = new SelectorMatch(SelectorMatchResult.AlwaysThisType);
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class.
/// Gets a selector match with the result of <see cref="SelectorMatchResult.AlwaysThisInstance"/>.
/// </summary>
/// <param name="match">The observable match value.</param>
public static readonly SelectorMatch AlwaysThisInstance = new SelectorMatch(SelectorMatchResult.AlwaysThisInstance);
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class with a
/// <see cref="SelectorMatchResult.Sometimes"/> result.
/// </summary>
/// <param name="match">The match activator.</param>
public SelectorMatch(IObservable<bool> match)
{
ObservableResult = match;
Contract.Requires<ArgumentNullException>(match != null);
Result = SelectorMatchResult.Sometimes;
Activator = match;
}
private SelectorMatch(SelectorMatchResult result) => Result = result;
/// <summary>
/// Gets the immediate result of the selector match, in the case of selectors that cannot
/// change over time.
/// Gets a value indicating whether the match was positive.
/// </summary>
public bool IsMatch => Result >= SelectorMatchResult.AlwaysThisType;
/// <summary>
/// Gets the result of the match.
/// </summary>
public bool? ImmediateResult { get; }
public SelectorMatchResult Result { get; }
/// <summary>
/// Gets an observable which tracks the selector match, in the case of selectors that can
/// change over time.
/// </summary>
public IObservable<bool> ObservableResult { get; }
public IObservable<bool> Activator { get; }
}
}

24
src/Avalonia.Styling/Styling/Style.cs

@ -106,20 +106,14 @@ namespace Avalonia.Styling
/// <inheritdoc/>
bool IResourceProvider.HasResources => _resources?.Count > 0;
/// <summary>
/// Attaches the style to a control if the style's selector matches.
/// </summary>
/// <param name="control">The control to attach to.</param>
/// <param name="container">
/// The control that contains this style. May be null.
/// </param>
public void Attach(IStyleable control, IStyleHost container)
/// <inheritdoc/>
public bool Attach(IStyleable control, IStyleHost container)
{
if (Selector != null)
{
var match = Selector.Match(control);
if (match.ImmediateResult != false)
if (match.IsMatch)
{
var controlSubscriptions = GetSubscriptions(control);
@ -129,9 +123,10 @@ namespace Avalonia.Styling
{
foreach (var animation in Animations)
{
IObservable<bool> obsMatch = match.ObservableResult;
var obsMatch = match.Activator;
if (match.ImmediateResult == true)
if (match.Result == SelectorMatchResult.AlwaysThisType ||
match.Result == SelectorMatchResult.AlwaysThisInstance)
{
obsMatch = Observable.Return(true);
}
@ -143,13 +138,15 @@ namespace Avalonia.Styling
foreach (var setter in Setters)
{
var sub = setter.Apply(this, control, match.ObservableResult);
var sub = setter.Apply(this, control, match.Activator);
subs.Add(sub);
}
controlSubscriptions.Add(subs);
Subscriptions.Add(subs);
}
return match.Result != SelectorMatchResult.NeverThisType;
}
else if (control == container)
{
@ -165,7 +162,10 @@ namespace Avalonia.Styling
controlSubscriptions.Add(subs);
Subscriptions.Add(subs);
return true;
}
return false;
}
/// <inheritdoc/>

45
src/Avalonia.Styling/Styling/Styles.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
@ -15,6 +16,7 @@ namespace Avalonia.Styling
{
private IResourceNode _parent;
private IResourceDictionary _resources;
private Dictionary<Type, List<IStyle>> _cache;
public Styles()
{
@ -34,6 +36,7 @@ namespace Avalonia.Styling
}
x.ResourcesChanged += SubResourceChanged;
_cache = null;
},
x =>
{
@ -49,6 +52,7 @@ namespace Avalonia.Styling
}
x.ResourcesChanged -= SubResourceChanged;
_cache = null;
},
() => { });
}
@ -97,11 +101,46 @@ namespace Avalonia.Styling
/// <param name="container">
/// The control that contains this style. May be null.
/// </param>
public void Attach(IStyleable control, IStyleHost container)
public bool Attach(IStyleable control, IStyleHost container)
{
foreach (IStyle style in this)
if (_cache == null)
{
_cache = new Dictionary<Type, List<IStyle>>();
}
if (_cache.TryGetValue(control.StyleKey, out var cached))
{
if (cached != null)
{
foreach (var style in cached)
{
style.Attach(control, container);
}
return true;
}
return false;
}
else
{
style.Attach(control, container);
List<IStyle> result = null;
foreach (var style in this)
{
if (style.Attach(control, container))
{
if (result == null)
{
result = new List<IStyle>();
}
result.Add(style);
}
}
_cache.Add(control.StyleKey, result);
return result != null;
}
}

5
src/Avalonia.Styling/Styling/TemplateSelector.cs

@ -23,6 +23,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override bool InTemplate => true;
/// <inheritdoc/>
public override bool IsCombinator => true;
/// <inheritdoc/>
public override Type TargetType => null;
@ -46,7 +49,7 @@ namespace Avalonia.Styling
"Cannot call Template selector on control with null TemplatedParent.");
}
return _parent.Match(templatedParent, subscribe) ?? SelectorMatch.True;
return _parent.Match(templatedParent, subscribe);
}
protected override Selector MovePrevious() => null;

19
src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs

@ -68,6 +68,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override Type TargetType => _targetType ?? _previous?.TargetType;
/// <inheritdoc/>
public override bool IsCombinator => false;
/// <summary>
/// Whether the selector matches the concrete <see cref="TargetType"/> or any object which
/// implements <see cref="TargetType"/>.
@ -101,21 +104,23 @@ namespace Avalonia.Styling
{
if (controlType != TargetType)
{
return SelectorMatch.False;
return SelectorMatch.NeverThisType;
}
}
else
{
if (!TargetType.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo()))
{
return SelectorMatch.False;
return SelectorMatch.NeverThisType;
}
}
}
if (Name != null && control.Name != Name)
if (Name != null)
{
return SelectorMatch.False;
return control.Name == Name ?
SelectorMatch.AlwaysThisInstance :
SelectorMatch.NeverThisInstance;
}
if (_classes.IsValueCreated && _classes.Value.Count > 0)
@ -127,12 +132,14 @@ namespace Avalonia.Styling
}
else
{
return new SelectorMatch(Matches(control.Classes));
return Matches(control.Classes) ?
SelectorMatch.AlwaysThisInstance :
SelectorMatch.NeverThisInstance;
}
}
else
{
return SelectorMatch.True;
return SelectorMatch.AlwaysThisType;
}
}

6
src/Avalonia.Visuals/Matrix.cs

@ -293,13 +293,13 @@ namespace Avalonia
/// <returns>The inverted matrix.</returns>
public Matrix Invert()
{
if (GetDeterminant() == 0)
double d = GetDeterminant();
if (d == 0)
{
throw new InvalidOperationException("Transform is not invertible.");
}
double d = GetDeterminant();
return new Matrix(
_m22 / d,
-_m12 / d,

4
src/Avalonia.Visuals/Media/GradientStop.cs

@ -11,13 +11,13 @@ namespace Avalonia.Media
/// <summary>
/// Describes the <see cref="Offset"/> property.
/// </summary>
public static StyledProperty<double> OffsetProperty =
public static readonly StyledProperty<double> OffsetProperty =
AvaloniaProperty.Register<GradientStop, double>(nameof(Offset));
/// <summary>
/// Describes the <see cref="Color"/> property.
/// </summary>
public static StyledProperty<Color> ColorProperty =
public static readonly StyledProperty<Color> ColorProperty =
AvaloniaProperty.Register<GradientStop, Color>(nameof(Color));
/// <summary>

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

@ -7,7 +7,7 @@ namespace Avalonia.Media
/// </summary>
public class Typeface
{
public static Typeface Default = new Typeface(FontFamily.Default);
public static readonly Typeface Default = new Typeface(FontFamily.Default);
/// <summary>
/// Initializes a new instance of the <see cref="Typeface"/> class.

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -11,6 +11,7 @@
<Compile Include="Converters\AvaloniaUriTypeConverter.cs" />
<Compile Include="Converters\FontFamilyTypeConverter.cs" />
<Compile Include="Converters\MemberSelectorTypeConverter.cs" />
<Compile Include="Converters\NullableTypeConverter.cs" />
<Compile Include="Converters\ParseTypeConverter.cs" />
<Compile Include="Converters\SetterValueTypeConverter.cs" />
<Compile Include="Converters\TimeSpanTypeConverter.cs" />

8
src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs

@ -51,6 +51,14 @@ namespace Avalonia.Markup.Xaml
/// <returns>The type converter.</returns>
public static Type GetTypeConverter(Type type)
{
if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
var inner = GetTypeConverter(type.GetGenericArguments()[0]);
if (inner == null)
return null;
return typeof(NullableTypeConverter<>).MakeGenericType(inner);
}
if (_converters.TryGetValue(type, out var result))
{
return result;

89
src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs

@ -0,0 +1,89 @@
using System;
using System.Collections;
using System.ComponentModel;
using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters
{
public class NullableTypeConverter<T> : TypeConverter where T : TypeConverter, new()
{
private TypeConverter _inner;
public NullableTypeConverter()
{
_inner = new T();
}
public NullableTypeConverter(TypeConverter inner)
{
_inner = inner;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value == null)
return null;
return _inner.ConvertTo(context, culture, value, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
return null;
if (value as string == "")
return null;
return _inner.ConvertFrom(context, culture, value);
}
public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
{
return _inner.CreateInstance(context, propertyValues);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return _inner.GetStandardValuesSupported(context);
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return _inner.GetStandardValuesExclusive(context);
}
public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
{
return _inner.GetCreateInstanceSupported(context);
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return _inner.GetPropertiesSupported(context);
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return _inner.GetStandardValues(context);
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
return _inner.GetProperties(context, value, attributes);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return _inner.CanConvertTo(context, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return _inner.CanConvertFrom(context, sourceType);
}
public override bool IsValid(ITypeDescriptorContext context, object value)
{
return _inner.IsValid(context, value);
}
}
}

6
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -62,12 +62,14 @@ namespace Avalonia.Markup.Xaml.Styling
IResourceNode IResourceNode.ResourceParent => _parent;
/// <inheritdoc/>
public void Attach(IStyleable control, IStyleHost container)
public bool Attach(IStyleable control, IStyleHost container)
{
if (Source != null)
{
Loaded.Attach(control, container);
return Loaded.Attach(control, container);
}
return false;
}
public void Detach()

17
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -153,17 +153,24 @@ namespace Avalonia.Win32
public void Resize(Size value)
{
if (value != ClientSize)
int requestedClientWidth = (int)(value.Width * Scaling);
int requestedClientHeight = (int)(value.Height * Scaling);
UnmanagedMethods.RECT clientRect;
UnmanagedMethods.GetClientRect(_hwnd, out clientRect);
// do comparison after scaling to avoid rounding issues
if (requestedClientWidth != clientRect.Width || requestedClientHeight != clientRect.Height)
{
value *= Scaling;
UnmanagedMethods.RECT windowRect;
UnmanagedMethods.GetWindowRect(_hwnd, out windowRect);
UnmanagedMethods.SetWindowPos(
_hwnd,
IntPtr.Zero,
0,
0,
(int)value.Width,
(int)value.Height,
requestedClientWidth + (windowRect.Width - clientRect.Width),
requestedClientHeight + (windowRect.Height - clientRect.Height),
UnmanagedMethods.SetWindowPosFlags.SWP_RESIZE);
}
}

13
tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs

@ -568,6 +568,19 @@ namespace Avalonia.Base.UnitTests.Data.Core
Assert.Null(result.Item2.Target);
}
[Fact]
public void Should_Not_Throw_Exception_On_Unsubscribe_When_Already_Unsubscribed()
{
var source = new Class1 { Foo = "foo" };
var target = new PropertyAccessorNode("Foo", false);
Assert.NotNull(target);
target.Target = new WeakReference(source);
target.Subscribe(_ => { });
target.Unsubscribe();
target.Unsubscribe();
Assert.True(true);
}
private interface INext
{
int PropertyChangedSubscriptionCount { get; }

41
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs

@ -717,6 +717,45 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(10, target.Panel.Children.Count);
}
[Fact]
public void Scroll_To_Last_Should_Work()
{
var target = CreateTarget(itemCount: 11);
var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
var last = (target.Items as IList)[10];
target.ScrollIntoView(last);
Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset);
Assert.Same(target.Panel.Children[9].DataContext, last);
}
[Fact]
public void Second_Scroll_To_Last_Should_Work()
{
var target = CreateTarget(itemCount: 11);
var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
var last = (target.Items as IList)[10];
target.ScrollIntoView(last);
Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset);
Assert.Same(target.Panel.Children[9].DataContext, last);
target.ScrollIntoView(last);
Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset);
Assert.Same(target.Panel.Children[9].DataContext, last);
}
public class Vertical
{
[Fact]
@ -1090,4 +1129,4 @@ namespace Avalonia.Controls.UnitTests.Presenters
}
}
}
}
}

31
tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs

@ -0,0 +1,31 @@
using Avalonia.Controls;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Converters
{
public class ClassWithNullableProperties
{
public Thickness? Thickness { get; set; }
public Orientation? Orientation { get; set; }
}
public class NullableConverterTests
{
[Fact]
public void Nullable_Types_Should_Still_Be_Converted_Properly()
{
using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
{
var xaml = @"<ClassWithNullableProperties
xmlns='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Converters'
Thickness = '5' Orientation='Vertical'
></ClassWithNullableProperties>";
var loader = new AvaloniaXamlLoader();
var data = (ClassWithNullableProperties)loader.Load(xaml, typeof(ClassWithNullableProperties).Assembly);
Assert.Equal(new Thickness(5), data.Thickness);
Assert.Equal(Orientation.Vertical, data.Orientation);
}
}
}
}

8
tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs

@ -27,7 +27,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType<TestLogical1>().Child().OfType<TestLogical2>();
Assert.True(selector.Match(child).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result);
}
[Fact]
@ -42,7 +42,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType<TestLogical1>().Child().OfType<TestLogical3>();
Assert.False(selector.Match(child).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(child).Result);
}
[Fact]
@ -54,7 +54,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType<TestLogical1>().Class("foo").Child().OfType<TestLogical2>();
var activator = selector.Match(child).ObservableResult;
var activator = selector.Match(child).Activator;
var result = new List<bool>();
Assert.False(await activator.Take(1));
@ -70,7 +70,7 @@ namespace Avalonia.Styling.UnitTests
var control = new TestLogical3();
var selector = default(Selector).OfType<TestLogical1>().Child().OfType<TestLogical3>();
Assert.False(selector.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(control).Result);
}
[Fact]

23
tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs

@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var match = target.Match(control);
Assert.True(await activator.Take(1));
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
Assert.True(await match.Activator.Take(1));
}
[Fact]
@ -54,9 +55,10 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var match = target.Match(control);
Assert.False(await activator.Take(1));
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
Assert.False(await match.Activator.Take(1));
}
[Fact]
@ -69,9 +71,10 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var match = target.Match(control);
Assert.True(await activator.Take(1));
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
Assert.True(await match.Activator.Take(1));
}
[Fact]
@ -80,7 +83,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1();
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var activator = target.Match(control).Activator;
Assert.False(await activator.Take(1));
control.Classes.Add("foo");
@ -96,7 +99,7 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var activator = target.Match(control).Activator;
Assert.True(await activator.Take(1));
control.Classes.Remove("foo");
@ -108,7 +111,7 @@ namespace Avalonia.Styling.UnitTests
{
var control = new Control1();
var target = default(Selector).Class("foo").Class("bar");
var activator = target.Match(control).ObservableResult;
var activator = target.Match(control).Activator;
Assert.False(await activator.Take(1));
control.Classes.Add("foo");
@ -129,7 +132,7 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var activator = target.Match(control).Activator;
var result = new List<bool>();
using (activator.Subscribe(x => result.Add(x)))

10
tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs

@ -26,7 +26,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType<TestLogical1>().Descendant().OfType<TestLogical2>();
Assert.True(selector.Match(child).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result);
}
[Fact]
@ -41,7 +41,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType<TestLogical1>().Descendant().OfType<TestLogical3>();
Assert.True(selector.Match(child).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result);
}
[Fact]
@ -56,7 +56,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType<TestLogical1>().Class("foo").Descendant().OfType<TestLogical3>();
var activator = selector.Match(child).ObservableResult;
var activator = selector.Match(child).Activator;
Assert.True(await activator.Take(1));
}
@ -74,7 +74,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType<TestLogical1>().Class("foo").Descendant().OfType<TestLogical3>();
var activator = selector.Match(child).ObservableResult;
var activator = selector.Match(child).Activator;
Assert.False(await activator.Take(1));
}
@ -90,7 +90,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType<TestLogical1>().Class("foo").Descendant().OfType<TestLogical3>();
var activator = selector.Match(child).ObservableResult;
var activator = selector.Match(child).Activator;
Assert.False(await activator.Take(1));
parent.Classes.Add("foo");

40
tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling.UnitTests
public class SelectorTests_Multiple
{
[Fact]
public void Template_Child_Of_Control_With_Two_Classes()
public void Named_Template_Child_Of_Control_With_Two_Classes()
{
var template = new FuncControlTemplate(parent =>
{
@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests
var border = (Border)((IVisual)control).VisualChildren.Single();
var values = new List<bool>();
var activator = selector.Match(border).ObservableResult;
var match = selector.Match(border);
activator.Subscribe(x => values.Add(x));
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
match.Activator.Subscribe(x => values.Add(x));
Assert.Equal(new[] { false }, values);
control.Classes.AddRange(new[] { "foo", "bar" });
@ -51,6 +52,39 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal(new[] { false, true, false }, values);
}
[Fact]
public void Named_OfType_Template_Child_Of_Control_With_Two_Classes_Wrong_Type()
{
var template = new FuncControlTemplate(parent =>
{
return new Border
{
Name = "border",
};
});
var control = new Button
{
Template = template,
};
control.ApplyTemplate();
var selector = default(Selector)
.OfType<Button>()
.Class("foo")
.Class("bar")
.Template()
.OfType<TextBlock>()
.Name("baz");
var border = (Border)((IVisual)control).VisualChildren.Single();
var values = new List<bool>();
var match = selector.Match(border);
Assert.Equal(SelectorMatchResult.NeverThisType, match.Result);
}
[Fact]
public void TargetType_OfType()
{

6
tests/Avalonia.Styling.UnitTests/SelectorTests_Name.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1 { Name = "foo" };
var target = default(Selector).Name("foo");
Assert.True(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(control).Result);
}
[Fact]
@ -23,7 +23,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1 { Name = "foo" };
var target = default(Selector).Name("bar");
Assert.False(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(control).Result);
}
[Fact]
@ -33,7 +33,7 @@ namespace Avalonia.Styling.UnitTests
var target = default(Selector).Name("foo");
var activator = target.Match(control);
Assert.False(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(control).Result);
}
[Fact]

15
tests/Avalonia.Styling.UnitTests/SelectorTests_OfType.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1();
var target = default(Selector).OfType<Control1>();
Assert.True(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisType, target.Match(control).Result);
}
[Fact]
@ -23,7 +23,16 @@ namespace Avalonia.Styling.UnitTests
var control = new Control2();
var target = default(Selector).OfType<Control1>();
Assert.False(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(control).Result);
}
[Fact]
public void OfType_Class_Doesnt_Match_Control_Of_Wrong_Type()
{
var control = new Control2();
var target = default(Selector).OfType<Control1>().Class("foo");
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(control).Result);
}
[Fact]
@ -32,7 +41,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1 { TemplatedParent = new Mock<ITemplatedControl>().Object };
var target = default(Selector).OfType<Control1>();
Assert.True(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisType, target.Match(control).Result);
}
public class Control1 : TestControlBase

11
tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs

@ -16,7 +16,7 @@ namespace Avalonia.Styling.UnitTests
{
var control = new TextBlock();
var target = default(Selector).PropertyEquals(TextBlock.TextProperty, "foo");
var activator = target.Match(control).ObservableResult;
var activator = target.Match(control).Activator;
Assert.False(await activator.Take(1));
control.Text = "foo";
@ -25,6 +25,15 @@ namespace Avalonia.Styling.UnitTests
Assert.False(await activator.Take(1));
}
[Fact]
public void OfType_PropertyEquals_Doesnt_Match_Control_Of_Wrong_Type()
{
var control = new TextBlock();
var target = default(Selector).OfType<Border>().PropertyEquals(TextBlock.TextProperty, "foo");
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(control).Result);
}
[Fact]
public void PropertyEquals_Selector_Should_Have_Correct_String_Representation()
{

31
tests/Avalonia.Styling.UnitTests/SelectorTests_Template.cs

@ -29,7 +29,7 @@ namespace Avalonia.Styling.UnitTests
.Template()
.OfType<Border>();
Assert.True(selector.Match(border).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(border).Result);
}
[Fact]
@ -47,7 +47,24 @@ namespace Avalonia.Styling.UnitTests
.Template()
.OfType<Border>();
Assert.False(selector.Match(border).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(border).Result);
}
[Fact]
public void Control_In_Template_Of_Wrong_Type_Is_Not_Matched_With_Template_Selector()
{
var target = new Mock<IVisual>();
var templatedControl = target.As<ITemplatedControl>();
var styleable = target.As<IStyleable>();
BuildVisualTree(target);
var border = (Border)target.Object.GetVisualChildren().Single();
var selector = default(Selector)
.OfType<Button>()
.Template()
.OfType<Border>();
Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(border).Result);
}
[Fact]
@ -64,7 +81,7 @@ namespace Avalonia.Styling.UnitTests
.Template()
.OfType<TextBlock>();
Assert.True(selector.Match(textBlock).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(textBlock).Result);
}
[Fact]
@ -80,7 +97,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType(styleKey).Template().OfType<Border>();
Assert.True(selector.Match(border).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(border).Result);
}
[Fact]
@ -96,7 +113,7 @@ namespace Avalonia.Styling.UnitTests
styleable.Setup(x => x.Classes).Returns(new Classes("foo"));
var border = (Border)target.Object.VisualChildren.Single();
var selector = default(Selector).OfType(styleKey).Class("foo").Template().OfType<Border>();
var activator = selector.Match(border).ObservableResult;
var activator = selector.Match(border).Activator;
Assert.True(await activator.Take(1));
}
@ -112,7 +129,7 @@ namespace Avalonia.Styling.UnitTests
styleable.Setup(x => x.Classes).Returns(new Classes("bar"));
var border = (Border)target.Object.VisualChildren.Single();
var selector = default(Selector).OfType(templatedControl.Object.GetType()).Class("foo").Template().OfType<Border>();
var activator = selector.Match(border).ObservableResult;
var activator = selector.Match(border).Activator;
Assert.False(await activator.Take(1));
}
@ -128,7 +145,7 @@ namespace Avalonia.Styling.UnitTests
styleable.Setup(x => x.Classes).Returns(new Classes("foo"));
var border = (Border)target.Object.VisualChildren.Single();
var selector = default(Selector).OfType(templatedControl.Object.GetType()).Class("foo").Template().OfType<Border>();
var activator = selector.Match(border).ObservableResult;
var activator = selector.Match(border).Activator;
var inccDebug = (INotifyCollectionChangedDebug)styleable.Object.Classes;
using (activator.Subscribe(_ => { }))

4
tools/packages.config

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Cake" version="0.30.0" />
</packages>
Loading…
Cancel
Save