Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
VisualStateManager phase 1 (#1405)
Browse files Browse the repository at this point in the history
* Port from old VSM branch

* Add PS and notes

* Checkpoint: entry text UWP mostly working, need to check on background colors

* Remove irrelevant samples from the EntryDisabledStatesGallery
Make Background color work on UWP Entry with VSM

* Add platform specific for disabling legacy colors on Android

* Add OnPlatform example to visual state manager gallery

* Add example OnIdiom in Visual State Manager

* Add platform specific for disabling legacy color mode on iOS Entry

* Add gallery for Button disabled states
Handling legacy colors for Buttons on Android

* Split out disabled states galleries; disabled legacy handling for Picker

* TimePicker disabled states

* DatePicker color management on Android

* Color management for pre-AppCompat button

* Button legacy color handling on iOS

* Consolidate Platform Specifics;
legacy colors working for iOS Picker and DatePicker

* Fix broken search bar color management
SearchBar color management working with VSM
Add test page for SearchBar disabled color management
Consolidate legacy color management check code into extension method on Android

* Legacy color management for Editor on Android

* Fix legacy color stuff for SearchBar Cancel button on iOS

* C# 7 cleanup

* Add colors for Cancel Button

* Make sure VisualStateGroup collections set by styles are distinct objects

* Validation example

* Make common state names consts

* Make the Windows VSM and Forms VSM work together

* Update galleries for Windows

* Make new methods internal

* Split gallery classes and add more explanation to validation example

* Remove debugging statements

* Add a quick code-only example

* Make legacy color management work for fast button renderer

* Remove old TODO

* Update docs

* Move RunTimeNamePropertyAttribute to Xamarin.Forms.Xaml namespace

* Verify XF namespace when looking for VisualState

* Use nameof

* Make common states constants public

* Cast VisualElement directly so it crashes if the property is set on the wrong type

* Collection -> IList for VisualStateManager

* Setting fromStyle to true

* Remove extraneous `private set`

* Seal VSM classes

* Use constraints instead of ==

* Add teardown method; use constraints rather than ==

* Remove null checking with GetVisualStateGroups

* Don't explicitly initialize collections on elements

* Actually, turns out that fromStyle:false *was* correct

* Direct casts

* Use GetIsDefault check in GoToState

* Validate parents in FindTypeForVisualState

* Validate group and state names on Add

* Fixed check for setter collection

* Fix issues with "duplicate" names when VisualStateGroups declared directly on VisualElements

* Add gallery example for VSGs directly on VisualElements

* Update docs

* Fix bug where initial TextColor isn't set for FastRenderer Button

* Move to explicit VisualStateGroupList in Setter

* Fix return types for unit tests

* Using string.CompareOrdinal in GetState

* Update docs

* Add check for null/empty VisualState Name properties
  • Loading branch information
hartez authored and rmarinho committed Jan 5, 2018
1 parent c8b4685 commit 28948d7
Show file tree
Hide file tree
Showing 94 changed files with 4,550 additions and 334 deletions.
Expand Up @@ -32,6 +32,7 @@ public FieldReference GetBindablePropertyFieldReference(string value, ModuleDefi
var parts = value.Split('.');
if (parts.Length == 1) {
var parent = node.Parent?.Parent as IElementNode ?? (node.Parent?.Parent as IListNode)?.Parent as IElementNode;

if ((node.Parent as ElementNode)?.XmlType.NamespaceUri == XamlParser.XFUri &&
((node.Parent as ElementNode)?.XmlType.Name == "Setter" || (node.Parent as ElementNode)?.XmlType.Name == "PropertyCondition")) {
if (parent.XmlType.NamespaceUri == XamlParser.XFUri &&
Expand All @@ -41,6 +42,15 @@ 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.NamespaceUri == XamlParser.XFUri && parent.XmlType.Name == "VisualState") {
var current = parent.Parent.Parent.Parent as IElementNode;
if (current.XmlType.NamespaceUri == XamlParser.XFUri && current.XmlType.Name == "Setter") {
// Parent will be a Style, and the type will be that Style's TargetType
typeName =
((current?.Parent as IElementNode)?.Properties[new XmlName("", "TargetType")] as ValueNode)?.Value as string;
} else {
typeName = current.XmlType.Name;
}
}
} else if ((node.Parent as ElementNode)?.XmlType.NamespaceUri == XamlParser.XFUri && (node.Parent as ElementNode)?.XmlType.Name == "Trigger")
typeName = ((node.Parent as ElementNode).Properties [new XmlName("", "TargetType")] as ValueNode).Value as string;
Expand Down
Expand Up @@ -19,6 +19,12 @@ public IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefr
((IElementNode)node).CollectionItems.Count == 1)
valueNode = ((IElementNode)node).CollectionItems[0];

var bpNode = ((ValueNode)((IElementNode)node).Properties[new XmlName("", "Property")]);
var bpRef = (new BindablePropertyConverter()).GetBindablePropertyFieldReference((string)bpNode.Value, module, bpNode);

if (SetterValueIsCollection(bpRef, module, node, context))
yield break;

if (valueNode == null)
throw new XamlParseException("Missing Value for Setter", (IXmlLineInfo)node);

Expand All @@ -27,8 +33,6 @@ public IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefr
yield break;

var value = ((string)((ValueNode)valueNode).Value);
var bpNode = ((ValueNode)((IElementNode)node).Properties[new XmlName("", "Property")]);
var bpRef = (new BindablePropertyConverter()).GetBindablePropertyFieldReference((string)bpNode.Value, module, bpNode);

TypeReference _;
var setValueRef = module.ImportReference(module.ImportReference(typeof(Setter)).GetProperty(p => p.Name == "Value", out _).SetMethod);
Expand All @@ -43,5 +47,28 @@ public IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefr
//set the value
yield return Instruction.Create(OpCodes.Callvirt, setValueRef);
}

static bool SetterValueIsCollection(FieldReference bindablePropertyReference, ModuleDefinition module, BaseNode node, ILContext context)
{
var items = (node as IElementNode)?.CollectionItems;

if (items == null || items.Count <= 0)
return false;

// Is this a generic type ?
var generic = bindablePropertyReference.GetBindablePropertyType(node, module) as GenericInstanceType;

// With a single generic argument?
if (generic?.GenericArguments.Count != 1)
return false;

// Is the generic argument assignable from this value?
var genericType = generic.GenericArguments[0];

if (!(items[0] is IElementNode firstItem))
return false;

return context.Variables[firstItem].VariableType.InheritsFromOrImplements(genericType);
}
}
}
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
Expand Down Expand Up @@ -39,7 +40,7 @@ public void Visit(ElementNode node, INode parentNode)
{
VariableDefinition namescopeVarDef;
IList<string> namesInNamescope;
if (parentNode == null || IsDataTemplate(node, parentNode) || IsStyle(node, parentNode)) {
if (parentNode == null || IsDataTemplate(node, parentNode) || IsStyle(node, parentNode) || IsVisualStateGroupList(node)) {
namescopeVarDef = CreateNamescope();
namesInNamescope = new List<string>();
} else {
Expand All @@ -50,7 +51,7 @@ public void Visit(ElementNode node, INode parentNode)
SetNameScope(node, namescopeVarDef);
Context.Scopes[node] = new System.Tuple<VariableDefinition, IList<string>>(namescopeVarDef, namesInNamescope);
}

public void Visit(RootNode node, INode parentNode)
{
var namescopeVarDef = CreateNamescope();
Expand Down Expand Up @@ -81,6 +82,11 @@ static bool IsStyle(INode node, INode parentNode)
return pnode != null && pnode.XmlType.Name == "Style";
}

static bool IsVisualStateGroupList(ElementNode node)
{
return node != null && node.XmlType.Name == "VisualStateGroup" && node.Parent is IListNode;
}

static bool IsXNameProperty(ValueNode node, INode parentNode)
{
var parentElement = parentNode as IElementNode;
Expand Down
22 changes: 22 additions & 0 deletions Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs
Expand Up @@ -64,6 +64,8 @@ public void Visit(ValueNode node, INode parentNode)
return;
}

if (TrySetRuntimeName(propertyName, Context.Variables[(IElementNode)parentNode], node))
return;
if (skips.Contains(propertyName))
return;
if (parentNode is IElementNode && ((IElementNode)parentNode).SkipProperties.Contains (propertyName))
Expand Down Expand Up @@ -1354,6 +1356,26 @@ public static TypeReference GetParameterType(ParameterDefinition param)

loadTemplate.Body.Optimize();
}

bool TrySetRuntimeName(XmlName propertyName, VariableDefinition variableDefinition, ValueNode node)
{
if (propertyName != XmlName.xName)
return false;

var attributes = variableDefinition.VariableType.Resolve()
.CustomAttributes.Where(attribute => attribute.AttributeType.FullName == "Xamarin.Forms.Xaml.RuntimeNamePropertyAttribute").ToList();

if (!attributes.Any())
return false;

var runTimeName = attributes[0].ConstructorArguments[0].Value as string;

if (string.IsNullOrEmpty(runTimeName))
return false;

Context.IL.Append(SetPropertyValue(variableDefinition, new XmlName("", runTimeName), node, Context, node));
return true;
}
}

class VariableDefinitionReference
Expand Down
2 changes: 1 addition & 1 deletion Xamarin.Forms.Build.Tasks/XamlGenerator.cs
Expand Up @@ -250,7 +250,7 @@ static IEnumerable<CodeMemberField> GetCodeMemberFields(XmlNode root, XmlNamespa
XmlNodeList names =
root.SelectNodes(
"//*[@" + xPrefix + ":Name" +
"][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style)]", nsmgr);
"][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style) and not(ancestor:: __f__:VisualStateManager.VisualStateGroups)]", nsmgr);
foreach (XmlNode node in names) {
// Don't take the root canvas
if (node == root)
Expand Down
Expand Up @@ -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)]
public class DefaultColorToggleTest : TabbedPage
{
public DefaultColorToggleTest()
{
Title = "Test Color Toggle Page";

Children.Add(EntryPage());
Children.Add(PickerPage());
Children.Add(DatePickerPage());
Children.Add(TimePickerPage());
Children.Add(ButtonPage());
Children.Add(LabelPage());
Children.Add(EntryPage());
Children.Add(PasswordPage());
Children.Add(SearchBarPage());
}
Expand Down
2 changes: 2 additions & 0 deletions Xamarin.Forms.Controls/CoreGallery.cs
Expand Up @@ -7,6 +7,7 @@
using Xamarin.Forms.Internals;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
using Xamarin.Forms.Controls.GalleryPages.VisualStateManagerGalleries;

namespace Xamarin.Forms.Controls
{
Expand Down Expand Up @@ -244,6 +245,7 @@ public override string ToString()
}

List<GalleryPageFactory> _pages = new List<GalleryPageFactory> {
new GalleryPageFactory(() => new VisualStateManagerGallery(), "VisualStateManager Gallery"),
new GalleryPageFactory(() => new FlowDirectionGalleryLandingPage(), "FlowDirection"),
new GalleryPageFactory(() => new AutomationPropertiesGallery(), "Accessibility"),
new GalleryPageFactory(() => new PlatformSpecificsGallery(), "Platform Specifics"),
Expand Down
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:windowsSpecific="clr-namespace:Xamarin.Forms.PlatformConfiguration.WindowsSpecific;assembly=Xamarin.Forms.Core"
xmlns:androidSpecific="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
xmlns:iosSpecific="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
x:Class="Xamarin.Forms.Controls.GalleryPages.VisualStateManagerGalleries.ButtonDisabledStatesGallery"
Title="Button Disabled States Gallery">

<ContentPage.Resources>
<ResourceDictionary>

<Style TargetType="Button" x:Key="CustomDisabledState">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<!-- Because we're creating our own PointerOver state here, the Forms VSM will override
the Windows VSM for UWP. So this button won't display the native PointerOver behavior.
If we remove this next line, the usual PointerOver behavior will be restored. -->
<VisualState x:Name="PointerOver" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="Red" />
<Setter Property="BackgroundColor" Value="Purple" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>

<Style TargetType="Button" x:Key="DefaultInInitialState">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="Green" />
<Setter Property="BackgroundColor" Value="Black" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>

</ResourceDictionary>
</ContentPage.Resources>


<ContentPage.Content>
<ScrollView VerticalOptions="Fill" HorizontalOptions="Fill">
<StackLayout>

<!-- Not using the VSM at all; should exhibit pre-VSM default behavior -->
<Label Text="The Button below uses the current XF defaults; when IsEnabled is false, it uses the default native colors for the control. (Legacy Color Behavior)"/>
<Button Text="Button" x:Name="Button0" TextColor="Blue" BackgroundColor="Bisque" />
<Button Text="Toggle IsEnabled" x:Name="Button0_Toggle" Clicked="Button0_Toggle_OnClicked" />

<!-- Legacy Color Behavior turned off by the PlatformSpecific; ignores states entirely and uses
whatever colors are manually set for it -->
<Label Text="The Button below has the Legacy Color Behavior disabled; it will stick with whatever colors are set, regardless of state"/>
<Button Text="Button" x:Name="Button1" TextColor="Blue" BackgroundColor="Bisque"
androidSpecific:VisualElement.IsLegacyColorModeEnabled="False"
iosSpecific:VisualElement.IsLegacyColorModeEnabled="False"
windowsSpecific:VisualElement.IsLegacyColorModeEnabled="False"
/>
<Button Text="Toggle IsEnabled" x:Name="Button1_Toggle" Clicked="Button1_Toggle_OnClicked" />

<!-- Disabled colors fully managed by the VSM -->
<Label Text="The Button below uses the VSM to customize the colors for the disabled state." />
<Button Text="Button" x:Name="Button2" Style="{StaticResource CustomDisabledState}" TextColor="Green"
BackgroundColor="Beige" />
<Button Text="Toggle IsEnabled" x:Name="Button2_Toggle" Clicked="Button2_Toggle_OnClicked" />

<!-- This section verifies that the default colors for an entry's text and placeholder are cached and can be reverted to -->
<Label Text="This Button uses Color.Default for the intial text/background, and colors from the VSM when disabled."/>
<Button Text="Button" x:Name="Button3" Style="{StaticResource DefaultInInitialState}" TextColor="Default"
BackgroundColor="Default" />
<Button Text="Toggle IsEnabled" x:Name="Button3_Toggle" Clicked="Button3_Toggle_OnClicked" />
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
@@ -0,0 +1,48 @@
using System;
using Xamarin.Forms.Xaml;

namespace Xamarin.Forms.Controls.GalleryPages.VisualStateManagerGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ButtonDisabledStatesGallery : ContentPage
{
public ButtonDisabledStatesGallery ()
{
InitializeComponent ();
}

void Button0_Toggle_OnClicked(object sender, EventArgs e)
{
var button = sender as Button;
ToggleIsEnabled(Button0, button);
}

void Button1_Toggle_OnClicked(object sender, EventArgs e)
{
var button = sender as Button;
ToggleIsEnabled(Button1, button);
}

void Button2_Toggle_OnClicked(object sender, EventArgs e)
{
var button = sender as Button;
ToggleIsEnabled(Button2, button);
}

void Button3_Toggle_OnClicked(object sender, EventArgs e)
{
var button = sender as Button;
ToggleIsEnabled(Button3, button);
}

void ToggleIsEnabled(Button button, Button toggleButton)
{
button.IsEnabled = !button.IsEnabled;

if (toggleButton != null)
{
toggleButton.Text = $"Toggle IsEnabled (Currently {button.IsEnabled})";
}
}
}
}

0 comments on commit 28948d7

Please sign in to comment.