-
Notifications
You must be signed in to change notification settings - Fork 1.9k
VisualStateManager phase 1 #1405
Conversation
build |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- api changes part of the description misses VSG
- I'd have to do a 2nd pass on this Xaml/XamlC changes
- if you do not define your VSM in Style, XamlG will probably try to generate multiple fields with the name "Normal". The
x:Name
handling and RuntimeNamePropertyAttribute should be handled in the visitor handling x:Name and namescopes (creading a new node, skipping the x:Name setting)
[OOPS, clicked submit to early. continuing in another review]
@@ -41,6 +42,16 @@ public FieldReference GetBindablePropertyFieldReference(string value, ModuleDefi | |||
typeName = (ttnode as ValueNode).Value as string; | |||
else if (ttnode is IElementNode) | |||
typeName = ((ttnode as IElementNode).CollectionItems.FirstOrDefault() as ValueNode)?.Value as string ?? ((ttnode as IElementNode).Properties [new XmlName("", "TypeName")] as ValueNode)?.Value as string; | |||
} else if (parent.XmlType.Name == "VisualState") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check that the Namespace is XFUri to avoid mismatches
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the check.
@@ -43,5 +47,28 @@ public IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefr | |||
//set the value | |||
yield return Instruction.Create(OpCodes.Callvirt, setValueRef); | |||
} | |||
|
|||
bool IsSetterCollection(FieldReference bindablePropertyReference, ModuleDefinition module, BaseNode node, ILContext context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method doesn't check if this is a Setter at all, I find the name misleading, and I don't understand what this does
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea of the method is to check whether the setter is for a collection; renamed the method to SetterValueIsCollection
to (hopefully) be more clear. It short-circuits ProvideValue if the Setter is for a collection, since the collection will be intialized later when the first item is added.
Currently I have collections in Setters (e.g. IList<VisualStateGroup>
) being initialized when the first item is added to them (using EnsureSetterCollectionInitialized
). I suspect that this should instead be happening in IValueProvider.ProvideValue
/ICompiledValueProvider.ProvideValue
, but if there's a simple way to do that, I haven't been able to see it yet.
Any suggestions?
} | ||
|
||
static bool IsSetterCollection(VariableDefinition parent, string localName, INode node, ILContext context) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👮
return true; | ||
} | ||
|
||
static bool CanAddToAttachedProperty(FieldReference bpRef, bool attached, INode node, IXmlLineInfo iXmlLineInfo, ILContext context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I /think/ this is covered already, but I have to check
|
||
Type FindTypeForVisualState(List<object> parentObjects) | ||
{ | ||
var obj = parentObjects.Skip(3).Take(1).FirstOrDefault(); // Skip this Setter, VisualState, and VisualStateGroup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should skip them one by one, and verify their types (content properties)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added type verification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Part 2 of the review:
- I only reviewed the Xaml and Core parts.
- it misses Xaml unit tests for the xaml changes
Minus the comments, it looks all good. I'd have to make time to review the Xaml and XamlC changes
namespace Xamarin.Forms | ||
{ | ||
[AttributeUsage(AttributeTargets.Class)] | ||
public sealed class RuntimeNamePropertyAttribute : Attribute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't we reuse the System.Windows.Markup one now that we're on netstandard ? that'd make the life of people writing designers a bit easier.
if not, should be in X.F.Xaml ns
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed the namespace.
Xamarin.Forms.Core/Setter.cs
Outdated
target.SetValue(Property, Value, fromStyle); | ||
{ | ||
if (Value is Collection<VisualStateGroup> visualStateGroupCollection) | ||
target.SetValue(Property, visualStateGroupCollection.Clone(), fromStyle); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think the cloning should happen in the BP itself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only want the cloning to happen if the property is being set by a Setter; if it's being set directly on the element in Xaml or in code, we don't want to clone it. Is there some way to determine that a Setter is the source from within the BP?
} | ||
|
||
public static readonly BindableProperty VisualStateGroupsProperty = | ||
BindableProperty.CreateAttached("VisualStateGroups", typeof(Collection<VisualStateGroup>), typeof(VisualElement), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it should expose ICollection<VSG>
as a public api
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, what's the point of a collection vs a list ? the collection is cloned at the time it is applied, so we don't have to handle collectionEventArgs ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, using IList now.
|
||
static void VisualStateGroupsPropertyChanged(BindableObject bindable, object oldValue, object newValue) | ||
{ | ||
if (bindable is VisualElement visualElement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a case this condition is false ? if it is, it means the user added a VSG to a non VE, and we'd better crash (like we do for all other BP) in direct casting.
at some point in the future, we'll have to modify BO so it validates types...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed - now uses a direct cast.
if (bindable is VisualElement visualElement) | ||
{ | ||
// Start out in the Normal state, if one is defined | ||
GoToState(visualElement, CommonStates.Normal); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a extension method on VE could be handy, if GoToState is meant to be extensively used in user code
|
||
internal VisualStateGroup Clone() | ||
{ | ||
var clone = new VisualStateGroup {TargetType = TargetType, Name = Name, CurrentState = CurrentState}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't this the right place to set the CurrentState as "Normal" ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that seems a right place as well to throw if the name is null (x:Name is mandatory on VSG)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setters aren't the only way to set the VisualStateGroups for an element. They can also be set directly in Xaml or from code. And we don't want to set CurrentState to normal directly on the property - we need it to be set with GoToState so the values for the Normal state are applied to the element.
} | ||
|
||
public string Name { get; set; } | ||
public Collection<Setter> Setters { get;} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you know...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Fixed.
} | ||
|
||
[RuntimeNameProperty("Name")] | ||
public class VisualState |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no storyboard, no triggers, right ? if we don't have plans to support them in the future, the ContentProperty could be set to "Setters"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The plan is to support them in the future.
} | ||
} | ||
|
||
[RuntimeNameProperty("Name")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nameof(Name)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
} | ||
|
||
[RuntimeNameProperty("Name")] | ||
[ContentProperty("States")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nameof for both
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Name = name; | ||
} | ||
|
||
public string Name { get; private set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
turn on r#. it'll tell you you don't have to say private set;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to turn if off when I'm working in your Xaml code, otherwise it formats everything correctly :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
@@ -88,8 +89,8 @@ public partial class VisualElement : Element, IAnimatable, IVisualElementControl | |||
public static readonly BindableProperty MinimumHeightRequestProperty = BindableProperty.Create("MinimumHeightRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged); | |||
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] | |||
public static readonly BindablePropertyKey IsFocusedPropertyKey = BindableProperty.CreateReadOnly("IsFocused", typeof(bool), typeof(VisualElement), default(bool), | |||
propertyChanged: OnIsFocusedPropertyChanged); | |||
public static readonly BindablePropertyKey IsFocusedPropertyKey = BindableProperty.CreateReadOnly("IsFocused", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hate that public BP
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was like that when I got here. ¯\(ツ)/¯
Xamarin.Forms.Core/VisualElement.cs
Outdated
static void OnIsFocusedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue) | ||
{ | ||
var element = bindable as VisualElement; | ||
if (!(bindable is VisualElement element)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check for BO.GetIsDefault(). element will only be a VE, or null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you saying that GetIsDefault will be faster than the pattern matching for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using direct cast now.
public static class CollectionExtensions | ||
{ | ||
public static object EnsureCollectionInitialized(this BindableObject bindable, BindableProperty property) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not up to the Xaml to create collection if not explicitly declared. if you want to add to a collection, that collection has to exists (can be lazily created in the property getter).
we're doing this at other places
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed for BindableObject - the collections are now initialized lazily when the value is retrieved.
var currentState1 = groups1[0].CurrentState; | ||
var currentState2 = groups2[0].CurrentState; | ||
|
||
Assert.That(currentState1.Name == "Normal"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
either use
Assert.AreEqual(currentState1.Name, "Normal")
or
Assert.That(currentState1.Name, IS.EqualTo("Normal")) // actually my preferred form
or even
Assert.True(currentState1.Name == "Normal")
but try to avoid this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
{ | ||
Device.PlatformServices = new MockPlatformServices (); | ||
Application.Current = new MockApplication (); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a TearDown, both of those are static, and could influence the test running next
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the TearDown.
|
||
[RuntimeNameProperty(nameof(Name))] | ||
[ContentProperty(nameof(States))] | ||
public class VisualStateGroup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as the Clone
is internal, this class should be sealed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
} | ||
|
||
[RuntimeNameProperty(nameof(Name))] | ||
public class VisualState |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seal as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
var isEnabled = (bool)newValue; | ||
|
||
VisualStateManager.GoToState(element, isEnabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't call the VisualStateManager directly. There should be a method such as "UpdateVisualState" that is called whenever the VisualElement thinks the visual state should be updated. This should be avirtual method so that developers can easily sub-class and override the visual state manager behaviour.
I misread.
Don’t GetIsDefaut.
Direct cast and null comparison
… On 16 Dec 2017, at 00:39, E.Z. Hart ***@***.***> wrote:
@hartez commented on this pull request.
In Xamarin.Forms.Core/VisualElement.cs:
> static void OnIsFocusedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
- var element = bindable as VisualElement;
+ if (!(bindable is VisualElement element))
Are you saying that GetIsDefault will be faster than the pattern matching for this?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
|
Tested this, and you are absolutely correct.
So in the NameScopeVisitor, should I be creating a new namescope when I've got a VisualStateGroups element which is not part of a Style? |
|
|
||
internal VisualState GetState(string name) | ||
{ | ||
foreach (VisualState state in States) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this method is called often, does it make sense to switch to for loop? Or is this premature optimisation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't think there'd be a significant difference, since the underlying implementation is using List. But just to be certain I wrote up some quick-and-dirty benchmarking tests to see if for
would make any difference vs foreach
here.
For the first batch, I created VisualStateGroupLists with 1 group, and the group had 2 states (which I think will be a pretty common scenario). I then had the test run through 100,000 iterations of moving randomly between the states (using the GoToState
method). Between the two implementations (foreach
and for
), the test runs took the same amount of time on average.
For the second batch, I created an uglier scenario with VisualStateGroupLists with 10 groups, each group having 10 states. Running that over 100,000 iterations, the foreach
actually ended up being slightly faster.
Looking at the tests in the profiler, the bulk of the time spent in the method (in either implementation) is the string comparison. The looping method doesn't end up making much difference.
So for the time being I think we'll stick with foreach
for clarity. If we start running into performance problems with this later, we can revisit it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference isn't in performance but in allocation. foreach generates an iterator which adds some GC pressure. It probably doesn't matter much though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The enumerator for List is a struct, so it's pretty low-impact.
@@ -117,7 +123,7 @@ void SetNameScope(ElementNode node, VariableDefinition ns) | |||
void RegisterName(string str, VariableDefinition namescopeVarDef, IList<string> namesInNamescope, VariableDefinition element, INode node) | |||
{ | |||
if (namesInNamescope.Contains(str)) | |||
throw new XamlParseException($"An element with the name \"{str}\" already exists in this NameScope", node as IXmlLineInfo); | |||
throw new XamlParseException($"An element with the name \"{str}\" already exists in this NameScope 1", node as IXmlLineInfo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
failed test not related |
Make Background color work on UWP Entry with VSM
…ectly on VisualElements
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good stuff, i wonder if we should make useLegacy color a Flag instead of PS ..
@@ -6,19 +6,19 @@ | |||
namespace Xamarin.Forms.Controls | |||
{ | |||
[Preserve (AllMembers=true)] | |||
[Issue (IssueTracker.None, 0, "Default colors toggle test", PlatformAffected.All)] | |||
[Issue (IssueTracker.None, 9906753, "Default colors toggle test", PlatformAffected.All)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can drop the 990 now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I like the number 9906753.
* Added the new Contacts.GetAllAsync * Removed the ContactType enum as it was not quite ready (see more #1545) * Added more members to the Contact type Co-authored-by: sung-su.kim <sung-su.kim@samsung.com> Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Description of Change
Adds the capability to manage properties on Forms VisualElements via declarative states. States can be declared in code or in XAML.
The
Xamarin.Forms.Controls\GalleryPages\VisualStateManagerGalleries
folder contains example code for using this feature.Also adds the Platform Specific
IsLegacyColorModeEnabled
to provide the option of disabling the partial state/color handling introduced in earlier versions of Forms.Bugs Fixed
API Changes
Added:
VisualStateManager:
public static Collection<VisualStateGroup> GetVisualStateGroups(VisualElement visualElement)
public static void SetVisualStateGroups(VisualElement visualElement, Collection<VisualStateGroup> value)
public static bool GoToState(VisualElement visualElement, string name)
AndroidSpecific, iOSSpecific, WindowsSpecific:
public static bool GetIsLegacyColorModeEnabled(BindableObject element)
public static void SetIsLegacyColorModeEnabled(BindableObject element, bool value)
public static bool GetIsLegacyColorModeEnabled(this IPlatformElementConfiguration<Android, VisualElement> config)
public static IPlatformElementConfiguration<Android, VisualElement> SetIsLegacyColorModeEnabled( this IPlatformElementConfiguration<Android, VisualElement> config, bool value)
PR Checklist