diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8c5380e65e..bcd0082c6f 100644 --- a/azure-pipelines.yml +++ b/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' diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs index 5f8ec7267e..afd1950859 100644 --- a/nukebuild/BuildParameters.cs +++ b/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) { diff --git a/parameters.cake b/parameters.cake deleted file mode 100644 index 4ef7e8e05a..0000000000 --- a/parameters.cake +++ /dev/null @@ -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; - } -} diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index f2f3ed9bfc..8a2dd46b86 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -147,6 +147,7 @@ namespace Avalonia.Data.Core private void StopListening() { StopListeningCore(); + _listening = false; } private BindingNotification TargetNullNotification() diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 577555333f..fb6dacaf81 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/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 HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); + public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); public IBrush HeaderBackground { get { return GetValue(HeaderBackgroundProperty); } diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index b177dc50cf..f72a65ead2 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/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) diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 5308a062ec..ed590679cd 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/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 AdornedElementProperty = + public static readonly AttachedProperty AdornedElementProperty = AvaloniaProperty.RegisterAttached("AdornedElement"); private static readonly AttachedProperty s_adornedElementInfoProperty = diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index db753f4ab4..3a070d07b2 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls /// /// The stretch property /// - public static AvaloniaProperty StretchProperty = + public static readonly AvaloniaProperty StretchProperty = AvaloniaProperty.RegisterDirect(nameof(Stretch), v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform); diff --git a/src/Avalonia.Input/Cursors.cs b/src/Avalonia.Input/Cursors.cs index 6f67f3b08c..d3618f30f3 100644 --- a/src/Avalonia.Input/Cursors.cs +++ b/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) { diff --git a/src/Avalonia.Input/DataFormats.cs b/src/Avalonia.Input/DataFormats.cs index 559d2cb643..cf5a6592e1 100644 --- a/src/Avalonia.Input/DataFormats.cs +++ b/src/Avalonia.Input/DataFormats.cs @@ -5,11 +5,11 @@ /// /// Dataformat for plaintext /// - public static string Text = nameof(Text); + public static readonly string Text = nameof(Text); /// /// Dataformat for one or more filenames /// - public static string FileNames = nameof(FileNames); + public static readonly string FileNames = nameof(FileNames); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Input/DragDrop.cs b/src/Avalonia.Input/DragDrop.cs index b5aba55f6c..c58b764b1d 100644 --- a/src/Avalonia.Input/DragDrop.cs +++ b/src/Avalonia.Input/DragDrop.cs @@ -9,21 +9,21 @@ namespace Avalonia.Input /// /// Event which is raised, when a drag-and-drop operation enters the element. /// - public static RoutedEvent DragEnterEvent = RoutedEvent.Register("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragEnterEvent = RoutedEvent.Register("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation leaves the element. /// - public static RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation is updated while over the element. /// - public static RoutedEvent DragOverEvent = RoutedEvent.Register("DragOver", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragOverEvent = RoutedEvent.Register("DragOver", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation should complete over the element. /// - public static RoutedEvent DropEvent = RoutedEvent.Register("Drop", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DropEvent = RoutedEvent.Register("Drop", RoutingStrategies.Bubble, typeof(DragDrop)); - public static AvaloniaProperty AllowDropProperty = AvaloniaProperty.RegisterAttached("AllowDrop", typeof(DragDrop), inherits: true); + public static readonly AvaloniaProperty AllowDropProperty = AvaloniaProperty.RegisterAttached("AllowDrop", typeof(DragDrop), inherits: true); /// /// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation. diff --git a/src/Avalonia.Styling/Styling/ChildSelector.cs b/src/Avalonia.Styling/Styling/ChildSelector.cs index bcbf358721..8b1d2a8553 100644 --- a/src/Avalonia.Styling/Styling/ChildSelector.cs +++ b/src/Avalonia.Styling/Styling/ChildSelector.cs @@ -24,6 +24,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => _parent.InTemplate; + /// + public override bool IsCombinator => true; + /// 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; } } diff --git a/src/Avalonia.Styling/Styling/DescendentSelector.cs b/src/Avalonia.Styling/Styling/DescendentSelector.cs index 943b3f0161..a81908f23d 100644 --- a/src/Avalonia.Styling/Styling/DescendentSelector.cs +++ b/src/Avalonia.Styling/Styling/DescendentSelector.cs @@ -22,6 +22,9 @@ namespace Avalonia.Styling _parent = parent; } + /// + public override bool IsCombinator => true; + /// 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; } } diff --git a/src/Avalonia.Styling/Styling/IStyle.cs b/src/Avalonia.Styling/Styling/IStyle.cs index 3cf8491ae3..da2a08f04d 100644 --- a/src/Avalonia.Styling/Styling/IStyle.cs +++ b/src/Avalonia.Styling/Styling/IStyle.cs @@ -17,7 +17,12 @@ namespace Avalonia.Styling /// /// The control that contains this style. May be null. /// - void Attach(IStyleable control, IStyleHost container); + /// + /// True if the style can match a control of type + /// (even if it does not match this control specifically); false if the style + /// can never match. + /// + bool Attach(IStyleable control, IStyleHost container); void Detach(); } diff --git a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs index 25f12ffa57..cfc0998fe0 100644 --- a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs @@ -30,6 +30,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => _previous?.InTemplate ?? false; + /// + public override bool IsCombinator => false; + /// /// Gets the name of the control to match. /// @@ -72,17 +75,14 @@ namespace Avalonia.Styling /// 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; } } diff --git a/src/Avalonia.Styling/Styling/Selector.cs b/src/Avalonia.Styling/Styling/Selector.cs index 24c6be9b7a..af209ea970 100644 --- a/src/Avalonia.Styling/Styling/Selector.cs +++ b/src/Avalonia.Styling/Styling/Selector.cs @@ -17,6 +17,14 @@ namespace Avalonia.Styling /// public abstract bool InTemplate { get; } + /// + /// Gets a value indicating whether this selector is a combinator. + /// + /// + /// A combinator is a selector such as Child or Descendent which links simple selectors. + /// + public abstract bool IsCombinator { get; } + /// /// Gets the target type of the selector, if available. /// @@ -33,25 +41,32 @@ namespace Avalonia.Styling /// A . public SelectorMatch Match(IStyleable control, bool subscribe = true) { - List> inputs = new List>(); - Selector selector = this; + var inputs = new List>(); + 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; } } diff --git a/src/Avalonia.Styling/Styling/SelectorMatch.cs b/src/Avalonia.Styling/Styling/SelectorMatch.cs index f325578389..63b89e9e97 100644 --- a/src/Avalonia.Styling/Styling/SelectorMatch.cs +++ b/src/Avalonia.Styling/Styling/SelectorMatch.cs @@ -5,51 +5,95 @@ using System; namespace Avalonia.Styling { + /// + /// Describes how a matches a control and its type. + /// + public enum SelectorMatchResult + { + /// + /// The selector never matches this type. + /// + NeverThisType, + + /// + /// The selector never matches this instance, but can match this type. + /// + NeverThisInstance, + + /// + /// The selector always matches this type. + /// + AlwaysThisType, + + /// + /// The selector always matches this instance, but doesn't always match this type. + /// + AlwaysThisInstance, + + /// + /// The selector matches this instance based on the . + /// + Sometimes, + } + /// /// Holds the result of a match. /// /// - /// There are two types of selectors - ones whose match can never change for a particular - /// control (such as ) and ones whose result can - /// change over time (such as . For the first - /// category of selectors, the value of will be set but for the - /// second, will be null and will - /// hold an observable which tracks the match. + /// A selector match describes whether and how a matches a control, and + /// in addition whether the selector can ever match a control of the same type. /// public class SelectorMatch { - public static readonly SelectorMatch False = new SelectorMatch(false); + /// + /// A selector match with the result of . + /// + public static readonly SelectorMatch NeverThisType = new SelectorMatch(SelectorMatchResult.NeverThisType); - public static readonly SelectorMatch True = new SelectorMatch(true); + /// + /// A selector match with the result of . + /// + public static readonly SelectorMatch NeverThisInstance = new SelectorMatch(SelectorMatchResult.NeverThisInstance); /// - /// Initializes a new instance of the class. + /// A selector match with the result of . /// - /// The immediate match value. - public SelectorMatch(bool match) - { - ImmediateResult = match; - } + public static readonly SelectorMatch AlwaysThisType = new SelectorMatch(SelectorMatchResult.AlwaysThisType); /// - /// Initializes a new instance of the class. + /// Gets a selector match with the result of . /// - /// The observable match value. + public static readonly SelectorMatch AlwaysThisInstance = new SelectorMatch(SelectorMatchResult.AlwaysThisInstance); + + /// + /// Initializes a new instance of the class with a + /// result. + /// + /// The match activator. public SelectorMatch(IObservable match) { - ObservableResult = match; + Contract.Requires(match != null); + + Result = SelectorMatchResult.Sometimes; + Activator = match; } + private SelectorMatch(SelectorMatchResult result) => Result = result; + /// - /// 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. + /// + public bool IsMatch => Result >= SelectorMatchResult.AlwaysThisType; + + /// + /// Gets the result of the match. /// - public bool? ImmediateResult { get; } + public SelectorMatchResult Result { get; } /// /// Gets an observable which tracks the selector match, in the case of selectors that can /// change over time. /// - public IObservable ObservableResult { get; } + public IObservable Activator { get; } } } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 23c318a809..27fad58346 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -106,20 +106,14 @@ namespace Avalonia.Styling /// bool IResourceProvider.HasResources => _resources?.Count > 0; - /// - /// Attaches the style to a control if the style's selector matches. - /// - /// The control to attach to. - /// - /// The control that contains this style. May be null. - /// - public void Attach(IStyleable control, IStyleHost container) + /// + 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 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; } /// diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 22ed8bb241..51499b737a 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/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> _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 /// /// The control that contains this style. May be null. /// - 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>(); + } + + 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 result = null; + + foreach (var style in this) + { + if (style.Attach(control, container)) + { + if (result == null) + { + result = new List(); + } + + result.Add(style); + } + } + + _cache.Add(control.StyleKey, result); + return result != null; } } diff --git a/src/Avalonia.Styling/Styling/TemplateSelector.cs b/src/Avalonia.Styling/Styling/TemplateSelector.cs index 6919b4ad27..7530339883 100644 --- a/src/Avalonia.Styling/Styling/TemplateSelector.cs +++ b/src/Avalonia.Styling/Styling/TemplateSelector.cs @@ -23,6 +23,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => true; + /// + public override bool IsCombinator => true; + /// 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; diff --git a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs index 94c0b75c6e..cd019e6535 100644 --- a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs @@ -68,6 +68,9 @@ namespace Avalonia.Styling /// public override Type TargetType => _targetType ?? _previous?.TargetType; + /// + public override bool IsCombinator => false; + /// /// Whether the selector matches the concrete or any object which /// implements . @@ -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; } } diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index cdaaa2b4b4..d083a2aaf8 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -293,13 +293,13 @@ namespace Avalonia /// The inverted matrix. 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, diff --git a/src/Avalonia.Visuals/Media/GradientStop.cs b/src/Avalonia.Visuals/Media/GradientStop.cs index 00d96a0b3c..25a6eb10dc 100644 --- a/src/Avalonia.Visuals/Media/GradientStop.cs +++ b/src/Avalonia.Visuals/Media/GradientStop.cs @@ -11,13 +11,13 @@ namespace Avalonia.Media /// /// Describes the property. /// - public static StyledProperty OffsetProperty = + public static readonly StyledProperty OffsetProperty = AvaloniaProperty.Register(nameof(Offset)); /// /// Describes the property. /// - public static StyledProperty ColorProperty = + public static readonly StyledProperty ColorProperty = AvaloniaProperty.Register(nameof(Color)); /// diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs index 6dde2bb591..37ac0953bf 100644 --- a/src/Avalonia.Visuals/Media/Typeface.cs +++ b/src/Avalonia.Visuals/Media/Typeface.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media /// public class Typeface { - public static Typeface Default = new Typeface(FontFamily.Default); + public static readonly Typeface Default = new Typeface(FontFamily.Default); /// /// Initializes a new instance of the class. diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 0b5e25fa9f..38d207a31d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs index fa9d364fc0..e09d9bfd17 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs @@ -51,6 +51,14 @@ namespace Avalonia.Markup.Xaml /// The type converter. 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; diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs new file mode 100644 index 0000000000..5e7a31da56 --- /dev/null +++ b/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 : 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); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index fc2656f236..9273011cb9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -62,12 +62,14 @@ namespace Avalonia.Markup.Xaml.Styling IResourceNode IResourceNode.ResourceParent => _parent; /// - 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() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 6174bc360f..f4eb0e2ea8 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/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); } } diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index 239c9d56aa..b56afa33a4 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/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; } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index b992453fb0..7187ea16da 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/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 } } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs new file mode 100644 index 0000000000..abe6fa84b0 --- /dev/null +++ b/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 = @""; + 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); + } + } + } +} diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs index d837f2cb2f..0561837ad1 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs @@ -27,7 +27,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Child().OfType(); - 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().Child().OfType(); - 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().Class("foo").Child().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; var result = new List(); Assert.False(await activator.Take(1)); @@ -70,7 +70,7 @@ namespace Avalonia.Styling.UnitTests var control = new TestLogical3(); var selector = default(Selector).OfType().Child().OfType(); - Assert.False(selector.Match(control).ImmediateResult); + Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(control).Result); } [Fact] diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs index 75599925b7..496998ecd9 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs +++ b/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(); using (activator.Subscribe(x => result.Add(x))) diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs index b75b59c212..56dad13186 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs @@ -26,7 +26,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Descendant().OfType(); - 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().Descendant().OfType(); - 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().Class("foo").Descendant().OfType(); - 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().Class("foo").Descendant().OfType(); - 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().Class("foo").Descendant().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; Assert.False(await activator.Take(1)); parent.Classes.Add("foo"); diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs index 067b08b296..04b29376b0 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs +++ b/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(); - 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