From 3f7d7b2a6542cc16279625e496d9bc0f180a9c0e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 22 Oct 2018 10:38:03 +0200 Subject: [PATCH 01/85] Don't call IsRegistered in PropertyEqualsSelector. Was showing up in the profiling and it's not even correct anymore, as any property can be set on any object now, registered or not. --- src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs index 25f12ffa57..541e8646b1 100644 --- a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs @@ -72,11 +72,7 @@ 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))); } From 94b2e2becadb44054b755a8627cdfc45bf6a28d0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Oct 2018 00:28:06 +0200 Subject: [PATCH 02/85] Cache the styles for control types. A lot of time was being spent calling `Attach` on styles that will never match a control. On the first pass, cache the styles that can match a particular control type and use this cache to bypass styles that will never match on subsequent passes. --- src/Avalonia.Styling/Styling/ChildSelector.cs | 20 ++++- .../Styling/DescendentSelector.cs | 16 ++-- src/Avalonia.Styling/Styling/IStyle.cs | 7 +- .../Styling/PropertyEqualsSelector.cs | 6 +- src/Avalonia.Styling/Styling/Selector.cs | 39 ++++++--- src/Avalonia.Styling/Styling/SelectorMatch.cs | 86 ++++++++++++++----- src/Avalonia.Styling/Styling/Style.cs | 24 +++--- src/Avalonia.Styling/Styling/Styles.cs | 45 +++++++++- .../Styling/TemplateSelector.cs | 5 +- .../Styling/TypeNameAndClassSelector.cs | 19 ++-- .../Styling/StyleInclude.cs | 6 +- .../SelectorTests_Child.cs | 8 +- .../SelectorTests_Class.cs | 23 ++--- .../SelectorTests_Descendent.cs | 10 +-- .../SelectorTests_Multiple.cs | 40 ++++++++- .../SelectorTests_Name.cs | 6 +- .../SelectorTests_OfType.cs | 15 +++- .../SelectorTests_PropertyEquals.cs | 11 ++- .../SelectorTests_Template.cs | 31 +++++-- 19 files changed, 313 insertions(+), 104 deletions(-) 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 541e8646b1..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. /// @@ -78,7 +81,8 @@ namespace Avalonia.Styling } 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..ca73c1f727 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/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/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 public CrossFade() - :this(TimeSpan.Zero) + : this(TimeSpan.Zero) { } @@ -33,30 +33,36 @@ namespace Avalonia.Animation { _fadeOutAnimation = new Animation { - new KeyFrame - ( - new Setter + Children = + { + new KeyFrame + ( + new Setter + { + Property = Visual.OpacityProperty, + Value = 0d + } + ) { - Property = Visual.OpacityProperty, - Value = 0d + Cue = new Cue(1d) } - ) - { - Cue = new Cue(1d) } }; _fadeInAnimation = new Animation { - new KeyFrame - ( - new Setter + Children = + { + new KeyFrame + ( + new Setter + { + Property = Visual.OpacityProperty, + Value = 0d + } + ) { - Property = Visual.OpacityProperty, - Value = 0d + Cue = new Cue(0d) } - ) - { - Cue = new Cue(0d) } }; _fadeOutAnimation.Duration = _fadeInAnimation.Duration = duration; diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs index c5d43068c1..c1848d7daa 100644 --- a/src/Avalonia.Visuals/Animation/PageSlide.cs +++ b/src/Avalonia.Visuals/Animation/PageSlide.cs @@ -74,34 +74,34 @@ namespace Avalonia.Animation var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty; - - // TODO: Implement relevant transition logic here (or discard this class) - // in favor of XAML based transition for pages if (from != null) { var animation = new Animation { - new KeyFrame - ( - new Setter - { - Property = translateProperty, - Value = 0d - } - ) + Children = { - Cue = new Cue(0d) - }, - new KeyFrame - ( - new Setter + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = 0d + } + ) { - Property = translateProperty, - Value = forward ? -distance : distance + Cue = new Cue(0d) + }, + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = forward ? -distance : distance + } + ) + { + Cue = new Cue(1d) } - ) - { - Cue = new Cue(1d) } }; animation.Duration = Duration; @@ -113,29 +113,31 @@ namespace Avalonia.Animation to.IsVisible = true; var animation = new Animation { - - new KeyFrame - ( - new Setter - { - Property = translateProperty, - Value = forward ? distance : -distance - } - ) + Children = { - Cue = new Cue(0d) - }, - new KeyFrame - ( - new Setter + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = forward ? distance : -distance + } + ) { - Property = translateProperty, - Value = 0d - } - ) - { - Cue = new Cue(1d) - }, + Cue = new Cue(0d) + }, + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = 0d + } + ) + { + Cue = new Cue(1d) + }, + } }; animation.Duration = Duration; tasks.Add(animation.RunAsync(to)); From 7e023ee5af1e531373f3615cae7a15582c120822 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Dec 2018 01:45:55 +0800 Subject: [PATCH 12/85] [Breaking Change] Replace RepeatCount with IterationCount. --- samples/RenderDemo/Pages/AnimationsPage.xaml | 4 +- samples/RenderDemo/Pages/ClippingPage.xaml | 2 +- src/Avalonia.Animation/Animation.cs | 4 +- src/Avalonia.Animation/AnimationInstance`1.cs | 13 +- src/Avalonia.Animation/IterationCount.cs | 176 ++++++++++++++++ ...rter.cs => IterationCountTypeConverter.cs} | 4 +- src/Avalonia.Animation/RepeatCount.cs | 199 ------------------ src/Avalonia.Themes.Default/ProgressBar.xaml | 4 +- 8 files changed, 188 insertions(+), 218 deletions(-) create mode 100644 src/Avalonia.Animation/IterationCount.cs rename src/Avalonia.Animation/{RepeatCountTypeConverter.cs => IterationCountTypeConverter.cs} (83%) delete mode 100644 src/Avalonia.Animation/RepeatCount.cs diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index 1646708797..9f8e7b5fa5 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -43,7 +43,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + @@ -121,6 +149,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + From 87e8bca8793c40b5b320f611213122ee9d343a4a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 01:03:12 +0800 Subject: [PATCH 35/85] Minor fixes. --- samples/RenderDemo/Pages/AnimationsPage.xaml | 2 +- .../Animation/Animators/SolidColorBrushAnimator.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index e6e0eff941..53f8e855e0 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -149,7 +149,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - + diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 43cc9d5476..518d3d2ca7 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -7,8 +7,7 @@ using Avalonia.Media.Immutable; namespace Avalonia.Animation.Animators { /// - /// Animator that interpolates through - /// gamma sRGB color space for better visual result. + /// Animator that handles . /// public class SolidColorBrushAnimator : Animator { From 13eb3655714558ed3d66f34b83e1d6ee136b6ccf Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 11:54:38 +0800 Subject: [PATCH 36/85] Fix SCBA. --- .../Animators/SolidColorBrushAnimator.cs | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 518d3d2ca7..c3de578959 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -13,53 +13,62 @@ namespace Avalonia.Animation.Animators { ColorAnimator colorAnimator; + void InitializeColorAnimator() + { + colorAnimator = new ColorAnimator(); + + foreach (AnimatorKeyFrame keyframe in this) + { + colorAnimator.Add(keyframe); + } + + colorAnimator.Property = SolidColorBrush.ColorProperty; + } + public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) { var ctrl = (Visual)control; foreach (var keyframe in this) { - // Return if the keyframe value is not a SolidColorBrush if (keyframe.Value as ISolidColorBrush == null) - { return Disposable.Empty; - } - // Preprocess values to Color if the xaml parser converts them to ISCB + // Preprocess keyframe values to Color if the xaml parser converts them to ISCB. if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush)) { keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color; } } - // Make sure that the target property has SCB instead of the immutable one nor null. + // Add SCB if the target prop is empty. + if (control.GetValue(Property) == null) + control.SetValue(Property, new SolidColorBrush(Colors.Transparent)); var targetVal = control.GetValue(Property); - SolidColorBrush targetSCB = null; - - if (targetVal == null) - targetSCB = new SolidColorBrush(Colors.Transparent); - else if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) - targetSCB = new SolidColorBrush(((ISolidColorBrush)targetVal).Color); - else - return Disposable.Empty; - - control.SetValue(Property, targetSCB); - - if (colorAnimator == null) + // Continue if target prop is not empty & is a SolidColorBrush derivative. + if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) { - colorAnimator = new ColorAnimator(); + if (colorAnimator == null) + InitializeColorAnimator(); + + SolidColorBrush finalTarget; - foreach (AnimatorKeyFrame keyframe in this) + // If it's ISCB, change it back to SCB. + if (targetVal.GetType() == typeof(ImmutableSolidColorBrush)) { - colorAnimator.Add(keyframe); + var col = (ImmutableSolidColorBrush)targetVal; + targetVal = new SolidColorBrush(col.Color); + control.SetValue(Property, targetVal); } - colorAnimator.Property = SolidColorBrush.ColorProperty; + finalTarget = targetVal as SolidColorBrush; + + return colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete); } - return colorAnimator.Apply(animation, targetSCB, clock ?? control.Clock, match, onComplete); + return Disposable.Empty; } public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null; From cfe26a787df7f2869643e130f16a7275f43f7f01 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 7 Dec 2018 22:25:52 +0300 Subject: [PATCH 37/85] Enable GPU acceleration on Linux by default --- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 8444e509fd..965973d3cb 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -50,7 +50,7 @@ namespace Avalonia.Gtk3 { Resolver.Custom = options.CustomResolver; UseDeferredRendering = EnvOption("USE_DEFERRED_RENDERING", true, options.UseDeferredRendering); - var useGpu = EnvOption("USE_GPU", false, options.UseGpuAcceleration); + var useGpu = EnvOption("USE_GPU", true, options.UseGpuAcceleration); if (!s_gtkInitialized) { try From ea7dc760cc22af125a8d9fdfca46c36dfb0d7a5f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 10 Dec 2018 10:18:52 +0300 Subject: [PATCH 38/85] Updated SkiaSharp to 1.68.0 --- build/Magick.NET-Q16-AnyCPU.props | 2 +- build/SkiaSharp.props | 4 ++-- nukebuild/Build.cs | 5 ++-- .../Avalonia.Skia/FramebufferRenderTarget.cs | 3 ++- src/Skia/Avalonia.Skia/GlRenderTarget.cs | 23 ++++++++----------- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 2 +- .../Controls/BorderTests.cs | 12 +++++----- tests/Avalonia.RenderTests/TestBase.cs | 1 + tests/Avalonia.RenderTests/TestSkip.cs | 21 +++++++++++++++++ 9 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 tests/Avalonia.RenderTests/TestSkip.cs diff --git a/build/Magick.NET-Q16-AnyCPU.props b/build/Magick.NET-Q16-AnyCPU.props index 4e600a1c11..21d9cdcb1f 100644 --- a/build/Magick.NET-Q16-AnyCPU.props +++ b/build/Magick.NET-Q16-AnyCPU.props @@ -1,5 +1,5 @@ - + diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index 35c979a95e..a43c99e978 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,6 +1,6 @@  - - + + diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 61944c4dbc..1e1becb1c4 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -138,12 +138,13 @@ partial class Build : NukeBuild }); Target RunRenderTests => _ => _ - .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .OnlyWhen(() => !Parameters.SkipTests) .DependsOn(Compile) .Executes(() => { RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", true); - RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true); }); Target RunDesignerTests => _ => _ diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 5efbc0861e..02b6188fb6 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -42,7 +42,8 @@ namespace Avalonia.Skia { var framebuffer = _platformSurface.Lock(); var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height, - framebuffer.Format.ToSkColorType(), SKAlphaType.Premul); + framebuffer.Format.ToSkColorType(), + framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul); CreateSurface(framebufferImageInfo, framebuffer); diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs index a6269473a6..3360255dae 100644 --- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -31,24 +31,18 @@ namespace Avalonia.Skia var size = session.Size; var scaling = session.Scaling; - GRBackendRenderTargetDesc desc = new GRBackendRenderTargetDesc - { - Width = size.Width, - Height = size.Height, - SampleCount = disp.SampleCount, - StencilBits = disp.StencilSize, - Config = GRPixelConfig.Rgba8888, - Origin=GRSurfaceOrigin.BottomLeft, - RenderTargetHandle = new IntPtr(fb) - }; - - gl.Viewport(0, 0, desc.Width, desc.Height); + gl.Viewport(0, 0, size.Width, size.Height); gl.ClearStencil(0); gl.ClearColor(0, 0, 0, 0); gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - var surface = SKSurface.Create(_grContext, desc); - + GRBackendRenderTarget renderTarget = + new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize, + new GRGlFramebufferInfo((uint)fb, GRPixelConfig.Rgba8888.ToGlSizedFormat())); + var surface = SKSurface.Create(_grContext, renderTarget, + GRSurfaceOrigin.BottomLeft, + GRPixelConfig.Rgba8888.ToColorType()); + var nfo = new DrawingContextImpl.CreateInfo { GrContext = _grContext, @@ -62,6 +56,7 @@ namespace Avalonia.Skia { surface.Canvas.Flush(); surface.Dispose(); + renderTarget.Dispose(); session.Dispose(); })); } diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 36ccd7930a..eb3a11b2a1 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -41,7 +41,7 @@ namespace Avalonia.Skia var nfo = new SKImageInfo(size.Width, size.Height, colorType, SKAlphaType.Premul); var blob = runtimePlatform.AllocBlob(nfo.BytesSize); - _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, s_releaseDelegate, blob); + _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, s_releaseDelegate, blob); } else { diff --git a/tests/Avalonia.RenderTests/Controls/BorderTests.cs b/tests/Avalonia.RenderTests/Controls/BorderTests.cs index c82d616094..4f4004e159 100644 --- a/tests/Avalonia.RenderTests/Controls/BorderTests.cs +++ b/tests/Avalonia.RenderTests/Controls/BorderTests.cs @@ -188,7 +188,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } - [Fact] + [Win32Fact("Has text")] public async Task Border_Centers_Content_Horizontally() { Decorator target = new Decorator @@ -215,7 +215,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Centers_Content_Vertically() { Decorator target = new Decorator @@ -296,7 +296,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Left_Aligns_Content() { Decorator target = new Decorator @@ -323,7 +323,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Right_Aligns_Content() { Decorator target = new Decorator @@ -350,7 +350,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Top_Aligns_Content() { Decorator target = new Decorator @@ -377,7 +377,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Bottom_Aligns_Content() { Decorator target = new Decorator diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 4c8abd85f7..2efd28c2d5 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -46,6 +46,7 @@ namespace Avalonia.Direct2D1.RenderTests public TestBase(string outputPath) { + outputPath = outputPath.Replace('\\', Path.DirectorySeparatorChar); var testPath = GetTestsDirectory(); var testFiles = Path.Combine(testPath, "TestFiles"); #if AVALONIA_SKIA diff --git a/tests/Avalonia.RenderTests/TestSkip.cs b/tests/Avalonia.RenderTests/TestSkip.cs new file mode 100644 index 0000000000..75407332e3 --- /dev/null +++ b/tests/Avalonia.RenderTests/TestSkip.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests +#endif +{ + public class Win32Fact : FactAttribute + { + public Win32Fact(string message) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Skip = message; + } + } +} + From 739309c28f116f3f9ce56a70a884dd330c879393 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 15:58:53 +0800 Subject: [PATCH 39/85] Replace binary-search on keyframe selection with naive approach for now. --- .../Animators/Animator`1.cs | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Animation/Animators/Animator`1.cs b/src/Avalonia.Animation/Animators/Animator`1.cs index e79aa128d6..e42489d6a6 100644 --- a/src/Avalonia.Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Animation/Animators/Animator`1.cs @@ -78,7 +78,7 @@ namespace Avalonia.Animation.Animators double t0 = firstKeyframe.Cue.CueValue; double t1 = lastKeyframe.Cue.CueValue; - double progress = (animationTime - t0) / (t1 - t0); + double progress = (animationTime - t0) / (t1 - t0); T oldValue, newValue; @@ -97,30 +97,11 @@ namespace Avalonia.Animation.Animators private int FindClosestBeforeKeyFrame(double time) { - int FindClosestBeforeKeyFrame(int startIndex, int length) - { - if (length == 0 || length == 1) - { - return startIndex; - } - - int middle = startIndex + (length / 2); + for (int i = 0; i < _convertedKeyframes.Count; i++) + if (_convertedKeyframes[i].Cue.CueValue > time) + return i - 1; - if (_convertedKeyframes[middle].Cue.CueValue < time) - { - return FindClosestBeforeKeyFrame(middle, length - middle); - } - else if (_convertedKeyframes[middle].Cue.CueValue > time) - { - return FindClosestBeforeKeyFrame(startIndex, middle - startIndex); - } - else - { - return middle; - } - } - - return FindClosestBeforeKeyFrame(0, _convertedKeyframes.Count); + throw new Exception("Index time is out of keyframe time range."); } /// @@ -139,13 +120,10 @@ namespace Avalonia.Animation.Animators } /// - /// Interpolates a value given the desired time. + /// Interpolates in-between two key values given the desired progress time. /// public abstract T Interpolate(double progress, T oldValue, T newValue); - /// - /// Verifies, converts and sorts keyframe values according to this class's target type. - /// private void VerifyConvertKeyFrames() { foreach (AnimatorKeyFrame keyframe in this) @@ -193,4 +171,4 @@ namespace Avalonia.Animation.Animators } } } -} \ No newline at end of file +} From 2fe9378ebc79b0f7f32a45bca84c15b514d54b3f Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 16:01:21 +0800 Subject: [PATCH 40/85] Adjust rainbow example --- samples/RenderDemo/Pages/AnimationsPage.xaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index 53f8e855e0..b5a8ff6dea 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -106,19 +106,20 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">