diff --git a/.gitignore b/.gitignore
index 7184ff87d09..579107c2eec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -72,3 +72,4 @@ caketools/
.XamarinFormsVersionFile.txt
.idea
Visual Studio 2019/**
+build/packages
diff --git a/.nuspec/Xamarin.Forms.AppLinks.nuspec b/.nuspec/Xamarin.Forms.AppLinks.nuspec
index 4a192d4ed1d..d3933ca99eb 100644
--- a/.nuspec/Xamarin.Forms.AppLinks.nuspec
+++ b/.nuspec/Xamarin.Forms.AppLinks.nuspec
@@ -7,7 +7,7 @@
microsoft xamarin
xamarin forms applinks xamarinforms xamarin.forms
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
@@ -36,6 +36,8 @@
+
+
diff --git a/.nuspec/Xamarin.Forms.DualScreen.nuspec b/.nuspec/Xamarin.Forms.DualScreen.nuspec
index 24bb81621f6..5883436d1f7 100644
--- a/.nuspec/Xamarin.Forms.DualScreen.nuspec
+++ b/.nuspec/Xamarin.Forms.DualScreen.nuspec
@@ -7,7 +7,7 @@
microsoft xamarin
xamarin forms twopaneview DualScreen xamarinforms xamarinformsdualscreen xamarin.forms.dualscreen
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
@@ -26,7 +26,8 @@
-
+
+
diff --git a/.nuspec/Xamarin.Forms.Maps.GTK.nuspec b/.nuspec/Xamarin.Forms.Maps.GTK.nuspec
index ac71741e2b1..51b7594d305 100644
--- a/.nuspec/Xamarin.Forms.Maps.GTK.nuspec
+++ b/.nuspec/Xamarin.Forms.Maps.GTK.nuspec
@@ -7,7 +7,7 @@
microsoft xamarin
xamarin forms maps xamarinforms xamarinformsmaps xamarin.forms.maps gtk gtk-sharp linux
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
@@ -21,6 +21,8 @@
+
+
diff --git a/.nuspec/Xamarin.Forms.Maps.WPF.nuspec b/.nuspec/Xamarin.Forms.Maps.WPF.nuspec
index ece574b178c..df56e85acdf 100644
--- a/.nuspec/Xamarin.Forms.Maps.WPF.nuspec
+++ b/.nuspec/Xamarin.Forms.Maps.WPF.nuspec
@@ -7,7 +7,7 @@
microsoft xamarin
xamarin forms maps xamarinforms xamarinformsmaps xamarin.forms.maps wpf
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
@@ -24,6 +24,8 @@
+
+
diff --git a/.nuspec/Xamarin.Forms.Maps.nuspec b/.nuspec/Xamarin.Forms.Maps.nuspec
index d0768fe5ac1..337fa0a9073 100644
--- a/.nuspec/Xamarin.Forms.Maps.nuspec
+++ b/.nuspec/Xamarin.Forms.Maps.nuspec
@@ -7,7 +7,7 @@
microsoft xamarin
xamarin forms maps xamarinforms xamarinformsmaps xamarin.forms.maps
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
@@ -38,6 +38,8 @@
+
+
diff --git a/.nuspec/Xamarin.Forms.Pages.Azure.nuspec b/.nuspec/Xamarin.Forms.Pages.Azure.nuspec
index 3824e0a0a5f..cad7e5debe9 100644
--- a/.nuspec/Xamarin.Forms.Pages.Azure.nuspec
+++ b/.nuspec/Xamarin.Forms.Pages.Azure.nuspec
@@ -6,7 +6,7 @@
Microsoft
microsoft xamarin
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
@@ -21,6 +21,8 @@
+
+
diff --git a/.nuspec/Xamarin.Forms.Pages.nuspec b/.nuspec/Xamarin.Forms.Pages.nuspec
index 9f78e54ad7a..ca3809fa830 100644
--- a/.nuspec/Xamarin.Forms.Pages.nuspec
+++ b/.nuspec/Xamarin.Forms.Pages.nuspec
@@ -6,7 +6,7 @@
Microsoft
microsoft xamarin
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
@@ -20,6 +20,8 @@
+
+
diff --git a/.nuspec/Xamarin.Forms.Platform.GTK.nuspec b/.nuspec/Xamarin.Forms.Platform.GTK.nuspec
index 1798800c1f7..6d743ff11ec 100644
--- a/.nuspec/Xamarin.Forms.Platform.GTK.nuspec
+++ b/.nuspec/Xamarin.Forms.Platform.GTK.nuspec
@@ -7,7 +7,7 @@
microsoft xamarin
xamarin forms xamarinforms xamarin.forms gtk gtk-sharp linux
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
@@ -21,6 +21,8 @@
+
+
diff --git a/.nuspec/Xamarin.Forms.Platform.WPF.nuspec b/.nuspec/Xamarin.Forms.Platform.WPF.nuspec
index 26dbb7a3f2d..d60dbbcd315 100644
--- a/.nuspec/Xamarin.Forms.Platform.WPF.nuspec
+++ b/.nuspec/Xamarin.Forms.Platform.WPF.nuspec
@@ -7,22 +7,25 @@
microsoft xamarin
xamarin forms xamarinforms xamarin.forms wpf
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
Xamarin Forms Renderer to build native UIs for WPF
© Microsoft Corporation. All rights reserved.
-
-
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
diff --git a/.nuspec/Xamarin.Forms.Visual.Material.nuspec b/.nuspec/Xamarin.Forms.Visual.Material.nuspec
index 998fb4382e1..5e919ea1185 100644
--- a/.nuspec/Xamarin.Forms.Visual.Material.nuspec
+++ b/.nuspec/Xamarin.Forms.Visual.Material.nuspec
@@ -7,7 +7,7 @@
microsoft xamarin
xamarin forms visual material xamarinforms xamarinformsvisualmaterial xamarin.forms.visual.material
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
@@ -38,7 +38,8 @@
-
+
+
diff --git a/.nuspec/Xamarin.Forms.nuspec b/.nuspec/Xamarin.Forms.nuspec
index 496b444d9ee..2f0209071af 100644
--- a/.nuspec/Xamarin.Forms.nuspec
+++ b/.nuspec/Xamarin.Forms.nuspec
@@ -7,7 +7,7 @@
microsoft xamarin
xamarin forms xamarinforms xamarin.forms
MIT
- https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Assets/xamarin_128x128.png
+ Assets\xamarin_128x128.png
http://xamarin.com/forms
true
@@ -37,7 +37,7 @@
-
+
@@ -100,7 +100,8 @@
-
+
+
diff --git a/.nuspec/Xamarin.Forms.targets b/.nuspec/Xamarin.Forms.targets
index 2a3e666293e..cd9f89fce33 100644
--- a/.nuspec/Xamarin.Forms.targets
+++ b/.nuspec/Xamarin.Forms.targets
@@ -84,7 +84,8 @@
Inputs="@(_XamlGInputs)"
Outputs="@(_XamlGOutputs)">
- @(ReferencePath)
+ @(ReferencePath)
+ $(_ReferencedAssemblies)
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 2064154c2ec..bd083c26d47 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -20,4 +20,4 @@
-
\ No newline at end of file
+
diff --git a/DualScreen/DualScreen/TwoPanePropertiesGallery.xaml.cs b/DualScreen/DualScreen/TwoPanePropertiesGallery.xaml.cs
index 2f358067415..67f61fe1c09 100644
--- a/DualScreen/DualScreen/TwoPanePropertiesGallery.xaml.cs
+++ b/DualScreen/DualScreen/TwoPanePropertiesGallery.xaml.cs
@@ -38,18 +38,18 @@ protected override void OnAppearing()
TallModeConfiguration.SelectedIndex = 1;
WideModeConfiguration.SelectedIndex = 1;
- //DualScreenInfo.Current.HingeAngleChanged += OnHingeAngleChanged;
+ DualScreenInfo.Current.HingeAngleChanged += OnHingeAngleChanged;
}
protected override void OnDisappearing()
{
- //DualScreenInfo.Current.HingeAngleChanged -= OnHingeAngleChanged;
+ DualScreenInfo.Current.HingeAngleChanged -= OnHingeAngleChanged;
}
- //void OnHingeAngleChanged(object sender, DualScreen.HingeAngleChangedEventArgs e)
- //{
- // lblHingeAngle.Text = e.HingeAngleInDegrees.ToString();
- //}
+ void OnHingeAngleChanged(object sender, HingeAngleChangedEventArgs e)
+ {
+ lblHingeAngle.Text = e.HingeAngleInDegrees.ToString();
+ }
void Setup(double width, double height)
{
@@ -72,4 +72,4 @@ void OnReset(object sender, EventArgs e)
Pane2Length.Value = 0.5;
}
}
-}
\ No newline at end of file
+}
diff --git a/Environment.Build.props b/Environment.Build.props
index a39567fd2e6..ff76b375092 100644
--- a/Environment.Build.props
+++ b/Environment.Build.props
@@ -4,7 +4,8 @@
-
+
+
false
true
@@ -45,4 +46,4 @@
True
True
-
\ No newline at end of file
+
diff --git a/GitInfo.txt b/GitInfo.txt
index 6016e8addc4..f6cdf40983f 100644
--- a/GitInfo.txt
+++ b/GitInfo.txt
@@ -1 +1 @@
-4.6.0
+4.7.0
diff --git a/Nuget.targets b/Nuget.targets
new file mode 100644
index 00000000000..518a38de3f0
--- /dev/null
+++ b/Nuget.targets
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Nuget/.gitignore b/Nuget/.gitignore
new file mode 100644
index 00000000000..86d0cb2726c
--- /dev/null
+++ b/Nuget/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
\ No newline at end of file
diff --git a/PagesGallery/PagesGallery.Droid/PagesGallery.Droid.csproj b/PagesGallery/PagesGallery.Droid/PagesGallery.Droid.csproj
index 05da9049ef8..011e8fe85fa 100644
--- a/PagesGallery/PagesGallery.Droid/PagesGallery.Droid.csproj
+++ b/PagesGallery/PagesGallery.Droid/PagesGallery.Droid.csproj
@@ -114,12 +114,8 @@
Xamarin.Forms.Xaml
+ {343ebf1b-f26d-4d4d-b7d9-f6e25247af31}
PagesGallery
- {7B5F9E6A-6334-4C74-9B77-A55B3DA60E41}
-
-
- {e1586ce6-8eac-4388-a15a-1aabf108b5f8}
- Xamarin.Forms.Material.Android
@@ -286,4 +282,4 @@
-
+
\ No newline at end of file
diff --git a/PagesGallery/PagesGallery.UWP/PagesGallery.UWP.csproj b/PagesGallery/PagesGallery.UWP/PagesGallery.UWP.csproj
index 278c7f29691..95289841899 100644
--- a/PagesGallery/PagesGallery.UWP/PagesGallery.UWP.csproj
+++ b/PagesGallery/PagesGallery.UWP/PagesGallery.UWP.csproj
@@ -157,11 +157,11 @@
6.0.15
- 2.3.191211002
+ 2.4.2
14.0
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index dc87f7628ad..1a783ca6b28 100644
--- a/README.md
+++ b/README.md
@@ -143,7 +143,7 @@ You should now be able to run any of the UWP UI Tests.
## Coding Style ##
-We follow the style used by the [.NET Foundation](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md), with a few exceptions:
+We follow the style used by the [.NET Foundation](https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/coding-style.md), with a few exceptions:
- We do not use the `private` keyword as it is the default accessibility level in C#.
- We use hard tabs over spaces. You can change this setting in Visual Studio for Windows via `Tools > Options` and navigating to `Text Editor > C#` and selecting the "Keep tabs" radio option. In Visual Studio for Mac it's set via preferences in `Source Code > Code Formatting > C# source code` and disabling the checkbox for `Convert tabs to spaces`.
diff --git a/Stubs/Xamarin.Forms.Platform.cs b/Stubs/Xamarin.Forms.Platform.cs
index 3e8331b88c5..1dd7032c1ec 100644
--- a/Stubs/Xamarin.Forms.Platform.cs
+++ b/Stubs/Xamarin.Forms.Platform.cs
@@ -174,6 +174,26 @@ internal class _RefreshViewRenderer { }
[RenderWith(typeof(SwipeViewRenderer))]
internal class _SwipeViewRenderer { }
+
+#if !TIZEN4_0
+ [RenderWith(typeof(PathRenderer))]
+ internal class _PathRenderer { }
+
+ [RenderWith(typeof(EllipseRenderer))]
+ internal class _EllipseRenderer { }
+
+ [RenderWith(typeof(LineRenderer))]
+ internal class _LineRenderer { }
+
+ [RenderWith(typeof(PolylineRenderer))]
+ internal class _PolylineRenderer { }
+
+ [RenderWith(typeof(PolygonRenderer))]
+ internal class _PolygonRenderer { }
+
+ [RenderWith(typeof(RectangleRenderer))]
+ internal class _RectangleRenderer { }
+#endif
}
diff --git a/UWP.Build.props b/UWP.Build.props
index f7762a17cb0..a96966cf411 100644
--- a/UWP.Build.props
+++ b/UWP.Build.props
@@ -1,11 +1,27 @@
-
- uap10.0.14393;uap10.0.16299;
+
+ $(UwpMinTargetFrameworks)
+
+
+ uap10.0.14393;uap10.0.16299
+
netstandard2.0
+
+
10.0.16299.0
10.0.18362.0
+ 10.0.19559.0
-
+
+ $(DefineConstants);UWP
+
+
$(DefineConstants);UWP_14393
+
+ $(DefineConstants);UWP_18362
+
+
+ $(DefineConstants);UWP_18362;UWP_19000
+
diff --git a/UWP.Build.targets b/UWP.Build.targets
index 8fd6159a13c..614d6a57b86 100644
--- a/UWP.Build.targets
+++ b/UWP.Build.targets
@@ -2,11 +2,16 @@
-
+
Windows Mobile Extensions for the UWP
+
+
+ Windows Mobile Extensions for the UWP
+
+
diff --git a/Xamarin.Forms.Build.Tasks/CssGenerator.cs b/Xamarin.Forms.Build.Tasks/CssGenerator.cs
index 276e352aa04..16db7e4c746 100644
--- a/Xamarin.Forms.Build.Tasks/CssGenerator.cs
+++ b/Xamarin.Forms.Build.Tasks/CssGenerator.cs
@@ -1,12 +1,11 @@
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
-
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.CSharp;
-
using Xamarin.Forms.Xaml;
+using IOPath = System.IO.Path;
namespace Xamarin.Forms.Build.Tasks
{
@@ -79,7 +78,7 @@ public bool Execute()
void GenerateCode()
{
//Create the target directory if required
- Directory.CreateDirectory(Path.GetDirectoryName(OutputFile));
+ Directory.CreateDirectory(IOPath.GetDirectoryName(OutputFile));
var ccu = new CodeCompileUnit();
ccu.AssemblyCustomAttributes.Add(
diff --git a/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs
index 25be35bbdd4..cd83090e020 100644
--- a/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs
+++ b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs
@@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
-
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
-
using static Microsoft.Build.Framework.MessageImportance;
+using IOPath = System.IO.Path;
namespace Xamarin.Forms.Build.Tasks
{
@@ -28,7 +26,7 @@ public override bool Execute(out IList thrownExceptions)
if (!string.IsNullOrEmpty(ReferencePath)) {
var paths = ReferencePath.Replace("//", "/").Split(';');
foreach (var p in paths) {
- var searchpath = Path.GetDirectoryName(p);
+ var searchpath = IOPath.GetDirectoryName(p);
LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}Adding searchpath {searchpath}");
resolver.AddSearchDirectory(searchpath);
}
diff --git a/Xamarin.Forms.Build.Tasks/XamlCTask.cs b/Xamarin.Forms.Build.Tasks/XamlCTask.cs
index 465b877c16a..1d8a7d3f601 100644
--- a/Xamarin.Forms.Build.Tasks/XamlCTask.cs
+++ b/Xamarin.Forms.Build.Tasks/XamlCTask.cs
@@ -5,14 +5,12 @@
using System.Linq;
using System.Xml;
using Microsoft.Build.Framework;
-
using Mono.Cecil;
using Mono.Cecil.Cil;
-
using Xamarin.Forms.Xaml;
-
using static Microsoft.Build.Framework.MessageImportance;
using static Mono.Cecil.Cil.OpCodes;
+using IOPath = System.IO.Path;
namespace Xamarin.Forms.Build.Tasks
{
@@ -65,7 +63,7 @@ public override bool Execute(out IList thrownExceptions)
if (!string.IsNullOrEmpty(ReferencePath)) {
var paths = ReferencePath.Replace("//", "/").Split(';').Distinct();
foreach (var p in paths) {
- var searchpath = Path.GetDirectoryName(p);
+ var searchpath = IOPath.GetDirectoryName(p);
LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}Adding searchpath {searchpath}");
xamlCResolver.AddSearchDirectory(searchpath);
}
@@ -82,7 +80,7 @@ public override bool Execute(out IList thrownExceptions)
ReadSymbols = debug && !ValidateOnly, // We don't need symbols for ValidateOnly, since we won't be writing
};
- using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(Path.GetFullPath(Assembly),readerParameters)) {
+ using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(IOPath.GetFullPath(Assembly),readerParameters)) {
CustomAttribute xamlcAttr;
if (assemblyDefinition.HasCustomAttributes &&
(xamlcAttr =
diff --git a/Xamarin.Forms.Build.Tasks/XamlGTask.cs b/Xamarin.Forms.Build.Tasks/XamlGTask.cs
index 2cbbf9ed6a8..4a8f6311d19 100644
--- a/Xamarin.Forms.Build.Tasks/XamlGTask.cs
+++ b/Xamarin.Forms.Build.Tasks/XamlGTask.cs
@@ -1,9 +1,9 @@
using System;
using System.IO;
using System.Xml;
-
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
+using IOPath = System.IO.Path;
namespace Xamarin.Forms.Build.Tasks
{
@@ -38,9 +38,9 @@ public override bool Execute()
for (int i = 0; i < XamlFiles.Length; i++) {
var xamlFile = XamlFiles[i];
var outputFile = OutputFiles[i].ItemSpec;
- if (Path.DirectorySeparatorChar == '/' && outputFile.Contains(@"\"))
+ if (IOPath.DirectorySeparatorChar == '/' && outputFile.Contains(@"\"))
outputFile = outputFile.Replace('\\','/');
- else if (Path.DirectorySeparatorChar == '\\' && outputFile.Contains(@"/"))
+ else if (IOPath.DirectorySeparatorChar == '\\' && outputFile.Contains(@"/"))
outputFile = outputFile.Replace('/', '\\');
var generator = new XamlGenerator(xamlFile, Language, AssemblyName, outputFile, References, Log);
@@ -52,10 +52,12 @@ public override bool Execute()
}
catch (XmlException xe) {
Log.LogError(null, null, null, xamlFile.ItemSpec, xe.LineNumber, xe.LinePosition, 0, 0, xe.Message, xe.HelpLink, xe.Source);
+ Log.LogMessage(MessageImportance.Low, xe.StackTrace);
success = false;
}
catch (Exception e) {
Log.LogError(null, null, null, xamlFile.ItemSpec, 0, 0, 0, 0, e.Message, e.HelpLink, e.Source);
+ Log.LogMessage(MessageImportance.Low, e.StackTrace);
success = false;
}
}
diff --git a/Xamarin.Forms.Build.Tasks/XamlGenerator.cs b/Xamarin.Forms.Build.Tasks/XamlGenerator.cs
index 9d4f87e0bc8..049abbff7f7 100644
--- a/Xamarin.Forms.Build.Tasks/XamlGenerator.cs
+++ b/Xamarin.Forms.Build.Tasks/XamlGenerator.cs
@@ -11,6 +11,7 @@
using Xamarin.Forms.Xaml;
using Xamarin.Forms.Internals;
using Mono.Cecil;
+using IOPath = System.IO.Path;
namespace Xamarin.Forms.Build.Tasks
{
@@ -173,7 +174,7 @@ internal bool ParseXaml(TextReader xaml)
void GenerateCode()
{
//Create the target directory if required
- Directory.CreateDirectory(Path.GetDirectoryName(OutputFile));
+ Directory.CreateDirectory(IOPath.GetDirectoryName(OutputFile));
var ccu = new CodeCompileUnit();
ccu.AssemblyCustomAttributes.Add(
@@ -351,8 +352,6 @@ CodeTypeReference GetType(XmlType xmlType,
static string GetClrNamespace(string namespaceuri)
{
- if (namespaceuri == XamlParser.XFUri)
- return "Xamarin.Forms";
if (namespaceuri == XamlParser.X2009Uri)
return "System";
if (namespaceuri != XamlParser.X2006Uri &&
@@ -390,7 +389,7 @@ void GatherXmlnsDefinitionAttributes()
string[] paths = References.Split(';').Distinct().ToArray();
foreach (var path in paths) {
- string asmName = Path.GetFileName(path);
+ string asmName = IOPath.GetFileName(path);
if (AssemblyIsSystem(asmName))
// Skip the myriad "System." assemblies and others
continue;
@@ -408,6 +407,8 @@ void GatherXmlnsDefinitionAttributes()
bool AssemblyIsSystem(string name)
{
+ if (name.StartsWith("System.Maui", StringComparison.CurrentCultureIgnoreCase))
+ return false;
if (name.StartsWith("System.", StringComparison.CurrentCultureIgnoreCase))
return true;
else if (name.Equals("mscorlib.dll", StringComparison.CurrentCultureIgnoreCase))
@@ -430,7 +431,7 @@ CodeTypeReference GetCustomNamespaceUrlType(XmlType xmlType)
(typeInfo) =>
{
ModuleDefinition module = null;
- if (!_xmlnsModules.TryGetValue(typeInfo.AssemblyName, out module))
+ if (typeInfo.AssemblyName == null || !_xmlnsModules.TryGetValue(typeInfo.AssemblyName, out module))
return null;
string typeName = typeInfo.TypeName.Replace('+', '/'); //Nested types
string fullName = $"{typeInfo.ClrNamespace}.{typeInfo.TypeName}";
diff --git a/Xamarin.Forms.ControlGallery.Android/Android10.Build.targets b/Xamarin.Forms.ControlGallery.Android/Android10.Build.targets
index 64e2aef9027..9406850d2ac 100644
--- a/Xamarin.Forms.ControlGallery.Android/Android10.Build.targets
+++ b/Xamarin.Forms.ControlGallery.Android/Android10.Build.targets
@@ -9,4 +9,4 @@
-
\ No newline at end of file
+
diff --git a/Xamarin.Forms.ControlGallery.Android/CacheService.cs b/Xamarin.Forms.ControlGallery.Android/CacheService.cs
index 1f371656d25..fb86a8fa9d1 100644
--- a/Xamarin.Forms.ControlGallery.Android/CacheService.cs
+++ b/Xamarin.Forms.ControlGallery.Android/CacheService.cs
@@ -1,6 +1,6 @@
-using System.IO;
using System.IO.IsolatedStorage;
using Xamarin.Forms.Controls;
+using IOPath = System.IO.Path;
namespace Xamarin.Forms.ControlGallery.Android
{
@@ -15,9 +15,9 @@ static void DeleteFilesInDirectory (string directory)
{
using (IsolatedStorageFile isolatedStorage = IsolatedStorageFile.GetUserStoreForApplication ()) {
if (isolatedStorage.DirectoryExists (directory)) {
- var files = isolatedStorage.GetFileNames (Path.Combine (directory, "*"));
+ var files = isolatedStorage.GetFileNames (IOPath.Combine (directory, "*"));
foreach (string file in files) {
- isolatedStorage.DeleteFile (Path.Combine (directory, file));
+ isolatedStorage.DeleteFile (IOPath.Combine (directory, file));
}
}
}
diff --git a/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs b/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs
index 7282a91a105..049db3a33fd 100644
--- a/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs
+++ b/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs
@@ -30,9 +30,9 @@
using FragmentTransaction = Android.Support.V4.App.FragmentTransaction;
using NestedScrollView = global::Android.Support.V4.Widget.NestedScrollView;
#endif
-using System.IO;
using AMenuItemCompat = global::Android.Support.V4.View.MenuItemCompat;
using Android.Support.V4.Content;
+using IOPath = System.IO.Path;
[assembly: ExportRenderer(typeof(Issue5461.ScrollbarFadingEnabledFalseScrollView), typeof(ScrollbarFadingEnabledFalseScrollViewRenderer))]
[assembly: ExportRenderer(typeof(Issue1942.CustomGrid), typeof(Issue1942GridRenderer))]
@@ -104,7 +104,7 @@ protected override void UpdateMenuItemIcon(Context context, IMenuItem menuItem,
if (toolBarItem.IconImageSource is FileImageSource fileImageSource)
{
- var name = Path.GetFileNameWithoutExtension(fileImageSource.File);
+ var name = IOPath.GetFileNameWithoutExtension(fileImageSource.File);
var id = Xamarin.Forms.Platform.Android.ResourceManager.GetDrawableByName(name);
if (id != 0)
{
diff --git a/Xamarin.Forms.ControlGallery.Android/Directory.Build.targets b/Xamarin.Forms.ControlGallery.Android/Directory.Build.targets
index fe3b8e64991..680415c9bd3 100644
--- a/Xamarin.Forms.ControlGallery.Android/Directory.Build.targets
+++ b/Xamarin.Forms.ControlGallery.Android/Directory.Build.targets
@@ -1,3 +1,6 @@
-
\ No newline at end of file
+
+
+
+
diff --git a/Xamarin.Forms.ControlGallery.Android/Nuget2017.Build.targets b/Xamarin.Forms.ControlGallery.Android/Nuget2017.Build.targets
new file mode 100644
index 00000000000..d5c7b72cec8
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Nuget2017.Build.targets
@@ -0,0 +1,7 @@
+
+
+
+ 0.4.11
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Nuget2019.Build.targets b/Xamarin.Forms.ControlGallery.Android/Nuget2019.Build.targets
new file mode 100644
index 00000000000..aea0f99001a
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Nuget2019.Build.targets
@@ -0,0 +1,17 @@
+
+
+
+ 0.10.0
+
+
+
+
+ 1.0.0
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/PlatformSpecificCoreGalleryFactory.cs b/Xamarin.Forms.ControlGallery.Android/PlatformSpecificCoreGalleryFactory.cs
index bb739bd7c01..d5a5df24af5 100644
--- a/Xamarin.Forms.ControlGallery.Android/PlatformSpecificCoreGalleryFactory.cs
+++ b/Xamarin.Forms.ControlGallery.Android/PlatformSpecificCoreGalleryFactory.cs
@@ -3,11 +3,13 @@
using Xamarin.Forms;
using Xamarin.Forms.ControlGallery.Android;
using Xamarin.Forms.Controls;
+using Xamarin.Forms.Internals;
[assembly: Dependency(typeof(PlatformSpecificCoreGalleryFactory))]
namespace Xamarin.Forms.ControlGallery.Android
{
+ [Preserve(AllMembers = true)]
public class PlatformSpecificCoreGalleryFactory : IPlatformSpecificCoreGalleryFactory
{
public string Title => "Android Core Gallery";
diff --git a/Xamarin.Forms.ControlGallery.Android/StaggeredCollectionViewRenderer.cs b/Xamarin.Forms.ControlGallery.Android/StaggeredCollectionViewRenderer.cs
index 2b27c4f3a41..58d193450f8 100644
--- a/Xamarin.Forms.ControlGallery.Android/StaggeredCollectionViewRenderer.cs
+++ b/Xamarin.Forms.ControlGallery.Android/StaggeredCollectionViewRenderer.cs
@@ -1,6 +1,5 @@
using System;
using Android.Content;
-using Android.Graphics;
#if __ANDROID_29__
using AndroidX.AppCompat.Widget;
using AndroidX.RecyclerView.Widget;
@@ -11,6 +10,7 @@
using Xamarin.Forms.ControlGallery.Android;
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.AlternateLayoutGalleries;
using Xamarin.Forms.Platform.Android;
+using ARect = Android.Graphics.Rect;
using AView = Android.Views.View;
[assembly: ExportRenderer(typeof(StaggeredCollectionView), typeof(StaggeredCollectionViewRenderer))]
@@ -70,7 +70,7 @@ public SpacingItemDecoration(StaggeredGridItemsLayout itemsLayout)
}
}
- public override void GetItemOffsets(Rect outRect, AView view, RecyclerView parent, RecyclerView.State state)
+ public override void GetItemOffsets(ARect outRect, AView view, RecyclerView parent, RecyclerView.State state)
{
base.GetItemOffsets(outRect, view, parent, state);
diff --git a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
index 238627685fd..7d7cafc2403 100644
--- a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
+++ b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
@@ -132,6 +132,7 @@
+
@@ -178,11 +179,7 @@
-
-
- {6e53feb1-1100-46ae-8013-17bba35cc197}
- Xamarin.Forms.Platform.Android (Forwarders)
-
+
{cb9c96ce-125c-4a68-b6a1-c3ff1fbf93e1}
Xamarin.Forms.Controls
@@ -191,6 +188,19 @@
{4dcd0420-1168-4b77-86db-6196ee4bd491}
Xamarin.Forms.CustomAttributes
+
+ {a4c57790-71d1-467c-a69e-2bd84cb6666c}
+ Xamarin.Forms.Platform.Android.UnitTests
+
+
+
+
+ proguard.cfg
+
+
+ {6e53feb1-1100-46ae-8013-17bba35cc197}
+ Xamarin.Forms.Platform.Android (Forwarders)
+
{bd50b39a-ebc5-408f-9c5e-923a8ebae473}
Xamarin.Forms.Maps.Android
@@ -207,10 +217,6 @@
{3b72465b-acae-43ae-9327-10f372fe5f80}
Xamarin.Forms.Platform.Android.FormsViewGroup
-
- {a4c57790-71d1-467c-a69e-2bd84cb6666c}
- Xamarin.Forms.Platform.Android.UnitTests
-
{0e16e70a-d6dd-4323-ad5d-363abff42d6a}
Xamarin.Forms.Platform.Android
@@ -342,9 +348,6 @@
3.0.2
-
- 0.7.1
-
@@ -378,11 +381,6 @@
-
-
- proguard.cfg
-
-
@@ -412,4 +410,4 @@
-
\ No newline at end of file
+
diff --git a/Xamarin.Forms.ControlGallery.Android/_10940CustomRenderer.cs b/Xamarin.Forms.ControlGallery.Android/_10940CustomRenderer.cs
new file mode 100644
index 00000000000..ae1cffc753a
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/_10940CustomRenderer.cs
@@ -0,0 +1,27 @@
+using Android.Content;
+using Xamarin.Forms;
+using Xamarin.Forms.ControlGallery.Android;
+using Xamarin.Forms.Controls.Issues;
+using Xamarin.Forms.Platform.Android;
+
+[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
+[assembly: ExportRenderer(typeof(CustomSwipeView), typeof(CustomSwipeViewRenderer))]
+namespace Xamarin.Forms.ControlGallery.Android
+{
+ public class CustomEntryRenderer : EntryRenderer
+ {
+ public CustomEntryRenderer(Context context) : base(context)
+ {
+ System.Diagnostics.Debug.WriteLine("Create CustomEntry");
+ }
+ }
+
+ public class CustomSwipeViewRenderer : SwipeViewRenderer
+ {
+
+ public CustomSwipeViewRenderer(Context context) : base(context)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.MacOS/NativeServices.cs b/Xamarin.Forms.ControlGallery.MacOS/NativeServices.cs
index df0b19c03ca..27f0378b3b3 100644
--- a/Xamarin.Forms.ControlGallery.MacOS/NativeServices.cs
+++ b/Xamarin.Forms.ControlGallery.MacOS/NativeServices.cs
@@ -1,12 +1,15 @@
using System;
using System.IO;
+using AppKit;
using Xamarin.Forms;
using Xamarin.Forms.ControlGallery.MacOS;
using Xamarin.Forms.Controls;
using Xamarin.Forms.Platform.MacOS;
+using IOPath = System.IO.Path;
[assembly: Dependency(typeof(TestCloudService))]
[assembly: Dependency(typeof(CacheService))]
+[assembly: Dependency(typeof(NativeColorService))]
[assembly: ExportRenderer(typeof(DisposePage), typeof(DisposePageRenderer))]
[assembly: ExportRenderer(typeof(DisposeLabel), typeof(DisposeLabelRenderer))]
@@ -17,7 +20,7 @@ public class CacheService : ICacheService
public void ClearImageCache()
{
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
- var cache = Path.Combine(documents, ".config", ".isolated-storage", "ImageLoaderCache");
+ var cache = IOPath.Combine(documents, ".config", ".isolated-storage", "ImageLoaderCache");
foreach (var file in Directory.GetFiles(cache))
{
File.Delete(file);
@@ -25,6 +28,17 @@ public void ClearImageCache()
}
}
+ public class NativeColorService : INativeColorService
+ {
+ public Color GetConvertedColor(bool shouldCrash)
+ {
+ if (shouldCrash)
+ return NSColor.ControlText.ToColor();
+
+ return NSColor.ControlText.ToColor(NSColorSpace.DeviceRGBColorSpace);
+ }
+ }
+
public class DisposePageRenderer : PageRenderer
{
protected override void Dispose(bool disposing)
diff --git a/Xamarin.Forms.ControlGallery.Tizen/ControlGallery.Tizen.cs b/Xamarin.Forms.ControlGallery.Tizen/ControlGallery.Tizen.cs
index c737c052a71..9ef12bd05ac 100644
--- a/Xamarin.Forms.ControlGallery.Tizen/ControlGallery.Tizen.cs
+++ b/Xamarin.Forms.ControlGallery.Tizen/ControlGallery.Tizen.cs
@@ -22,7 +22,7 @@ static void Main(string[] args)
{
var app = new MainApplication();
FormsMaps.Init("HERE", "write-your-API-key-here");
- Forms.SetFlags("CollectionView_Experimental", "Shell_Experimental", "MediaElement_Experimental", "IndicatorView_Experimental");
+ Forms.SetFlags("CollectionView_Experimental", "Shell_Experimental", "MediaElement_Experimental");
Forms.Init(app);
FormsMaterial.Init();
app.Run(args);
diff --git a/Xamarin.Forms.ControlGallery.WPF/Renderers/Issue6693ControlRenderer.cs b/Xamarin.Forms.ControlGallery.WPF/Renderers/Issue6693ControlRenderer.cs
index 124f14cba1b..c5b03607035 100644
--- a/Xamarin.Forms.ControlGallery.WPF/Renderers/Issue6693ControlRenderer.cs
+++ b/Xamarin.Forms.ControlGallery.WPF/Renderers/Issue6693ControlRenderer.cs
@@ -1,17 +1,11 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
+using System.Windows;
using System.Windows.Media;
using Xamarin.Forms.ControlGallery.WPF.Renderers;
using Xamarin.Forms.Controls.Issues;
using Xamarin.Forms.Platform.WPF;
-using WColor = System.Windows.Media.Color;
+using WRect = System.Windows.Rect;
-[assembly:ExportRenderer(typeof(Issue6693Control), typeof(Issue6693ControlRenderer))]
+[assembly: ExportRenderer(typeof(Issue6693Control), typeof(Issue6693ControlRenderer))]
namespace Xamarin.Forms.ControlGallery.WPF.Renderers
{
public class Issue6693ControlRenderer:ViewRenderer
@@ -35,7 +29,7 @@ public WIssue6693Control()
protected override void OnRender(DrawingContext drawingContext)
{
- drawingContext.DrawRectangle(Brushes.LightGray, new Pen(Brushes.Black, 1), new Rect(0,0,ActualWidth, ActualHeight));
+ drawingContext.DrawRectangle(Brushes.LightGray, new Pen(Brushes.Black, 1), new WRect(0,0,ActualWidth, ActualHeight));
var isEnabledText = IsEnabled ? "I'm enabled :)" : "I'm disabled :(";
drawingContext.DrawText(new FormattedText(isEnabledText,
System.Globalization.CultureInfo.CurrentCulture,
diff --git a/Xamarin.Forms.ControlGallery.WPF/Xamarin.Forms.ControlGallery.WPF.csproj b/Xamarin.Forms.ControlGallery.WPF/Xamarin.Forms.ControlGallery.WPF.csproj
index f7538980f57..ed555198aed 100644
--- a/Xamarin.Forms.ControlGallery.WPF/Xamarin.Forms.ControlGallery.WPF.csproj
+++ b/Xamarin.Forms.ControlGallery.WPF/Xamarin.Forms.ControlGallery.WPF.csproj
@@ -142,5 +142,8 @@
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.WPF/crimson.jpg b/Xamarin.Forms.ControlGallery.WPF/crimson.jpg
new file mode 100644
index 00000000000..e0be19c886f
Binary files /dev/null and b/Xamarin.Forms.ControlGallery.WPF/crimson.jpg differ
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/BrokenNativeControl.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/BrokenNativeControl.cs
index 6d98aefc2b1..b66d86bad62 100644
--- a/Xamarin.Forms.ControlGallery.WindowsUniversal/BrokenNativeControl.cs
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/BrokenNativeControl.cs
@@ -1,10 +1,10 @@
-using Windows.Foundation;
using Windows.Graphics.Display;
using Windows.UI;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
+using WRect = Windows.Foundation.Rect;
namespace Xamarin.Forms.ControlGallery.WindowsUniversal
{
@@ -47,7 +47,7 @@ public string Text
protected override Windows.Foundation.Size ArrangeOverride(Windows.Foundation.Size finalSize)
{
- _textBlock.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
+ _textBlock.Arrange(new WRect(0, 0, finalSize.Width, finalSize.Height));
return finalSize;
}
@@ -57,7 +57,7 @@ protected override Windows.Foundation.Size MeasureOverride (Windows.Foundation.
_textBlock.Measure (availableSize);
// This deliberately does something wrong so we can demo fixing it
- Rect bounds = ApplicationView.GetForCurrentView ().VisibleBounds;
+ WRect bounds = ApplicationView.GetForCurrentView ().VisibleBounds;
double scaleFactor = DisplayInformation.GetForCurrentView ().RawPixelsPerViewPixel;
var size = new Size (bounds.Width * scaleFactor, bounds.Height * scaleFactor);
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs
index 0cc317d10b8..152a42d27ff 100644
--- a/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs
@@ -5,10 +5,11 @@
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
-using Windows.UI.Xaml.Shapes;
using Xamarin.Forms.ControlGallery.WindowsUniversal;
using Xamarin.Forms.Controls.Issues;
using Xamarin.Forms.Platform.UWP;
+using WEllipse = Windows.UI.Xaml.Shapes.Ellipse;
+using WShape = Windows.UI.Xaml.Shapes.Shape;
[assembly: ExportRenderer(typeof(Xamarin.Forms.Controls.Issues.Bugzilla42602.TextBoxView), typeof(Xamarin.Forms.ControlGallery.WindowsUniversal.TextBoxViewRenderer))]
[assembly: ExportRenderer(typeof(Issue1683.EntryKeyboardFlags), typeof(EntryRendererKeyboardFlags))]
@@ -143,7 +144,7 @@ protected override void OnElementChanged(ElementChangedEventArgs e)
Children.Add(m_Canvas);
//ellipse
- Shape ellipse = new Ellipse()
+ WShape ellipse = new WEllipse()
{
Width = 100,
Height = 100,
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Directory.Build.targets b/Xamarin.Forms.ControlGallery.WindowsUniversal/Directory.Build.targets
new file mode 100644
index 00000000000..6f72129c885
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Directory.Build.targets
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs
index 63c56eb4f20..d095e91118e 100644
--- a/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs
@@ -9,13 +9,12 @@
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
-using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Xamarin.Forms;
-using Xamarin.Forms.ControlGallery.WindowsUniversal;
using Xamarin.Forms.Controls;
using Xamarin.Forms.Platform.UWP;
-
+using WRectangleGeometry = Windows.UI.Xaml.Media.RectangleGeometry;
+using WRect = Windows.Foundation.Rect;
namespace Xamarin.Forms.ControlGallery.WindowsUniversal
{
@@ -119,17 +118,17 @@ void AddNativeControls(NestedNativeControlGalleryPage page)
// The broken control always tries to size itself to the screen width
// So figure that out and we'll know how far off it's laying itself out
- Rect bounds = ApplicationView.GetForCurrentView().VisibleBounds;
+ WRect bounds = ApplicationView.GetForCurrentView().VisibleBounds;
double scaleFactor = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
var screenWidth = new Size(bounds.Width * scaleFactor, bounds.Height * scaleFactor);
// We can re-center it by offsetting it during the Arrange call
double diff = Math.Abs(screenWidth.Width - finalSize.Width) / -2;
- frameworkElement.Arrange(new Rect(diff, 0, finalSize.Width - diff, finalSize.Height));
+ frameworkElement.Arrange(new WRect(diff, 0, finalSize.Width - diff, finalSize.Height));
// Arranging the control to the left will make it show up past the edge of the stack layout
// We can fix that by clipping it manually
- var clip = new RectangleGeometry { Rect = new Rect(-diff, 0, finalSize.Width, finalSize.Height) };
+ var clip = new RectangleGeometry { Rect = new WRect(-diff, 0, finalSize.Width, finalSize.Height) };
frameworkElement.Clip = clip;
return finalSize;
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj b/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj
index 69f63d2da39..b95d75edd95 100644
--- a/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj
@@ -101,6 +101,18 @@
{cb9c96ce-125c-4a68-b6a1-c3ff1fbf93e1}
Xamarin.Forms.Controls
+
+ {8f245976-f555-4814-b935-9ad58f4d2871}
+ Xamarin.Forms.Platform.UAP.UnitTests
+
+
+
+
+ {4dcd0420-1168-4b77-86db-6196ee4bd491}
+ Xamarin.Forms.CustomAttributes
+
+
+
{57b8b73d-c3b5-4c42-869e-7b2f17d354ac}
Xamarin.Forms.Core
@@ -113,10 +125,6 @@
{7d13bac2-c6a4-416a-b07e-c169b199e52b}
Xamarin.Forms.Maps
-
- {8f245976-f555-4814-b935-9ad58f4d2871}
- Xamarin.Forms.Platform.UAP.UnitTests
-
{00d8d049-ffaa-4759-8fc9-1eca30777f72}
Xamarin.Forms.Platform.UAP
@@ -249,7 +257,7 @@
6.2.9
- 2.3.191211002
+ 2.4.2
11.0.2
diff --git a/Xamarin.Forms.ControlGallery.iOS/AppDelegate.cs b/Xamarin.Forms.ControlGallery.iOS/AppDelegate.cs
index 15f825bb989..ed7b3c7c52b 100644
--- a/Xamarin.Forms.ControlGallery.iOS/AppDelegate.cs
+++ b/Xamarin.Forms.ControlGallery.iOS/AppDelegate.cs
@@ -10,6 +10,7 @@
using Xamarin.Forms.Controls;
using Xamarin.Forms.Controls.Issues;
using Xamarin.Forms.Platform.iOS;
+using IOPath = System.IO.Path;
[assembly: Dependency(typeof(TestCloudService))]
[assembly: Dependency(typeof(CacheService))]
@@ -40,7 +41,7 @@ public class CacheService : ICacheService
public void ClearImageCache()
{
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
- var cache = Path.Combine(documents, ".config", ".isolated-storage", "ImageLoaderCache");
+ var cache = IOPath.Combine(documents, ".config", ".isolated-storage", "ImageLoaderCache");
foreach (var file in Directory.GetFiles(cache))
{
File.Delete(file);
diff --git a/Xamarin.Forms.ControlGallery.iOS/Directory.Build.targets b/Xamarin.Forms.ControlGallery.iOS/Directory.Build.targets
new file mode 100644
index 00000000000..6f72129c885
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Directory.Build.targets
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
index 3a9a3d518b6..155811707c9 100644
--- a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
+++ b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
@@ -3,7 +3,8 @@
Debug
- iPhone
+ iPhone
+ iPhoneSimulator
{C7131F14-274F-4B55-ACA9-E81731AD012F}
{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Exe
@@ -148,6 +149,20 @@
{cb9c96ce-125c-4a68-b6a1-c3ff1fbf93e1}
Xamarin.Forms.Controls
+
+ {52c50e88-7f15-45fe-a63c-8e7c76ca2bae}
+ Xamarin.Forms.Platform.iOS.UnitTests
+ false
+ false
+
+
+
+
+ {4dcd0420-1168-4b77-86db-6196ee4bd491}
+ Xamarin.Forms.CustomAttributes
+
+
+
{aba078c4-f9bb-4924-8b2b-10fe0d2f5491}
Xamarin.Forms.Maps.iOS
@@ -162,12 +177,6 @@
{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}
Xamarin.Forms.Core
-
- {52c50e88-7f15-45fe-a63c-8e7c76ca2bae}
- Xamarin.Forms.Platform.iOS.UnitTests
- false
- false
-
{271193c1-6e7c-429c-a36d-3f1be5267231}
Xamarin.Forms.Platform.iOS
@@ -429,4 +438,4 @@
-
\ No newline at end of file
+
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Controls/INativeColorService.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Controls/INativeColorService.cs
new file mode 100644
index 00000000000..48cf5363dad
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Controls/INativeColorService.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms.Controls
+{
+ public interface INativeColorService
+ {
+ Color GetConvertedColor(bool shouldCrash);
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10134.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10134.cs
new file mode 100644
index 00000000000..c3aea8d4990
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10134.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.Shell)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 10134, "Shell Top Tabbar focus issue", PlatformAffected.iOS)]
+ public class Issue10134 : TestShell
+ {
+ protected override void Init()
+ {
+ ContentPage page1 = AddTopTab("Tab 1");
+ page1.Title = "Top Bar Page 1";
+
+ for(int i = 2; i < 20; i++)
+ {
+ AddTopTab($"Tab {i}");
+ }
+
+ page1.Content =
+ new StackLayout()
+ {
+ Children =
+ {
+ new Label()
+ {
+ Text = "Scroll and click on any of the currently non visible tabs. After clicking, if the Top Tabs don't scroll back to the beginninig the test has passed"
+ }
+ }
+ };
+ }
+
+#if UITEST && __SHELL__
+ [Test]
+ public void TopTabsDontScrollBackToStartWhenSelected()
+ {
+ var element1 = RunningApp.WaitForElement("Tab 1", "Shell hasn't loaded")[0].Rect;
+ RunningApp.WaitForNoElement("Tab 12", "Tab shouldn't be visible");
+
+ UITest.Queries.AppRect element2 = element1;
+
+ for (int i = 2; i < 20; i++)
+ {
+ var results = RunningApp.Query($"Tab {i}");
+
+ if (results.Length == 0)
+ break;
+
+ element2 = results[0].Rect;
+ }
+
+ RunningApp.DragCoordinates(element2.CenterX, element2.CenterY, element1.CenterX, element1.CenterY);
+
+ RunningApp.WaitForNoElement("Tab 1");
+ bool testPassed = false;
+
+ // figure out what tabs are visible
+ for (int i = 20; i > 1; i--)
+ {
+ var results = RunningApp.Query($"Tab {i}");
+
+ if (results.Length > 0)
+ {
+ RunningApp.Tap($"Tab {i}");
+ RunningApp.WaitForElement($"Tab {i}");
+ testPassed = true;
+ break;
+ }
+ }
+
+ RunningApp.WaitForNoElement("Tab 1");
+ Assert.IsTrue(testPassed);
+ }
+#endif
+
+ }
+}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10497.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10497.cs
new file mode 100644
index 00000000000..6350bec4ca0
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10497.cs
@@ -0,0 +1,105 @@
+using System.Collections.Generic;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.CollectionView)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 10497, "[Bug] Controls inside CollectionView might flash scrollbar while they're not scrollable", PlatformAffected.Android)]
+ public class Issue10497 : TestContentPage
+ {
+ public Issue10497()
+ {
+ Title = "Issue 10497";
+
+ var layout = new StackLayout();
+
+ var instructions = new Label
+ {
+ BackgroundColor = Color.Black,
+ TextColor = Color.White,
+ Text = "If loading the page you don't see the scrollbar in each CollectionView Item, the test has passed."
+ };
+
+ var scrollBarVisibilityPicker = new Picker
+ {
+ Title = "VerticalScrollBarVisibility",
+ ItemsSource = new List
+ {
+ "Default",
+ "Always",
+ "Never"
+ },
+ SelectedIndex = 0
+ };
+
+ var collectionView = new CollectionView
+ {
+ Margin = new Thickness(0, 0, 50, 0)
+ };
+
+ collectionView.ItemsSource = new List
+ {
+ "Item 1",
+ "Item 2",
+ "Item 3",
+ "Item 4",
+ "Item 5",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
+ "Item 7",
+ "Item 8",
+ "Item 9",
+ "Item 10"
+ };
+
+ collectionView.ItemTemplate = new DataTemplate(() =>
+ {
+ var label = new Label
+ {
+ HeightRequest = 60,
+ FontSize = 75
+ };
+
+ label.SetBinding(Label.TextProperty, ".");
+
+ return label;
+ });
+
+ layout.Children.Add(instructions);
+ layout.Children.Add(scrollBarVisibilityPicker);
+ layout.Children.Add(collectionView);
+
+ Content = layout;
+
+ scrollBarVisibilityPicker.SelectedIndexChanged += (sender, args) =>
+ {
+ switch (scrollBarVisibilityPicker.SelectedIndex)
+ {
+ case 0:
+ collectionView.VerticalScrollBarVisibility = ScrollBarVisibility.Default;
+ break;
+ case 1:
+ collectionView.VerticalScrollBarVisibility = ScrollBarVisibility.Always;
+ break;
+ case 2:
+ collectionView.VerticalScrollBarVisibility = ScrollBarVisibility.Never;
+ break;
+ }
+ };
+ }
+
+ protected override void Init()
+ {
+
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10909.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10909.cs
new file mode 100644
index 00000000000..120f6f09c98
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10909.cs
@@ -0,0 +1,67 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+using Xamarin.Forms.Core.UITests;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.DatePicker)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 10909, "[Bug] UWP DatePicker and TimePicker Focus() function does not open the popup to set the date/time",
+ PlatformAffected.UWP)]
+ public class Issue10909 : TestContentPage
+ {
+ protected override void Init()
+ {
+ Title = "Issue 10909";
+
+ var layout = new StackLayout();
+
+ var instructions = new Label
+ {
+ Padding = 12,
+ Text = "If pressing the buttons opens the popup of both DatePicker and TimePicker, the test has passed.",
+ BackgroundColor = Color.Black,
+ TextColor = Color.White
+ };
+
+ var datePickerFocusButton = new Button
+ {
+ Text = "Set focus on DatePicker"
+ };
+
+ var datepicker = new DatePicker();
+
+ var timePickerFocusButton = new Button
+ {
+ Text = "Set focus on TimePicker"
+ };
+
+ var timePicker = new TimePicker();
+
+ layout.Children.Add(instructions);
+ layout.Children.Add(datePickerFocusButton);
+ layout.Children.Add(datepicker);
+ layout.Children.Add(timePickerFocusButton);
+ layout.Children.Add(timePicker);
+
+ Content = layout;
+
+ datePickerFocusButton.Clicked += (sender, args) =>
+ {
+ datepicker.Focus();
+ };
+
+ timePickerFocusButton.Clicked += (sender, args) =>
+ {
+ timePicker.Focus();
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10940.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10940.cs
new file mode 100644
index 00000000000..d8c1410e4e9
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue10940.cs
@@ -0,0 +1,89 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using System.Collections.Generic;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+using Xamarin.Forms.Core.UITests;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.SwipeView)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 10940,
+ "[Android]CustomRenderer object was created twice for single control when add custom control in SwipeView item",
+ PlatformAffected.Android)]
+ public class Issue10940 : TestContentPage
+ {
+ public Issue10940()
+ {
+#if APP
+ Device.SetFlags(new List { ExperimentalFlags.SwipeViewExperimental });
+#endif
+ }
+
+ protected override void Init()
+ {
+ Title = "Issue 10940";
+
+ var layout = new StackLayout();
+
+ var instructions = new Label
+ {
+ Padding = 12,
+ BackgroundColor = Color.Black,
+ TextColor = Color.White,
+ Text = "Swipe to the left and verify that opening the SwipeView the CustomEntry is created only one time."
+ };
+
+ var customSwipeView = new CustomSwipeView
+ {
+ BackgroundColor = Color.LightGray,
+ HeightRequest = 200
+ };
+
+ var swipeItemView = new SwipeItemView();
+
+ var swipeItemViewContent = new Grid
+ {
+ BackgroundColor = Color.White,
+ WidthRequest = 150
+ };
+
+ var customEntry = new CustomEntry
+ {
+ VerticalOptions = LayoutOptions.Center
+ };
+
+ swipeItemViewContent.Children.Add(customEntry);
+
+ swipeItemView.Content = swipeItemViewContent;
+
+ customSwipeView.RightItems = new SwipeItems
+ {
+ swipeItemView
+ };
+
+ layout.Children.Add(instructions);
+ layout.Children.Add(customSwipeView);
+
+ Content = layout;
+ }
+ }
+
+ [Preserve(AllMembers = true)]
+ public class CustomEntry : Entry
+ {
+
+ }
+
+ [Preserve(AllMembers = true)]
+ public class CustomSwipeView : SwipeView
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5470.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5470.cs
index 66f15ff17a7..32eb3affd20 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5470.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5470.cs
@@ -2,6 +2,7 @@
using Xamarin.Forms.Internals;
using System;
using System.Threading.Tasks;
+using System.Threading;
#if UITEST
using Xamarin.Forms.Core.UITests;
@@ -56,9 +57,9 @@ AppLinkEntry GetEntry()
#if UITEST && __IOS__
[Test]
- public async void Issue5470Test()
+ public void Issue5470Test()
{
- await Task.Delay(500); // give it time to crash
+ Thread.Sleep(500); // give it time to crash
RunningApp.WaitForElement (q => q.Marked ("IssuePageLabel"));
}
#endif
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7856.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7856.cs
new file mode 100644
index 00000000000..2e1d1e1ba6a
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7856.cs
@@ -0,0 +1,63 @@
+using System;
+using Xamarin.Forms.CustomAttributes;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+using Xamarin.Forms.Core.UITests;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Issue(IssueTracker.Github, 7856,
+ "[Bug] Shell BackButtonBehaviour TextOverride breaks back",
+ PlatformAffected.iOS)]
+#if UITEST
+ [NUnit.Framework.Category(UITestCategories.Shell)]
+#endif
+ public class Issue7856 : TestShell
+ {
+
+ const string ContentPageTitle = "Item1";
+ const string ButtonId = "ButtonId";
+
+ protected override void Init()
+ {
+ CreateContentPage(ContentPageTitle).Content =
+ new StackLayout
+ {
+ Children =
+ {
+ new Button
+ {
+ AutomationId = ButtonId,
+ Text = "Tap to Navigate To the Page With BackButtonBehavior",
+ Command = new Command(NavToBackButtonBehaviorPage)
+ }
+ }
+ };
+ }
+
+ private void NavToBackButtonBehaviorPage()
+ {
+ _ = Shell.Current.Navigation.PushAsync(new Issue7856_1());
+ }
+
+#if UITEST && __IOS__
+ [Test]
+ public void BackButtonBehaviorTest()
+ {
+ RunningApp.Tap(x => x.Text("Tap to Navigate To the Page With BackButtonBehavior"));
+
+ RunningApp.WaitForElement(x => x.Text("Navigate again"));
+
+ RunningApp.Tap(x => x.Text("Navigate again"));
+
+ RunningApp.WaitForElement(x => x.Text("Test"));
+
+ RunningApp.Tap(x => x.Text("Test"));
+
+ }
+#endif
+ }
+}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7856_1.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7856_1.xaml
new file mode 100644
index 00000000000..0ecc00686d0
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7856_1.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7856_1.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7856_1.xaml.cs
new file mode 100644
index 00000000000..1b107500ef2
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7856_1.xaml.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+
+using Xamarin.Forms;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ public partial class Issue7856_1 : ContentPage
+ {
+ public Issue7856_1()
+ {
+#if APP
+ InitializeComponent();
+#endif
+
+ Shell.SetBackButtonBehavior(this, new BackButtonBehavior
+ {
+ TextOverride = "Test"
+ });
+ }
+
+ private void Navigate_Clicked(object sender, EventArgs e)
+ {
+ _ = Shell.Current.Navigation.PushAsync(new Issue7856_1());
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8263.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8263.xaml
new file mode 100644
index 00000000000..aab71652109
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8263.xaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8263.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8263.xaml.cs
new file mode 100644
index 00000000000..958785d5f3e
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8263.xaml.cs
@@ -0,0 +1,41 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 8263, "[Enhancement] Add On/Off VisualStates for Switch")]
+ public partial class Issue8263 : TestContentPage
+ {
+ public Issue8263()
+ {
+#if APP
+ InitializeComponent();
+#endif
+ }
+ protected override void Init()
+ {
+
+ }
+
+#if UITEST
+ [Test]
+ [Category(UITestCategories.ManualReview)]
+ public void SwitchOnOffVisualStatesTest()
+ {
+ RunningApp.WaitForElement("Switch");
+ RunningApp.Screenshot("Switch Default");
+ RunningApp.Tap("Switch");
+ RunningApp.Screenshot("Switch Off with Red ThumbColor");
+ RunningApp.Tap("Switch");
+ RunningApp.Screenshot("Switch On with Green ThumbColor");
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8693.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8693.cs
index d831f8e8f75..86fcad8b171 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8693.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8693.cs
@@ -27,9 +27,6 @@ public Issue8693()
protected override void Init()
{
-#if APP
- Device.SetFlags(new List(Device.Flags ?? new List()) { "IndicatorView_Experimental" });
-#endif
var layout = new StackLayout();
var instructions = new Label
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8821.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8821.cs
index ed44a95f4b3..252fbc0bc3d 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8821.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8821.cs
@@ -5,6 +5,7 @@
using System.Threading.Tasks;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
+using IOPath = System.IO.Path;
#if UITEST
using Xamarin.Forms.Core.UITests;
@@ -79,7 +80,7 @@ async Task CreateImage(string imageUrl)
#else
path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
#endif
- SecondImageSource = Path.Combine(path, "Issue8821.gif");
+ SecondImageSource = IOPath.Combine(path, "Issue8821.gif");
File.WriteAllBytes(SecondImageSource, bytes);
_image.Source = SecondImageSource;
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8836.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8836.cs
new file mode 100644
index 00000000000..be16e4e2650
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8836.cs
@@ -0,0 +1,48 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 8836, "[Bug] ClearButtonVisibility.Never does not take effect on UWP", PlatformAffected.UWP)]
+ public class Issue8836 : TestContentPage
+ {
+ protected override void Init()
+ {
+ var stack = new StackLayout();
+
+ stack.Children.Add(new Label {Text = "Click the button to toggle the clear button visibility."});
+
+ Entry = new Entry
+ {
+ Text = "Clear Button: While Editing",
+ ClearButtonVisibility = ClearButtonVisibility.WhileEditing
+ };
+ stack.Children.Add(Entry);
+
+ var button = new Button { Text = "Toggle Clear Button State" };
+ button.Clicked += Button_Clicked;
+ stack.Children.Add(button);
+
+ Content = stack;
+ }
+
+ private void Button_Clicked(object sender, System.EventArgs e)
+ {
+ if (Entry.ClearButtonVisibility == ClearButtonVisibility.Never)
+ {
+ Entry.ClearButtonVisibility = ClearButtonVisibility.WhileEditing;
+ Entry.Text = "Clear Button: While Editing";
+ }
+ else
+ {
+ Entry.ClearButtonVisibility = ClearButtonVisibility.Never;
+ Entry.Text = "Clear Button: Never";
+ }
+ Entry.Focus();
+ }
+
+ public Entry Entry { get; set; }
+
+ }
+}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8870.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8870.cs
new file mode 100644
index 00000000000..15c19eee41b
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8870.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using System.Threading.Tasks;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.CollectionView)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 8870, "[Bug] CollectionView with HTML Labels Freeze the Screen on Rotation",
+ PlatformAffected.iOS)]
+ public class Issue8870 : TestContentPage
+ {
+ public const string Success = "Success";
+ public const string CheckResult = "Check";
+
+ protected override void Init()
+ {
+#if APP
+ var instructions = new Label { Text = "Rotate the device, then rotate it back 3 times. If the application crashes or hangs, this test has failed." };
+
+ var button = new Button { Text = CheckResult, AutomationId = CheckResult };
+ button.Clicked += (sender, args) => { instructions.Text = Success; };
+
+ var source = new List();
+ for (int n = 0; n < 100; n++)
+ {
+ source.Add($"Item: {n}");
+ }
+
+ var template = new DataTemplate(() => {
+ var label = new Label
+ {
+ TextType = TextType.Html
+ };
+
+ label.SetBinding(Label.TextProperty, new Binding(".", stringFormat: "{0}
"));
+
+ return label;
+ });
+
+ var cv = new CollectionView()
+ {
+ ItemsSource = source,
+ ItemTemplate = template
+ };
+
+ var layout = new StackLayout();
+
+ layout.Children.Add(instructions);
+ layout.Children.Add(button);
+ layout.Children.Add(cv);
+
+ Content = layout;
+#endif
+ }
+
+#if UITEST
+ [Test]
+ public async Task RotatingCollectionViewWithHTMLShouldNotHangOrCrash()
+ {
+ int delay = 3000;
+
+ RunningApp.WaitForElement(CheckResult);
+
+ RunningApp.SetOrientationPortrait();
+ await Task.Delay(delay);
+
+ RunningApp.SetOrientationLandscape();
+ await Task.Delay(delay);
+
+ RunningApp.SetOrientationPortrait();
+ await Task.Delay(delay);
+
+ RunningApp.SetOrientationLandscape();
+ await Task.Delay(delay);
+
+ RunningApp.SetOrientationPortrait();
+ await Task.Delay(delay);
+
+ RunningApp.WaitForElement(CheckResult);
+ RunningApp.Tap(CheckResult);
+
+ RunningApp.WaitForElement(Success);
+ }
+#endif
+ }
+}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8958.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8958.xaml.cs
index d308f517b76..346eb4ea55b 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8958.xaml.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8958.xaml.cs
@@ -21,7 +21,6 @@ public partial class Issue8958 : TestContentPage
public Issue8958()
{
#if APP
- Device.SetFlags(new List(Device.Flags ?? new List()) { "IndicatorView_Experimental" });
InitializeComponent();
#endif
}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9279.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9279.xaml
new file mode 100644
index 00000000000..c6071e3a821
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9279.xaml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Baboon
+ Capuchin Monkey
+ Blue Monkey
+ Squirrel Monkey
+ Golden Lion Tamarin
+ Howler Monkey
+ Japanese Macaque
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9279.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9279.xaml.cs
new file mode 100644
index 00000000000..80fe7069812
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9279.xaml.cs
@@ -0,0 +1,28 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+using Xamarin.UITest.iOS;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 9279, "[Bug] [UWP] CollectionView selected state not working on UWP", PlatformAffected.UWP)]
+ public partial class Issue9279 : TestContentPage
+ {
+ public Issue9279()
+ {
+#if APP
+ InitializeComponent();
+#endif
+ }
+
+ protected override void Init()
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9827.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9827.xaml.cs
index 6a1cc988643..98970128e7a 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9827.xaml.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9827.xaml.cs
@@ -30,7 +30,7 @@ public partial class Issue9827 : TestContentPage
public Issue9827()
{
#if APP
- Device.SetFlags(new List(Device.Flags ?? new List()) { ExperimentalFlags.IndicatorViewExperimental, ExperimentalFlags.CarouselViewExperimental });
+ Device.SetFlags(new List(Device.Flags ?? new List()) { ExperimentalFlags.CarouselViewExperimental });
InitializeComponent();
#endif
}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9962.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9962.cs
new file mode 100644
index 00000000000..283f68ed4f2
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9962.cs
@@ -0,0 +1,84 @@
+using System;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 9962, "NSException thrown when calling NSColor.ControlBackground.ToColor()", PlatformAffected.macOS)]
+ public class Issue9962 : TestContentPage
+ {
+ public Issue9962()
+ {
+ }
+
+ protected override void Init()
+ {
+ var stackLayout = new StackLayout
+ {
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.Center
+ };
+
+ var debugLabel = new Label
+ {
+ Text = "The first button should show an alert with the exception message, second button should retrieve an actual color and put the value in this label and in the BoxView"
+ };
+
+ var boxView = new BoxView
+ {
+ BackgroundColor = Color.Blue,
+ WidthRequest = 100,
+ HeightRequest = 100
+ };
+
+ var buttonBoom = new Button
+ {
+ Text = "This button should throw an Exception"
+ };
+
+ buttonBoom.Clicked += (_, __) =>
+ {
+ try
+ {
+ var color = DependencyService.Get()?.GetConvertedColor(true);
+
+ boxView.BackgroundColor = color ?? Color.Black;
+
+ }
+ catch (InvalidOperationException ex)
+ {
+ DisplayAlert("Exception!", ex.Message, "Gotcha");
+ }
+ };
+
+ var buttonNotBoom = new Button
+ {
+ Text = "This button should NOT throw an Exception"
+ };
+
+ buttonNotBoom.Clicked += (_, __) =>
+ {
+ try
+ {
+ var color = DependencyService.Get()?.GetConvertedColor(false);
+
+ debugLabel.Text = color?.ToString();
+ boxView.BackgroundColor = color ?? Color.Black;
+ }
+ catch (Exception ex)
+ {
+ DisplayAlert("Exception!", ex.Message, "Gotcha");
+ }
+ };
+
+
+ stackLayout.Children.Add(buttonBoom);
+ stackLayout.Children.Add(buttonNotBoom);
+ stackLayout.Children.Add(debugLabel);
+ stackLayout.Children.Add(boxView);
+
+ Content = stackLayout;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/PerformanceGallery/Scenarios/ImageScenarios.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/PerformanceGallery/Scenarios/ImageScenarios.cs
index 599c5a4ceca..0803ce1979b 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/PerformanceGallery/Scenarios/ImageScenarios.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/PerformanceGallery/Scenarios/ImageScenarios.cs
@@ -1,8 +1,7 @@
-using System;
-using System.IO;
-using System.Linq;
+using System.IO;
using System.Reflection;
using Xamarin.Forms.Internals;
+using IOPath = System.IO.Path;
namespace Xamarin.Forms.Controls.GalleryPages.PerformanceGallery.Scenarios
{
@@ -53,7 +52,7 @@ internal class ImageScenario4 : PerformanceScenario
static ImageScenario4()
{
//NOTE: copy image to disk in static ctor, so not to interfere with timing
- tempFile = Path.Combine(Path.GetTempPath(), $"{nameof(ImageScenario4)}.png");
+ tempFile = IOPath.Combine(IOPath.GetTempPath(), $"{nameof(ImageScenario4)}.png");
using (var embeddedStream = typeof(ImageScenario4).GetTypeInfo().Assembly.GetManifestResourceStream("Xamarin.Forms.Controls.GalleryPages.crimson.jpg"))
using (var fileStream = File.Create(tempFile))
embeddedStream.CopyTo(fileStream);
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/ShellBackButtonBehavior.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/ShellBackButtonBehavior.cs
index 52174e0bcfd..55a1a470284 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/ShellBackButtonBehavior.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/ShellBackButtonBehavior.cs
@@ -120,11 +120,29 @@ public BackButtonPage()
AutomationId = PushPageId
});
-
- Content = layout;
+ layout.Children.Add(new Button()
+ {
+ Text = "Toggle Flyout Behavior",
+ Command = new Command(ToggleFlyoutBehavior)
+ });
+
+
+ Content = new ScrollView() { Content = layout };
ToggleBehavior();
}
+ void ToggleFlyoutBehavior(object obj)
+ {
+ var behavior = (int)(Shell.Current.FlyoutBehavior);
+ behavior++;
+
+ if (Enum.GetValues(typeof(FlyoutBehavior)).Length <= behavior)
+ behavior = 0;
+
+ Shell.Current.FlyoutBehavior = (FlyoutBehavior)behavior;
+
+ }
+
async void PushPage(object obj)
{
await Navigation.PushAsync(new BackButtonPage());
@@ -181,7 +199,7 @@ public void ToggleIsEnabled()
}
-#if UITEST && (__IOS__ || __ANDROID__)
+#if UITEST && (__SHELL__)
[Test]
public void CommandTest()
{
@@ -198,6 +216,18 @@ public void CommandTest()
commandResult = RunningApp.WaitForElement(CommandResultId)[0].ReadText();
Assert.AreEqual(commandResult, "parameter");
}
+
+ [Test]
+ public void CommandWorksWhenItsTheOnlyThingSet()
+ {
+ RunningApp.Tap(PushPageId);
+ RunningApp.Tap(ToggleCommandId);
+ RunningApp.EnterText(EntryCommandParameter, "parameter");
+ TapBackArrow();
+
+ var commandResult = RunningApp.WaitForElement(CommandResultId)[0].ReadText();
+ Assert.AreEqual(commandResult, "parameter");
+ }
#endif
}
}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs
index f8238d5e24c..e41cfe4381c 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs
@@ -1,7 +1,7 @@
using System;
using System.Reflection;
using Xamarin.Forms.CustomAttributes;
-using System.IO;
+using IOPath = System.IO.Path;
#if UITEST
using Xamarin.Forms.Core.UITests;
@@ -61,7 +61,7 @@ static IApp InitializeApp()
#if __ANDROID__
static IApp InitializeAndroidApp()
{
- var fullApkPath = Path.Combine(TestContext.CurrentContext.TestDirectory, AppPaths.ApkPath);
+ var fullApkPath = IOPath.Combine(TestContext.CurrentContext.TestDirectory, AppPaths.ApkPath);
var app = ConfigureApp.Android.ApkFile(fullApkPath).Debug().StartApp(UITest.Configuration.AppDataMode.DoNotClear);
if (bool.Parse((string)app.Invoke("IsPreAppCompat")))
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/VisualControlsPage.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/VisualControlsPage.xaml
index c548f64ae2b..0590dbe4b4a 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/VisualControlsPage.xaml
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/VisualControlsPage.xaml
@@ -1,10 +1,11 @@
-
+
-
+
#673AB7
#448AFF
@@ -12,285 +13,301 @@
#D32F2F
#FFCDD2
-
+
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
-
-
+
-
-
+
-
-
+
-
-
-
-
-
-
-
-
-
- Baboon
- Capuchin Monkey
- Blue Monkey
- Squirrel Monkey
- Golden Lion Tamarin
- Howler Monkey
- Japanese Macaque
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ Baboon
+ Capuchin Monkey
+ Blue Monkey
+ Squirrel Monkey
+ Golden Lion Tamarin
+ Howler Monkey
+ Japanese Macaque
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/VisualControlsPage.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/VisualControlsPage.xaml.cs
index b89a88838f4..1548bdbc3fe 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/VisualControlsPage.xaml.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/VisualControlsPage.xaml.cs
@@ -10,7 +10,7 @@
namespace Xamarin.Forms.Controls.Issues
{
- public partial class VisualControlsPage : ContentPage
+ public partial class VisualControlsPage : TestShell
{
bool isVisible = false;
double percentage = 0.0;
@@ -20,7 +20,9 @@ public VisualControlsPage()
#if APP
InitializeComponent();
#endif
-
+ }
+ protected override void Init()
+ {
BindingContext = this;
}
@@ -61,54 +63,38 @@ protected override void OnDisappearing()
base.OnDisappearing();
}
- }
- [Preserve(AllMembers = true)]
- [Issue(IssueTracker.Github, 4435, "Visual Gallery Loads",
- PlatformAffected.iOS | PlatformAffected.Android)]
+
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 4435, "Visual Gallery Loads",
+ PlatformAffected.iOS | PlatformAffected.Android)]
#if UITEST
- [NUnit.Framework.Category(UITestCategories.Visual)]
+ [NUnit.Framework.Category(UITestCategories.Visual)]
#endif
- public class Issue4435 : TestNavigationPage
- {
- const string Success = "Success";
- Label successLabel;
- protected override void Init()
+ public class Issue4435 : VisualControlsPage
{
- var vg = new VisualControlsPage();
- successLabel = new Label();
- vg.Appearing += Vg_Appearing;
- PushAsync(new ContentPage()
+ protected override void Init()
{
- Content = new StackLayout()
- {
- Children =
- {
- successLabel
- }
- }
- });
-
- PushAsync(vg);
- }
+ }
- private void Vg_Appearing(object sender, EventArgs e)
- {
- Device.BeginInvokeOnMainThread(() =>
+#if UITEST && !__WINDOWS__
+ [Test]
+ public void LoadingVisualGalleryPageDoesNotCrash()
{
- PopAsync();
- successLabel.Text = Success;
- });
- }
+ RunningApp.WaitForElement("Activity Indicators");
+ }
-#if UITEST && !__WINDOWS__
- [Test]
- public void LoadingVisualGalleryPageDoesNotCrash()
- {
- RunningApp.WaitForElement(Success);
- }
+ [Test]
+ [NUnit.Framework.Category(UITestCategories.ManualReview)]
+ public void DisabledButtonTest()
+ {
+ TapInFlyout("Disabled Button Test");
+ RunningApp.WaitForElement("If either button looks odd this test has failed.");
+ RunningApp.Screenshot("If either button looks off (wrong shadow, border drawn inside button) the test has failed.");
+ }
#endif
+ }
}
}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
index 6f432f12842..e67b2aafdc6 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
@@ -20,6 +20,7 @@
+
@@ -39,11 +40,16 @@
+
+ Issue9279.xaml
+
+
+
Issue8902.xaml
Code
@@ -197,6 +203,10 @@
+
+ Issue8263.xaml
+ Code
+
@@ -1353,6 +1363,10 @@
+
+
+ Issue7856_1.xaml
+
@@ -1361,12 +1375,16 @@
+
+
+
+
Issue9711.xaml
Code
@@ -1399,6 +1417,7 @@
Issue9555.xaml
+
@@ -1558,6 +1577,10 @@
+
+ Designer
+ MSBuild:UpdateDesignTimeXaml
+
MSBuild:UpdateDesignTimeXaml
@@ -1861,6 +1884,9 @@
Issue7865.xaml
+
+ Issue8263.xaml
+
Issue8508.xaml
@@ -1989,6 +2015,11 @@
MSBuild:UpdateDesignTimeXaml
+
+
+ MSBuild:UpdateDesignTimeXaml
+
+
Designer
@@ -2074,4 +2105,10 @@
MSBuild:UpdateDesignTimeXaml
-
\ No newline at end of file
+
+
+ Designer
+ MSBuild:UpdateDesignTimeXaml
+
+
+
diff --git a/Xamarin.Forms.Controls/CoreGallery.cs b/Xamarin.Forms.Controls/CoreGallery.cs
index d6cd97e7944..d9a384ddc0e 100644
--- a/Xamarin.Forms.Controls/CoreGallery.cs
+++ b/Xamarin.Forms.Controls/CoreGallery.cs
@@ -18,6 +18,7 @@
using Xamarin.Forms.Controls.GalleryPages.AppThemeGalleries;
using Xamarin.Forms.Controls.GalleryPages.ExpanderGalleries;
using Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries;
+using Xamarin.Forms.Controls.GalleryPages.ShapesGalleries;
namespace Xamarin.Forms.Controls
{
@@ -360,6 +361,7 @@ public override string ToString()
new GalleryPageFactory(() => new ScrollGallery(ScrollOrientation.Horizontal), "ScrollView Gallery Horizontal"),
new GalleryPageFactory(() => new ScrollGallery(ScrollOrientation.Both), "ScrollView Gallery 2D"),
new GalleryPageFactory(() => new SearchBarCoreGalleryPage(), "SearchBar Gallery"),
+ new GalleryPageFactory(() => new ShapesGallery(), "Shapes Gallery"),
new GalleryPageFactory(() => new SliderCoreGalleryPage(), "Slider Gallery"),
new GalleryPageFactory(() => new StepperCoreGalleryPage(), "Stepper Gallery"),
new GalleryPageFactory(() => new SwitchCoreGalleryPage(), "Switch Gallery"),
diff --git a/Xamarin.Forms.Controls/Directory.Build.props b/Xamarin.Forms.Controls/Directory.Build.props
index ef994776379..5fa729b6773 100644
--- a/Xamarin.Forms.Controls/Directory.Build.props
+++ b/Xamarin.Forms.Controls/Directory.Build.props
@@ -1,4 +1,4 @@
-
+
diff --git a/Xamarin.Forms.Controls/Directory.Build.targets b/Xamarin.Forms.Controls/Directory.Build.targets
index 804a384f08c..10989cede11 100644
--- a/Xamarin.Forms.Controls/Directory.Build.targets
+++ b/Xamarin.Forms.Controls/Directory.Build.targets
@@ -1,4 +1,5 @@
-
-
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/AppThemeGalleries/AppThemeCodeGallery.cs b/Xamarin.Forms.Controls/GalleryPages/AppThemeGalleries/AppThemeCodeGallery.cs
index 770efc2ad8b..8ab35cab747 100644
--- a/Xamarin.Forms.Controls/GalleryPages/AppThemeGalleries/AppThemeCodeGallery.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/AppThemeGalleries/AppThemeCodeGallery.cs
@@ -30,7 +30,7 @@ public AppThemeCodeGallery()
Text = "TextColor through SetAppThemeColor"
};
- onThemeLabel.SetBinding(Label.TextColorProperty, new OnAppTheme() { Light = Color.Green, Dark = Color.Red });
+ onThemeLabel.SetBinding(Label.TextColorProperty, new AppThemeBinding() { Light = Color.Green, Dark = Color.Red });
onThemeLabel1.SetOnAppTheme(Label.TextColorProperty, Color.Green, Color.Red);
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselViewGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselViewGallery.cs
index 8d529191a3f..19fd63705cd 100644
--- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselViewGallery.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CarouselViewGalleries/CarouselViewGallery.cs
@@ -66,7 +66,7 @@ void ButtonClicked(object sender, System.EventArgs e)
button.TextColor = Color.Black;
button.IsEnabled = false;
- Device.SetFlags(new[] { ExperimentalFlags.CarouselViewExperimental, ExperimentalFlags.IndicatorViewExperimental });
+ Device.SetFlags(new[] { ExperimentalFlags.CarouselViewExperimental });
}
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ExampleTemplates.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ExampleTemplates.cs
index 22ec2d04d96..3c8b27edd5a 100644
--- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ExampleTemplates.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ExampleTemplates.cs
@@ -14,7 +14,8 @@ public static DataTemplate PhotoTemplate()
{
RowDefinitions = new RowDefinitionCollection { new RowDefinition(), new RowDefinition() },
WidthRequest = 200,
- HeightRequest = 100
+ HeightRequest = 100,
+ BackgroundColor = Color.White
};
var image = new Image
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCodeCollectionViewGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCodeCollectionViewGallery.cs
index 101e9b68fc4..033102860b0 100644
--- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCodeCollectionViewGallery.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCodeCollectionViewGallery.cs
@@ -3,7 +3,7 @@
internal class ObservableCodeCollectionViewGallery : ContentPage
{
public ObservableCodeCollectionViewGallery(ItemsLayoutOrientation orientation = ItemsLayoutOrientation.Vertical,
- bool grid = true, int initialItems = 1000, bool addItemsWithTimer = false)
+ bool grid = true, int initialItems = 1000, bool addItemsWithTimer = false, ItemsUpdatingScrollMode scrollMode = ItemsUpdatingScrollMode.KeepItemsInView)
{
var layout = new Grid
{
@@ -26,7 +26,7 @@ public ObservableCodeCollectionViewGallery(ItemsLayoutOrientation orientation =
var itemTemplate = ExampleTemplates.PhotoTemplate();
var collectionView = new CollectionView {ItemsLayout = itemsLayout, ItemTemplate = itemTemplate,
- AutomationId = "collectionview", Header = "This is the header" };
+ AutomationId = "collectionview", Header = "This is the header", ItemsUpdatingScrollMode = scrollMode};
var generator = new ItemsSourceGenerator(collectionView, initialItems, ItemsSourceType.ObservableCollection);
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCollectionGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCollectionGallery.cs
index 7eb82077ca6..497e30d00c4 100644
--- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCollectionGallery.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ObservableCollectionGallery.cs
@@ -41,7 +41,16 @@ public ObservableCollectionGallery()
GalleryBuilder.NavButton("Reset", () => new ObservableCollectionResetGallery(), Navigation),
GalleryBuilder.NavButton("Add Items with timer to Empty Collection", () =>
- new ObservableCodeCollectionViewGallery(grid: false, initialItems: 0, addItemsWithTimer: true), Navigation)
+ new ObservableCodeCollectionViewGallery(grid: false, initialItems: 0, addItemsWithTimer: true), Navigation),
+
+ GalleryBuilder.NavButton("Scroll mode Keep items in view", () =>
+ new ObservableCodeCollectionViewGallery(grid: false, initialItems: 0, addItemsWithTimer: true, scrollMode: ItemsUpdatingScrollMode.KeepItemsInView), Navigation),
+
+ GalleryBuilder.NavButton("Scroll mode Keep scroll offset", () =>
+ new ObservableCodeCollectionViewGallery(grid: false, initialItems: 0, addItemsWithTimer: true, scrollMode: ItemsUpdatingScrollMode.KeepScrollOffset), Navigation),
+
+ GalleryBuilder.NavButton("Scroll mode Keep last item in view", () =>
+ new ObservableCodeCollectionViewGallery(grid: false, initialItems: 0, addItemsWithTimer: true, scrollMode: ItemsUpdatingScrollMode.KeepLastItemInView), Navigation)
}
}
};
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/TemplateCodeCollectionViewGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/TemplateCodeCollectionViewGallery.cs
index 44f0f444e58..6e839cfde36 100644
--- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/TemplateCodeCollectionViewGallery.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/TemplateCodeCollectionViewGallery.cs
@@ -19,7 +19,8 @@ public TemplateCodeCollectionViewGallery(IItemsLayout itemsLayout)
{
ItemsLayout = itemsLayout,
ItemTemplate = itemTemplate,
- AutomationId = "collectionview"
+ AutomationId = "collectionview",
+ BackgroundColor = Color.Red
};
var generator = new ItemsSourceGenerator(collectionView, initialItems: 20);
diff --git a/Xamarin.Forms.Controls/GalleryPages/IndicatorViewGalleries/IndicatorGalleries.cs b/Xamarin.Forms.Controls/GalleryPages/IndicatorViewGalleries/IndicatorGalleries.cs
index 3156ae82635..53e73dbf14f 100644
--- a/Xamarin.Forms.Controls/GalleryPages/IndicatorViewGalleries/IndicatorGalleries.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/IndicatorViewGalleries/IndicatorGalleries.cs
@@ -1,6 +1,4 @@
-using System;
-
-namespace Xamarin.Forms.Controls.GalleryPages
+namespace Xamarin.Forms.Controls.GalleryPages
{
public class IndicatorGalleries : ContentPage
{
@@ -11,13 +9,6 @@ public IndicatorGalleries()
Title = "IndicatorView Galleries";
- var button = new Button
- {
- Text = "Enable IndicatorView",
- AutomationId = "EnableIndicator"
- };
- button.Clicked += ButtonClicked;
-
Content = new ScrollView
{
Content = new StackLayout
@@ -25,7 +16,6 @@ public IndicatorGalleries()
Children =
{
descriptionLabel,
- button,
GalleryBuilder.NavButton("IndicatorView Gallery", () =>
new IndicatorsSample(), Navigation),
GalleryBuilder.NavButton("Indicator MaxVisible Gallery", () =>
@@ -34,16 +24,5 @@ public IndicatorGalleries()
}
};
}
-
- void ButtonClicked(object sender, EventArgs e)
- {
- var button = sender as Button;
-
- button.Text = "IndicatorView Enabled!";
- button.TextColor = Color.Black;
- button.IsEnabled = false;
-
- Device.SetFlags(new[] { ExperimentalFlags.IndicatorViewExperimental });
- }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/MapElementsGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/MapElementsGallery.xaml.cs
index bc1acfe1324..3700d1efbaa 100644
--- a/Xamarin.Forms.Controls/GalleryPages/MapElementsGallery.xaml.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/MapElementsGallery.xaml.cs
@@ -1,12 +1,8 @@
using System;
-using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-using Xamarin.Forms;
-using Xamarin.Forms.Maps;
using Xamarin.Forms.Xaml;
+using Xamarin.Forms.Maps;
+using Map = Xamarin.Forms.Maps;
namespace Xamarin.Forms.Controls.GalleryPages
{
@@ -22,9 +18,9 @@ enum SelectedElementType
SelectedElementType _selectedType;
- Polyline _polyline;
- Polygon _polygon;
- Circle _circle;
+ Map.Polyline _polyline;
+ Map.Polygon _polygon;
+ Map.Circle _circle;
Random _random = new Random();
@@ -37,7 +33,7 @@ public MapElementsGallery()
new Position(39.828152, -98.569817),
Distance.FromMiles(1681)));
- _polyline = new Polyline
+ _polyline = new Maps.Polyline
{
Geopath =
{
@@ -47,7 +43,7 @@ public MapElementsGallery()
}
};
- _polygon = new Polygon
+ _polygon = new Maps.Polygon
{
StrokeColor = Color.FromHex("#002868"),
FillColor = Color.FromHex("#88BF0A30"),
@@ -108,10 +104,10 @@ void AddClicked(object sender, EventArgs e)
switch (_selectedType)
{
case SelectedElementType.Polyline:
- Map.MapElements.Add(_polyline = new Polyline());
+ Map.MapElements.Add(_polyline = new Maps.Polyline());
break;
case SelectedElementType.Polygon:
- Map.MapElements.Add(_polygon = new Polygon());
+ Map.MapElements.Add(_polygon = new Maps.Polygon());
break;
case SelectedElementType.Circle:
Map.MapElements.Add(_circle = new Circle());
@@ -125,18 +121,18 @@ void RemoveClicked(object sender, EventArgs e)
{
case SelectedElementType.Polyline:
Map.MapElements.Remove(_polyline);
- _polyline = Map.MapElements.OfType().LastOrDefault();
+ _polyline = Map.MapElements.OfType().LastOrDefault();
if (_polyline == null)
- Map.MapElements.Add(_polyline = new Polyline());
+ Map.MapElements.Add(_polyline = new Maps.Polyline());
break;
case SelectedElementType.Polygon:
Map.MapElements.Remove(_polygon);
- _polygon = Map.MapElements.OfType().LastOrDefault();
+ _polygon = Map.MapElements.OfType().LastOrDefault();
if (_polygon == null)
- Map.MapElements.Add(_polygon = new Polygon());
+ Map.MapElements.Add(_polygon = new Maps.Polygon());
break;
case SelectedElementType.Circle:
diff --git a/Xamarin.Forms.Controls/GalleryPages/MapGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/MapGallery.xaml
index 214bf5e8d50..beb055551a4 100644
--- a/Xamarin.Forms.Controls/GalleryPages/MapGallery.xaml
+++ b/Xamarin.Forms.Controls/GalleryPages/MapGallery.xaml
@@ -54,6 +54,11 @@
Clicked="ToggleMoveToLastRegionOnLayoutChange"
VerticalOptions="Center" />
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/MapGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/MapGallery.xaml.cs
index 7b61d66e5d8..42ea6eec6f6 100644
--- a/Xamarin.Forms.Controls/GalleryPages/MapGallery.xaml.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/MapGallery.xaml.cs
@@ -182,5 +182,11 @@ void ToggleMoveToLastRegionOnLayoutChange(object sender, EventArgs e)
Map.MoveToLastRegionOnLayoutChange = !Map.MoveToLastRegionOnLayoutChange;
((Button)sender).Text = Map.MoveToLastRegionOnLayoutChange.ToString();
}
+
+ void ShowTrafficToggled(object sender, ToggledEventArgs e)
+ {
+ var control = (Switch)sender;
+ Map.TrafficEnabled = control.IsToggled;
+ }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/AddRemoveClipGallery.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/AddRemoveClipGallery.cs
new file mode 100644
index 00000000000..e7c820939b4
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/AddRemoveClipGallery.cs
@@ -0,0 +1,100 @@
+using System;
+using Xamarin.Forms.Shapes;
+
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public class AddRemoveClipGallery : ContentPage
+ {
+ readonly Image _image;
+ readonly Grid _grid;
+
+ public AddRemoveClipGallery()
+ {
+ Title = "Add/Remove Clip Gallery";
+
+ var layout = new StackLayout
+ {
+ Padding = 12
+ };
+
+ var imageInfo = new Label
+ {
+ HorizontalOptions = LayoutOptions.Center,
+ Text = "Image"
+ };
+
+ _image = new Image
+ {
+ Aspect = Aspect.AspectFill,
+ Source = new FileImageSource { File = "crimson.jpg" },
+ HorizontalOptions = LayoutOptions.Center,
+ HeightRequest = 150,
+ WidthRequest = 150
+ };
+
+ var gridInfo = new Label
+ {
+ HorizontalOptions = LayoutOptions.Center,
+ Text = "Grid"
+ };
+
+ _grid = new Grid
+ {
+ BackgroundColor = Color.Red,
+ HorizontalOptions = LayoutOptions.Center,
+ HeightRequest = 150,
+ WidthRequest = 150
+ };
+
+ var buttonLayout = new StackLayout
+ {
+ Orientation = StackOrientation.Horizontal,
+ HorizontalOptions = LayoutOptions.Center
+ };
+
+ var addButton = new Button
+ {
+ Text = "Add EllipseGeometry",
+ WidthRequest = 150
+ };
+
+ addButton.Clicked += OnAddButtonClicked;
+
+ var removeButton = new Button
+ {
+ Text = "Remove EllipseGeometry",
+ WidthRequest = 150
+ };
+
+ removeButton.Clicked += OnRemoveButtonClicked;
+
+ buttonLayout.Children.Add(addButton);
+ buttonLayout.Children.Add(removeButton);
+
+ layout.Children.Add(imageInfo);
+ layout.Children.Add(_image);
+ layout.Children.Add(gridInfo);
+ layout.Children.Add(_grid);
+ layout.Children.Add(buttonLayout);
+
+ Content = layout;
+ }
+
+ void OnAddButtonClicked(object sender, EventArgs e)
+ {
+ var ellipseGeometry = new EllipseGeometry
+ {
+ Center = new Point(75, 75),
+ RadiusX = 60,
+ RadiusY = 60
+ };
+
+ _image.Clip = _grid.Clip = ellipseGeometry;
+ }
+
+ void OnRemoveButtonClicked(object sender, EventArgs e)
+ {
+ _image.Clip = _grid.Clip = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/AutoSizeShapesGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/AutoSizeShapesGallery.xaml
new file mode 100644
index 00000000000..7156726613e
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/AutoSizeShapesGallery.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/AutoSizeShapesGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/AutoSizeShapesGallery.xaml.cs
new file mode 100644
index 00000000000..cd3bb6f14ad
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/AutoSizeShapesGallery.xaml.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public partial class AutoSizeShapesGallery : ContentPage
+ {
+ public AutoSizeShapesGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipGallery.xaml
new file mode 100644
index 00000000000..3e6a6557828
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipGallery.xaml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipGallery.xaml.cs
new file mode 100644
index 00000000000..2232d1d050a
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipGallery.xaml.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public partial class ClipGallery : ContentPage
+ {
+ public ClipGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipPerformanceGallery.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipPerformanceGallery.cs
new file mode 100644
index 00000000000..6705c19310e
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipPerformanceGallery.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Diagnostics;
+using Xamarin.Forms.Shapes;
+
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public class ClipPerformanceGallery : ContentPage
+ {
+ readonly Stopwatch _stopwatch;
+
+ public ClipPerformanceGallery()
+ {
+ Title = "Clip Performance Gallery";
+
+ _stopwatch = new Stopwatch();
+
+ var layout = new StackLayout();
+
+ var imageButton = new Button
+ {
+ Text = "Measure Image"
+ };
+
+ var clipImageButton = new Button
+ {
+ Text = "Measure Clip Image"
+ };
+
+ layout.Children.Add(imageButton);
+ layout.Children.Add(clipImageButton);
+
+ Content = layout;
+
+ imageButton.Clicked += (sender, args) =>
+ {
+ _stopwatch.Start();
+ Navigation.PushAsync(new ImagesPerformanceTestPage(_stopwatch));
+ };
+
+ clipImageButton.Clicked += (sender, args) =>
+ {
+ _stopwatch.Start();
+ Navigation.PushAsync(new ClipImagesPerformanceTestPage(_stopwatch));
+ };
+ }
+ }
+
+ public class ImagesPerformanceTestPage : ContentPage
+ {
+ readonly Stopwatch _stopwatch;
+
+ public ImagesPerformanceTestPage(Stopwatch stopwatch)
+ {
+ _stopwatch = stopwatch;
+
+ Title = "Images";
+
+ var scrollView = new ScrollView();
+
+ var imagesGrid = new Grid();
+
+ imagesGrid.ColumnDefinitions.Add(new ColumnDefinition());
+ imagesGrid.ColumnDefinitions.Add(new ColumnDefinition());
+ imagesGrid.ColumnDefinitions.Add(new ColumnDefinition());
+ imagesGrid.ColumnDefinitions.Add(new ColumnDefinition());
+
+ for (int i = 0; i < 25; i++)
+ {
+ imagesGrid.RowDefinitions.Add(new RowDefinition { Height = 50 });
+
+ for (int j = 0; j < 4; j++)
+ {
+ var image = new Image
+ {
+ Aspect = Aspect.AspectFill,
+ Source = "crimson.jpg",
+ };
+ Grid.SetRow(image, i);
+ Grid.SetColumn(image, j);
+ imagesGrid.Children.Add(image);
+ }
+ }
+
+ scrollView.Content = imagesGrid;
+
+ Content = scrollView;
+ }
+
+ protected override void OnAppearing()
+ {
+ base.OnAppearing();
+ _stopwatch.Stop();
+ TimeSpan elapsed = _stopwatch.Elapsed;
+ string message = $"{elapsed.TotalMilliseconds} ms";
+ Debug.WriteLine(message);
+ _stopwatch.Reset();
+ }
+ }
+
+ public class ClipImagesPerformanceTestPage : ContentPage
+ {
+ readonly Stopwatch _stopwatch;
+
+ public ClipImagesPerformanceTestPage(Stopwatch stopwatch)
+ {
+ _stopwatch = stopwatch;
+
+ Title = "Clip Images";
+
+ var scrollView = new ScrollView();
+
+ var imagesGrid = new Grid();
+
+ imagesGrid.ColumnDefinitions.Add(new ColumnDefinition());
+ imagesGrid.ColumnDefinitions.Add(new ColumnDefinition());
+ imagesGrid.ColumnDefinitions.Add(new ColumnDefinition());
+ imagesGrid.ColumnDefinitions.Add(new ColumnDefinition());
+
+ for (int i = 0; i < 25; i++)
+ {
+ imagesGrid.RowDefinitions.Add(new RowDefinition { Height = 50 });
+
+ for (int j = 0; j < 4; j++)
+ {
+ var image = new Image
+ {
+ Aspect = Aspect.AspectFill,
+ Source = "crimson.jpg",
+ Clip = new EllipseGeometry
+ {
+ Center = new Point(25, 25),
+ RadiusX = 25,
+ RadiusY = 25
+ }
+ };
+ Grid.SetRow(image, i);
+ Grid.SetColumn(image, j);
+ imagesGrid.Children.Add(image);
+ }
+ }
+
+ scrollView.Content = imagesGrid;
+
+ Content = scrollView;
+ }
+
+ protected override void OnAppearing()
+ {
+ base.OnAppearing();
+ _stopwatch.Stop();
+ TimeSpan elapsed = _stopwatch.Elapsed;
+ string message = $"{elapsed.TotalMilliseconds} ms";
+ Debug.WriteLine(message);
+ _stopwatch.Reset();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipViewsGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipViewsGallery.xaml
new file mode 100644
index 00000000000..1e631a27615
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipViewsGallery.xaml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipViewsGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipViewsGallery.xaml.cs
new file mode 100644
index 00000000000..fea27cd8fbc
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ClipViewsGallery.xaml.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public partial class ClipViewsGallery : ContentPage
+ {
+ public ClipViewsGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/EllipseGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/EllipseGallery.xaml
new file mode 100644
index 00000000000..aa0b1cab17d
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/EllipseGallery.xaml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/EllipseGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/EllipseGallery.xaml.cs
new file mode 100644
index 00000000000..007bd6c1bf9
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/EllipseGallery.xaml.cs
@@ -0,0 +1,13 @@
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ [Preserve(AllMembers = true)]
+ public partial class EllipseGallery : ContentPage
+ {
+ public EllipseGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineCapGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineCapGallery.xaml
new file mode 100644
index 00000000000..97c40019c53
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineCapGallery.xaml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineCapGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineCapGallery.xaml.cs
new file mode 100644
index 00000000000..8ce6df00349
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineCapGallery.xaml.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public partial class LineCapGallery : ContentPage
+ {
+ public LineCapGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineGallery.xaml
new file mode 100644
index 00000000000..2f5a00c7141
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineGallery.xaml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineGallery.xaml.cs
new file mode 100644
index 00000000000..b37796366f7
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineGallery.xaml.cs
@@ -0,0 +1,13 @@
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ [Preserve(AllMembers = true)]
+ public partial class LineGallery : ContentPage
+ {
+ public LineGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineJoinGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineJoinGallery.xaml
new file mode 100644
index 00000000000..90047bd93bb
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineJoinGallery.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineJoinGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineJoinGallery.xaml.cs
new file mode 100644
index 00000000000..8aedd63c7d1
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/LineJoinGallery.xaml.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public partial class LineJoinGallery : ContentPage
+ {
+ public LineJoinGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathAspectGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathAspectGallery.xaml
new file mode 100644
index 00000000000..647877526df
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathAspectGallery.xaml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathAspectGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathAspectGallery.xaml.cs
new file mode 100644
index 00000000000..cbe9cda1f79
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathAspectGallery.xaml.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public partial class PathAspectGallery : ContentPage
+ {
+ public PathAspectGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathGallery.xaml
new file mode 100644
index 00000000000..5a2c5b73fcf
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathGallery.xaml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathGallery.xaml.cs
new file mode 100644
index 00000000000..44d92dc34fd
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathGallery.xaml.cs
@@ -0,0 +1,13 @@
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ [Preserve(AllMembers = true)]
+ public partial class PathGallery : ContentPage
+ {
+ public PathGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathLayoutOptionsGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathLayoutOptionsGallery.xaml
new file mode 100644
index 00000000000..f35f24872ee
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathLayoutOptionsGallery.xaml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathLayoutOptionsGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathLayoutOptionsGallery.xaml.cs
new file mode 100644
index 00000000000..cd2979f9f08
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathLayoutOptionsGallery.xaml.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public partial class PathLayoutOptionsGallery : ContentPage
+ {
+ public PathLayoutOptionsGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathTransformStringGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathTransformStringGallery.xaml
new file mode 100644
index 00000000000..d6b692b3696
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathTransformStringGallery.xaml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathTransformStringGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathTransformStringGallery.xaml.cs
new file mode 100644
index 00000000000..80b35f27420
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PathTransformStringGallery.xaml.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public partial class PathTransformStringGallery : ContentPage
+ {
+ public PathTransformStringGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolygonGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolygonGallery.xaml
new file mode 100644
index 00000000000..52410a709f0
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolygonGallery.xaml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolygonGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolygonGallery.xaml.cs
new file mode 100644
index 00000000000..909f29e65dd
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolygonGallery.xaml.cs
@@ -0,0 +1,13 @@
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ [Preserve(AllMembers = true)]
+ public partial class PolygonGallery : ContentPage
+ {
+ public PolygonGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolylineGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolylineGallery.xaml
new file mode 100644
index 00000000000..923d8c9e939
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolylineGallery.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolylineGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolylineGallery.xaml.cs
new file mode 100644
index 00000000000..be9186e615e
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/PolylineGallery.xaml.cs
@@ -0,0 +1,13 @@
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ [Preserve(AllMembers = true)]
+ public partial class PolylineGallery : ContentPage
+ {
+ public PolylineGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/RectangleGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/RectangleGallery.xaml
new file mode 100644
index 00000000000..83ac8130701
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/RectangleGallery.xaml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/RectangleGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/RectangleGallery.xaml.cs
new file mode 100644
index 00000000000..14318a7d0dd
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/RectangleGallery.xaml.cs
@@ -0,0 +1,13 @@
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ [Preserve(AllMembers = true)]
+ public partial class RectangleGallery : ContentPage
+ {
+ public RectangleGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ShapesGallery.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ShapesGallery.cs
new file mode 100644
index 00000000000..2ec85cbd35c
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/ShapesGallery.cs
@@ -0,0 +1,59 @@
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ [Preserve(AllMembers = true)]
+ public class ShapesGallery : ContentPage
+ {
+ public ShapesGallery()
+ {
+ Title = "Shapes Gallery";
+
+ var button = new Button
+ {
+ Text = "Enable Shapes",
+ AutomationId = "EnableShapes"
+ };
+ button.Clicked += ButtonClicked;
+
+ Content = new ScrollView
+ {
+ Content = new StackLayout
+ {
+ Children =
+ {
+ button,
+ GalleryBuilder.NavButton("Ellipse Gallery", () => new EllipseGallery(), Navigation),
+ GalleryBuilder.NavButton("Line Gallery", () => new LineGallery(), Navigation),
+ GalleryBuilder.NavButton("Polygon Gallery", () => new PolygonGallery(), Navigation),
+ GalleryBuilder.NavButton("Polyline Gallery", () => new PolylineGallery(), Navigation),
+ GalleryBuilder.NavButton("Rectangle Gallery", () => new RectangleGallery(), Navigation),
+ GalleryBuilder.NavButton("LineCap Gallery", () => new LineCapGallery(), Navigation),
+ GalleryBuilder.NavButton("LineJoin Gallery", () => new LineJoinGallery(), Navigation),
+ GalleryBuilder.NavButton("AutoSize Shapes Gallery", () => new AutoSizeShapesGallery(), Navigation),
+ GalleryBuilder.NavButton("Path Gallery", () => new PathGallery(), Navigation),
+ GalleryBuilder.NavButton("Path Aspect Gallery", () => new PathAspectGallery(), Navigation),
+ GalleryBuilder.NavButton("Path LayoutOptions Gallery", () => new PathLayoutOptionsGallery(), Navigation),
+ GalleryBuilder.NavButton("Transform Playground", () => new TransformPlaygroundGallery(), Navigation),
+ GalleryBuilder.NavButton("Path Transform using string (TypeConverter) Gallery", () => new PathTransformStringGallery(), Navigation),
+ GalleryBuilder.NavButton("Clip Gallery", () => new ClipGallery(), Navigation),
+ GalleryBuilder.NavButton("Clip Views Gallery", () => new ClipViewsGallery(), Navigation),
+ GalleryBuilder.NavButton("Add/Remove Clip Gallery", () => new AddRemoveClipGallery(), Navigation),
+ GalleryBuilder.NavButton("Clip Performance Gallery", () => new ClipPerformanceGallery(), Navigation)
+ }
+ }
+ };
+ }
+
+ void ButtonClicked(object sender, System.EventArgs e)
+ {
+ var button = sender as Button;
+
+ button.Text = "Shapes Enabled!";
+ button.TextColor = Color.Black;
+ button.IsEnabled = false;
+
+ Device.SetFlags(new[] { ExperimentalFlags.ShapesExperimental });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/TransformPlaygroundGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/TransformPlaygroundGallery.xaml
new file mode 100644
index 00000000000..6ced1f11aee
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/TransformPlaygroundGallery.xaml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/TransformPlaygroundGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/TransformPlaygroundGallery.xaml.cs
new file mode 100644
index 00000000000..6766eba2313
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/ShapesGalleries/TransformPlaygroundGallery.xaml.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
+{
+ public partial class TransformPlaygroundGallery : ContentPage
+ {
+ public TransformPlaygroundGallery()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/VisualStateManagerGalleries/StateTriggerGallery.cs b/Xamarin.Forms.Controls/GalleryPages/VisualStateManagerGalleries/StateTriggerGallery.cs
index 807433a5508..75216d47970 100644
--- a/Xamarin.Forms.Controls/GalleryPages/VisualStateManagerGalleries/StateTriggerGallery.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/VisualStateManagerGalleries/StateTriggerGallery.cs
@@ -6,18 +6,10 @@ public StateTriggerGallery()
{
Title = "StateTrigger Gallery";
- var button = new Button
- {
- Text = "Enable StateTriggers",
- AutomationId = "EnableStateTriggers"
- };
- button.Clicked += ButtonClicked;
-
Content = new StackLayout
{
Children =
{
- button,
GalleryBuilder.NavButton("MinWindowWidth AdaptiveTrigger Gallery", () => new MinWindowWidthAdaptiveTriggerGallery(), Navigation),
GalleryBuilder.NavButton("MinWindowHeight AdaptiveTrigger Gallery", () => new MinWindowHeightAdaptiveTriggerGallery(), Navigation),
GalleryBuilder.NavButton("CompareStateTrigger Gallery", () => new CompareStateTriggerGallery(), Navigation),
@@ -30,16 +22,5 @@ public StateTriggerGallery()
}
};
}
-
- void ButtonClicked(object sender, System.EventArgs e)
- {
- var button = sender as Button;
-
- button.Text = "StateTriggers Enabled!";
- button.TextColor = Color.Black;
- button.IsEnabled = false;
-
- Device.SetFlags(new[] { ExperimentalFlags.StateTriggersExperimental });
- }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/Nuget.Build.targets b/Xamarin.Forms.Controls/Nuget.Build.targets
new file mode 100644
index 00000000000..074e0a355e9
--- /dev/null
+++ b/Xamarin.Forms.Controls/Nuget.Build.targets
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/Source.Build.targets b/Xamarin.Forms.Controls/Source.Build.targets
new file mode 100644
index 00000000000..b79ec209eda
--- /dev/null
+++ b/Xamarin.Forms.Controls/Source.Build.targets
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/XamStore/Views/StorePages.cs b/Xamarin.Forms.Controls/XamStore/Views/StorePages.cs
index 525092fe271..41547370f5f 100644
--- a/Xamarin.Forms.Controls/XamStore/Views/StorePages.cs
+++ b/Xamarin.Forms.Controls/XamStore/Views/StorePages.cs
@@ -307,14 +307,24 @@ public BasePage(string title, Color tint)
Content = new ScrollView { Content = grid };
- grid.Children.Add(MakeButton("Hide Nav Shadow",
+ //grid.Children.Add(MakeButton("FlyoutBackdrop Color",
+ // () =>
+ // {
+ // if (Shell.GetFlyoutBackdropColor(Shell.Current) == Color.Default)
+ // Shell.SetFlyoutBackdropColor(Shell.Current, Color.Purple);
+ // else
+ // Shell.SetFlyoutBackdropColor(Shell.Current, Color.Default);
+ // }),
+ // 0, 21);
+
+ grid.Children.Add(MakeButton("Hide Nav Shadow",
() => Shell.SetNavBarHasShadow(this, false)),
1, 21);
grid.Children.Add(MakeButton("Show Nav Shadow",
() => Shell.SetNavBarHasShadow(this, true)),
2, 21);
- }
+ }
Switch _navBarVisibleSwitch;
Switch _tabBarVisibleSwitch;
diff --git a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
index 4f70e3ce2aa..7a15baa9b41 100644
--- a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
+++ b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
@@ -27,13 +27,16 @@
-
+
+
+
+
@@ -121,4 +124,4 @@
-
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/CollectionViewUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/CollectionViewUITests.cs
index ecce81c4dc6..6a33e94c21b 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/CollectionViewUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/CollectionViewUITests.cs
@@ -291,7 +291,7 @@ public void VisitAndCheckItem(string collectionTestName, string subgallery, stri
}
[TestCase("DataTemplate Galleries", "DataTemplateSelector")]
- void VisitAndCheckForItems(string collectionTestName, string subGallery)
+ public void VisitAndCheckForItems(string collectionTestName, string subGallery)
{
VisitInitialGallery(collectionTestName);
@@ -303,4 +303,4 @@ void VisitAndCheckForItems(string collectionTestName, string subGallery)
}
}
-}
\ No newline at end of file
+}
diff --git a/Xamarin.Forms.Core.UnitTests/AppThemeTests.cs b/Xamarin.Forms.Core.UnitTests/AppThemeTests.cs
index dd5f9f50906..a21cb941436 100644
--- a/Xamarin.Forms.Core.UnitTests/AppThemeTests.cs
+++ b/Xamarin.Forms.Core.UnitTests/AppThemeTests.cs
@@ -54,7 +54,7 @@ public void ThemeChangeUsingSetBinding()
Text = "Green on Light, Red on Dark"
};
- label.SetBinding(Label.TextColorProperty, new OnAppTheme { Light = Color.Green, Dark = Color.Red });
+ label.SetBinding(Label.TextColorProperty, new AppThemeBinding { Light = Color.Green, Dark = Color.Red });
Assert.AreEqual(Color.Green, label.TextColor);
SetAppTheme(OSAppTheme.Dark);
diff --git a/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs b/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
index 548034fd2b6..62bedaa947c 100644
--- a/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
+++ b/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
@@ -2259,5 +2259,16 @@ public void INPCOnBindingWithSource()
page.Title = "Bar";
Assert.That(label.Text, Is.EqualTo("Bar"));
}
+
+ [Test]
+ //https://github.com/xamarin/Xamarin.Forms/issues/10405
+ public void TypeConversionExceptionIsCaughtAndLogged()
+ {
+ var label = new Label();
+ label.SetBinding(Label.TextColorProperty, "color");
+
+ Assert.DoesNotThrow(() => label.BindingContext = new { color = "" });
+ Assert.That(log.Messages.Count, Is.EqualTo(1),"No error logged");
+ }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/DependencyServiceTests.cs b/Xamarin.Forms.Core.UnitTests/DependencyServiceTests.cs
index 30e38b622da..f8c9b138105 100644
--- a/Xamarin.Forms.Core.UnitTests/DependencyServiceTests.cs
+++ b/Xamarin.Forms.Core.UnitTests/DependencyServiceTests.cs
@@ -124,5 +124,14 @@ public void RegisterInterfaceAndOverrideImplementations ()
var global = DependencyService.Get ();
Assert.IsInstanceOf (global);
}
+
+ [Test]
+ public void RegisterSingletonInterface ()
+ {
+ var local = new DependencyTestRegisterImpl();
+ DependencyService.RegisterSingleton (local);
+ var global = DependencyService.Get ();
+ Assert.AreEqual(local, global);
+ }
}
}
diff --git a/Xamarin.Forms.Core.UnitTests/DoubleCollectionTests.cs b/Xamarin.Forms.Core.UnitTests/DoubleCollectionTests.cs
new file mode 100644
index 00000000000..7af21996ab3
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/DoubleCollectionTests.cs
@@ -0,0 +1,24 @@
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public class DoubleCollectionTests
+ {
+ DoubleCollectionConverter _doubleCollectionConverter;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _doubleCollectionConverter = new DoubleCollectionConverter();
+ }
+
+ [Test]
+ public void ConvertStringToDoubleCollectionTest()
+ {
+ DoubleCollection result = _doubleCollectionConverter.ConvertFromInvariantString("10,110 60,10 110,110") as DoubleCollection;
+
+ Assert.IsNotNull(result);
+ Assert.AreEqual(6, result.Count);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/LineTests.cs b/Xamarin.Forms.Core.UnitTests/LineTests.cs
new file mode 100644
index 00000000000..cac5000c1c1
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/LineTests.cs
@@ -0,0 +1,44 @@
+using NUnit.Framework;
+using Xamarin.Forms.Shapes;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public class LineTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup();
+
+ Device.SetFlags(new[] { ExperimentalFlags.ShapesExperimental });
+ }
+
+ [Test]
+ public void XPointCanBeSetFromStyle()
+ {
+ var line = new Line();
+
+ Assert.AreEqual(0.0, line.X1);
+ line.SetValue(Line.X1Property, 1.0, true);
+ Assert.AreEqual(1.0, line.X1);
+
+ Assert.AreEqual(0.0, line.X2);
+ line.SetValue(Line.X2Property, 100.0, true);
+ Assert.AreEqual(100.0, line.X2);
+ }
+
+ [Test]
+ public void YPointCanBeSetFromStyle()
+ {
+ var line = new Line();
+
+ Assert.AreEqual(0.0, line.Y1);
+ line.SetValue(Line.Y1Property, 1.0, true);
+ Assert.AreEqual(1.0, line.Y1);
+
+ Assert.AreEqual(0.0, line.Y2);
+ line.SetValue(Line.Y2Property, 10.0, true);
+ Assert.AreEqual(10.0, line.Y2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/Markup/DefaultBindablePropertiesTests.cs b/Xamarin.Forms.Core.UnitTests/Markup/DefaultBindablePropertiesTests.cs
index 5ba3ee99340..7c4418ddf9c 100644
--- a/Xamarin.Forms.Core.UnitTests/Markup/DefaultBindablePropertiesTests.cs
+++ b/Xamarin.Forms.Core.UnitTests/Markup/DefaultBindablePropertiesTests.cs
@@ -7,6 +7,7 @@
namespace Xamarin.Forms.Markup.UnitTests
{
+ using Xamarin.Forms.Shapes;
using XamarinFormsMarkupUnitTestsDefaultBindablePropertiesViews;
[TestFixture(true)]
@@ -77,7 +78,39 @@ public void AllBindableElementsInCoreHaveDefaultBindablePropertyOrAreExcluded()
{ typeof(ShellItem), tbd },
{ typeof(ShellSection), tbd },
{ typeof(Tab), tbd },
- { typeof(TabBar), tbd }
+ { typeof(TabBar), tbd },
+
+ { typeof(ArcSegment), tbd },
+ { typeof(BezierSegment), tbd },
+ { typeof(CompositeTransform), tbd },
+ { typeof(EllipseGeometry), tbd },
+ { typeof(Geometry), tbd },
+ { typeof(GeometryGroup), tbd },
+ { typeof(LineGeometry), tbd },
+ { typeof(LineSegment), tbd },
+ { typeof(MatrixTransform), tbd },
+ { typeof(Path), tbd },
+ { typeof(PathFigure), tbd },
+ { typeof(PathGeometry), tbd },
+ { typeof(PathSegment), tbd },
+ { typeof(PolyBezierSegment), tbd },
+ { typeof(PolyLineSegment), tbd },
+ { typeof(PolyQuadraticBezierSegment), tbd },
+ { typeof(QuadraticBezierSegment), tbd },
+ { typeof(RectangleGeometry), tbd },
+ { typeof(RotateTransform), tbd },
+ { typeof(ScaleTransform), tbd },
+ { typeof(SkewTransform), tbd },
+
+ { typeof(Shape), tbd },
+ { typeof(Transform), tbd },
+ { typeof(TransformGroup), tbd },
+ { typeof(TranslateTransform), tbd },
+ { typeof(Ellipse), tbd },
+ { typeof(Line), tbd },
+ { typeof(Polygon), tbd },
+ { typeof(Polyline), tbd },
+ { typeof(Rectangle), tbd },
};
var failMessage = new StringBuilder();
diff --git a/Xamarin.Forms.Core.UnitTests/MockPlatformServices.cs b/Xamarin.Forms.Core.UnitTests/MockPlatformServices.cs
index d8ba95c45b4..21cfa7372d4 100644
--- a/Xamarin.Forms.Core.UnitTests/MockPlatformServices.cs
+++ b/Xamarin.Forms.Core.UnitTests/MockPlatformServices.cs
@@ -12,6 +12,7 @@
using FileAccess = System.IO.FileAccess;
using FileShare = System.IO.FileShare;
using Stream = System.IO.Stream;
+using Xamarin.Forms.Internals;
[assembly:Dependency (typeof(MockDeserializer))]
[assembly:Dependency (typeof(MockResourcesProvider))]
@@ -40,18 +41,13 @@ public MockPlatformServices (Action invokeOnMainThread = null, Action> 4);
- ret [i*2+1] = (char)hex (bytes [i] & 0xf);
- }
- return new string (ret);
+ return Internals.Crc64.GetHash(input);
}
+
+ string IPlatformServices.GetMD5Hash(string input) => GetHash(input);
+
static int hex (int v)
{
if (v < 10)
diff --git a/Xamarin.Forms.Core.UnitTests/MultiBindingTests.cs b/Xamarin.Forms.Core.UnitTests/MultiBindingTests.cs
new file mode 100644
index 00000000000..d6a27d921f1
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MultiBindingTests.cs
@@ -0,0 +1,873 @@
+using System;
+using System.Threading.Tasks;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using NUnit.Framework;
+using Xamarin.Forms;
+using System.Globalization;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class MultiBindingTests : BaseTestFixture
+ {
+ const string c_Fallback = "First Middle Last";
+ const string c_TargetNull = "No Name Given";
+
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup();
+ Device.PlatformServices = new MockPlatformServices();
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void TestChildOneWayOnMultiTwoWay()
+ {
+ var group = new GroupViewModel();
+ var stack = new StackLayout
+ {
+ BindingContext = group.Person1
+ };
+
+ string oldName = group.Person1.FullName;
+ string oldFirstName = group.Person1.FirstName;
+ string oldMiddleName = group.Person1.MiddleName;
+ string oldLastName = group.Person1.LastName;
+
+ var label = new Label();
+ label.SetBinding(Label.TextProperty, new MultiBinding
+ {
+ Bindings = new Collection
+ {
+ new Binding(nameof(PersonViewModel.FirstName), mode: BindingMode.OneWay),
+ new Binding(nameof(PersonViewModel.MiddleName)),
+ new Binding(nameof(PersonViewModel.LastName)),
+ },
+ Converter = new StringConcatenationConverter(),
+ Mode = BindingMode.TwoWay,
+ });
+ stack.Children.Add(label);
+
+ Assert.AreEqual(oldName, label.Text);
+ Assert.AreEqual(oldName, group.Person1.FullName);
+
+ label.SetValueCore(Label.TextProperty, $"{oldFirstName.ToUpper()} {oldMiddleName} {oldLastName.ToUpper()}", Internals.SetValueFlags.None);
+ Assert.AreEqual($"{oldFirstName} {oldMiddleName} {oldLastName.ToUpper()}", group.Person1.FullName);
+ }
+
+ [Test]
+ public void TestRelativeSources()
+ {
+ // Self
+ var entry1 = new Entry()
+ {
+ FontFamily = "Courier New",
+ FontSize = 12,
+ FontAttributes = FontAttributes.Italic
+ };
+ entry1.SetBinding(Entry.TextProperty,
+ new MultiBinding
+ {
+ Bindings = new Collection
+ {
+ new Binding(nameof(Entry.FontFamily), source: RelativeBindingSource.Self),
+ new Binding(nameof(Entry.FontSize), source: RelativeBindingSource.Self),
+ new Binding(nameof(Entry.FontAttributes), source: RelativeBindingSource.Self),
+ },
+ Converter = new StringConcatenationConverter()
+ });
+ Assert.AreEqual("Courier New 12 Italic", entry1.Text);
+ // Our unit test's ConvertBack should throw an exception below because the desired
+ // return types aren't all strings
+ Assert.Throws(() => entry1.SetValueCore(Entry.TextProperty, "Arial 12 Italic", Internals.SetValueFlags.None));
+
+ // FindAncestor and FindAncestorBindingContext
+ // are already tested in TestNestedMultiBindings
+ TestNestedMultiBindings();
+
+ // TemplatedParent
+ var templ = new ControlTemplate(typeof(ExpanderControlTemplate));
+ var expander = new ExpanderControl
+ {
+ ControlTemplate = templ,
+ Content = new Label { Text = "Content" },
+ IsEnabled = true,
+ IsExpanded = true
+ };
+ var cp = expander.Children[0].LogicalChildren[1] as ContentPresenter;
+ Assert.IsTrue(cp.IsVisible);
+ expander.IsEnabled = false;
+ Assert.IsFalse(cp.IsVisible);
+ expander.IsEnabled = true;
+ Assert.IsTrue(cp.IsVisible);
+ expander.IsExpanded = false;
+ Assert.IsFalse(cp.IsVisible);
+ }
+
+ [Test]
+ public void TestNestedMultiBindings()
+ {
+ var group = new GroupViewModel();
+ var stack = new StackLayout
+ {
+ BindingContext = group
+ };
+
+ var checkBox = new CheckBox();
+ checkBox.SetBinding(
+ CheckBox.IsCheckedProperty,
+ new MultiBinding {
+ Bindings = {
+ new MultiBinding {
+ Bindings = {
+ new Binding(nameof(PersonViewModel.IsOver16)),
+ new Binding(nameof(PersonViewModel.HasPassedTest)),
+ new Binding(nameof(PersonViewModel.IsSuspended), converter: new Inverter()),
+ new Binding(
+ nameof(GroupViewModel.SuspendAll),
+ converter: new Inverter(),
+ source: new RelativeBindingSource(
+ RelativeBindingSourceMode.FindAncestorBindingContext,
+ ancestorType: typeof(GroupViewModel)))
+ },
+ Converter = new AllTrueMultiConverter()
+ },
+ new Binding(nameof(PersonViewModel.IsMonarch)),
+ new Binding(
+ $"{nameof(Element.BindingContext)}.{nameof(GroupViewModel.PardonAllSuspensions)}",
+ source: new RelativeBindingSource(RelativeBindingSourceMode.FindAncestor, typeof(StackLayout))),
+ },
+ Converter = new AnyTrueMultiConverter(),
+ FallbackValue = "false", //use a string literal here to test xaml conversion
+ });
+
+ // ^^
+ // CanDrive = (IsOver16 && HasPassedTest && !IsSuspended && !Group.SuspendAll) || IsMonarch || Group.PardonAllSuspensions
+
+ checkBox.BindingContext = group.Person5;
+ stack.Children.Add(checkBox);
+
+ // Monarch can do whatever she wants
+ Assert.IsTrue(checkBox.IsChecked);
+
+ // ... Until being deposed after a coup
+ group.Person5.IsMonarch = false;
+ Assert.IsFalse(checkBox.IsChecked);
+
+ // After passing test she can drive again
+ group.Person5.HasPassedTest = true;
+ Assert.IsTrue(checkBox.IsChecked);
+
+ // Martial law declared; no one can drive
+ group.SuspendAll = true;
+ Assert.IsFalse(checkBox.IsChecked);
+
+ // Martial law is over
+ group.SuspendAll = false;
+ Assert.IsTrue(checkBox.IsChecked);
+
+ // But she got in an accident and now can't drive again
+ group.Person5.IsSuspended = true;
+ Assert.IsFalse(checkBox.IsChecked);
+
+ // The new PM has pardoned everyone after the end of the rebellion
+ group.PardonAllSuspensions = true;
+ Assert.IsTrue(checkBox.IsChecked);
+ }
+
+ [Test]
+ public void TestConverterReturnValues()
+ {
+ var group = new GroupViewModel();
+ var stack = new StackLayout
+ {
+ BindingContext = group
+ };
+
+ string oldName, oldFirstName, oldMiddleName, oldLastName, newLabelText;
+
+ // "Convert" return values
+ oldName = group.Person1.FullName;
+ oldFirstName = group.Person1.FirstName;
+ var label1 = GenerateNameLabel(nameof(group.Person1), BindingMode.TwoWay);
+ stack.Children.Add(label1);
+ group.Person1.FirstName = "DoNothing";
+ Assert.AreEqual(oldName, label1.Text);
+ Assert.AreEqual("DoNothing", group.Person1.FirstName);
+
+ group.Person1.FirstName = "UnsetValue";
+ Assert.AreEqual(c_Fallback, label1.Text);
+ Assert.AreEqual("UnsetValue", group.Person1.FirstName);
+
+ group.Person1.FirstName = "null";
+ Assert.AreEqual(c_TargetNull, label1.Text);
+ Assert.AreEqual("null", group.Person1.FirstName);
+
+ // "ConvertBack" return values
+ oldName = group.Person2.FullName;
+ oldFirstName = group.Person2.FirstName;
+ oldMiddleName = group.Person2.MiddleName;
+ oldLastName = group.Person2.LastName;
+
+ var label2 = GenerateNameLabel(nameof(group.Person2), BindingMode.TwoWay);
+ stack.Children.Add(label2);
+ label2.SetValueCore(Label.TextProperty, $"DoNothing {oldMiddleName} {oldLastName.ToUpper()}", Internals.SetValueFlags.None);
+ Assert.AreEqual($"{oldFirstName} {oldMiddleName} {oldLastName.ToUpper()}", group.Person2.FullName);
+ Assert.AreEqual($"DoNothing {oldMiddleName} {oldLastName.ToUpper()}", label2.Text);
+
+ label2.Text = oldName;
+ Assert.AreEqual(oldName, group.Person2.FullName);
+ Assert.AreEqual(oldName, label2.Text);
+ // Any UnsetValue prevents any changes to source but target accepts value
+ label2.SetValueCore(Label.TextProperty, $"{oldFirstName.ToUpper()} UnsetValue {oldLastName}");
+ Assert.AreEqual($"{oldFirstName.ToUpper()} {oldMiddleName} {oldLastName}", group.Person2.FullName);
+ Assert.AreEqual($"{oldFirstName.ToUpper()} UnsetValue {oldLastName}", label2.Text);
+
+ label2.Text = oldName;
+ Assert.AreEqual(oldName, group.Person2.FullName);
+ Assert.AreEqual(oldName, label2.Text);
+ label2.SetValueCore(Label.TextProperty, "null");
+ // Returning null prevents changes to source but target accepts value
+ Assert.AreEqual(oldName, group.Person2.FullName);
+ Assert.AreEqual("null", label2.Text);
+
+ // Insufficient memebrs in ConvertBack array don't affect remaining
+ label2.Text = oldName;
+ Assert.AreEqual(oldName, group.Person2.FullName);
+ Assert.AreEqual(oldName, label2.Text);
+ label2.SetValueCore(Label.TextProperty, $"Duck Duck", Internals.SetValueFlags.None);
+ Assert.AreEqual($"Duck Duck {oldLastName}", group.Person2.FullName);
+ Assert.AreEqual($"Duck Duck", label2.Text);
+
+ // Too many members are no problem either
+ label2.Text = oldName;
+ Assert.AreEqual(oldName, group.Person2.FullName);
+ label2.SetValueCore(Label.TextProperty, oldName + " Extra", Internals.SetValueFlags.None);
+ Assert.AreEqual(oldName, group.Person2.FullName);
+ Assert.AreEqual(oldName + " Extra", label2.Text);
+ }
+
+ //[Test]
+ //public void TestEfficiency()
+ //{
+ // var group = new GroupViewModel();
+ // var stack = new StackLayout
+ // {
+ // BindingContext = group.Person1
+ // };
+
+ // string oldName = group.Person1.FullName;
+
+ // var converter = new StringConcatenationConverter();
+
+ // var label = new Label();
+ // label.SetBinding(Label.TextProperty, new MultiBinding
+ // {
+ // Bindings = new Collection
+ // {
+ // new Binding(nameof(PersonViewModel.FirstName)),
+ // new Binding(nameof(PersonViewModel.MiddleName)),
+ // new Binding(nameof(PersonViewModel.LastName)),
+ // },
+ // Converter = converter,
+ // Mode = BindingMode.TwoWay,
+ // });
+
+ // // Initial binding should result in 1 Convert, no ConvertBack's
+ // Assert.AreEqual(1, converter.Converts);
+ // Assert.AreEqual(0, converter.ConvertBacks);
+
+ // // Parenting results in bctx change; should be 1 additional Convert, no ConvertBack's
+ // stack.Children.Add(label);
+ // Assert.AreEqual(group.Person1.FullName, label.Text);
+ // Assert.AreEqual(2, converter.Converts);
+ // Assert.AreEqual(0, converter.ConvertBacks);
+
+ // // Source change results in 1 additional Convert, no ConvertBack's
+ // group.Person1.FirstName = group.Person1.FullName.ToUpper();
+ // Assert.AreEqual(3, converter.Converts);
+ // Assert.AreEqual(0, converter.ConvertBacks);
+
+ // // Target change results in 1 ConvertBack, one additional Convert
+ // label.Text = oldName;
+ // Assert.AreEqual(oldName, group.Person1.FullName);
+ // Assert.AreEqual(4, converter.Converts);
+ // Assert.AreEqual(1, converter.ConvertBacks);
+ //}
+
+ [Test]
+ public void TestBindingModes()
+ {
+ var group = new GroupViewModel();
+ var stack = new StackLayout
+ {
+ BindingContext = group
+ };
+
+ string oldName = group.Person1.FullName;
+ var label1W = GenerateNameLabel(nameof(group.Person1), BindingMode.OneWay);
+ stack.Children.Add(label1W);
+ Assert.AreEqual(group.Person1.FullName, label1W.Text);
+ label1W.SetValueCore(Label.TextProperty, "don't change source", Internals.SetValueFlags.None);
+ Assert.AreEqual(oldName, group.Person1.FullName);
+
+ var label2W = GenerateNameLabel(nameof(group.Person2), BindingMode.TwoWay);
+ stack.Children.Add(label2W);
+ Assert.AreEqual(group.Person2.FullName, label2W.Text);
+ label2W.Text = group.Person2.FullName.ToUpper();
+ Assert.AreEqual(group.Person2.FullName.ToUpper(), label2W.Text);
+
+ oldName = group.Person3.FullName;
+ var label1WTS = GenerateNameLabel(nameof(group.Person3), BindingMode.OneWayToSource);
+ stack.Children.Add(label1WTS);
+ Assert.AreEqual(Label.TextProperty.DefaultValue, label1WTS.Text);
+ label1WTS.SetValueCore(Label.TextProperty, oldName, Internals.SetValueFlags.None);
+ Assert.AreEqual(oldName, label1WTS.Text);
+ Assert.AreEqual(oldName, group.Person3.FullName);
+
+ oldName = group.Person4.FullName;
+ var label1T = GenerateNameLabel(nameof(group.Person4), BindingMode.OneTime);
+ stack.Children.Add(label1T);
+ Assert.AreEqual(group.Person4.FullName, label1T.Text);
+ group.Person4.FirstName = "Do";
+ group.Person4.MiddleName = "Not";
+ group.Person4.LastName = "Update";
+ // changing source values should not trigger update
+ Assert.AreEqual(oldName, label1T.Text);
+ Assert.AreEqual("Do Not Update", group.Person4.FullName);
+ group.Person4 = group.Person1;
+ // changing the bctx should trigger update
+ Assert.AreEqual(group.Person1.FullName, label1T.Text);
+ }
+
+ [Test]
+ public void TestStringFormat()
+ {
+ var property = BindableProperty.Create("foo", typeof(string), typeof(MockBindable), null);
+ var bindable = new MockBindable();
+ var multibinding = new MultiBinding {
+ Bindings = {
+ new Binding ("foo"),
+ new Binding ("bar"),
+ new Binding ("baz"),
+ },
+ StringFormat = "{0} - {1} - {2}"
+ };
+ Assert.DoesNotThrow(()=>bindable.SetBinding(property, multibinding));
+ Assert.DoesNotThrow(()=>bindable.BindingContext = new { foo = "FOO", bar = 42, baz = "BAZ" });
+ Assert.That(bindable.GetValue(property), Is.EqualTo("FOO - 42 - BAZ"));
+ }
+
+ private Label GenerateNameLabel(string person, BindingMode mode)
+ {
+ var label = new Label();
+ label.SetBinding(Label.TextProperty, new MultiBinding
+ {
+ Bindings = new Collection
+ {
+ new Binding(nameof(PersonViewModel.FirstName)),
+ new Binding(nameof(PersonViewModel.MiddleName)),
+ new Binding(nameof(PersonViewModel.LastName)),
+ },
+ Converter = new StringConcatenationConverter(),
+ Mode = mode,
+ FallbackValue = c_Fallback,
+ TargetNullValue = c_TargetNull
+ });
+ label.SetBinding(Label.BindingContextProperty, new Binding(person));
+ return label;
+ }
+
+ public class Inverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ bool? b = value as bool?;
+ if (b == null)
+ return false;
+ return !b.Value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return Convert(value, targetType, parameter, culture);
+ }
+ }
+
+ public class AllTrueMultiConverter : IMultiValueConverter
+ {
+ public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (values == null || !targetType.IsAssignableFrom(typeof(bool)))
+ // Return UnsetValue to use the binding FallbackValue
+ return BindableProperty.UnsetValue;
+ foreach (var value in values)
+ {
+ if (!(value is bool b))
+ return BindableProperty.UnsetValue;
+ else if (!b)
+ return false;
+ }
+ return true;
+ }
+
+ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+ {
+ if (!(value is bool b) || targetTypes.Any(t => !t.IsAssignableFrom(typeof(bool))))
+ // Return null to indicate conversion back is not possible
+ return null;
+
+ if (b)
+ return targetTypes.Select(t => (object)true).ToArray();
+ else
+ // Can't convert back from false because of ambiguity
+ return null;
+ }
+ }
+
+ public class AnyTrueMultiConverter : IMultiValueConverter
+ {
+ public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (values == null || !targetType.IsAssignableFrom(typeof(bool)))
+ // Return UnsetValue to use the binding FallbackValue
+ return BindableProperty.UnsetValue;
+ foreach (var value in values)
+ {
+ if (!(value is bool b))
+ return BindableProperty.UnsetValue;
+ else if (b)
+ return true;
+ }
+ return false;
+ }
+
+ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+ {
+ if (!(value is bool b) || targetTypes.Any(t => !t.IsAssignableFrom(typeof(bool))))
+ // Return null to indicate conversion back is not possible
+ return null;
+
+ if (!b)
+ return targetTypes.Select(t => (object)false).ToArray();
+ else
+ // Can't convert back from true because of ambiguity
+ return null;
+ }
+ }
+
+ public class StringConcatenationConverter : IMultiValueConverter
+ {
+ public int Converts { get; private set; }
+
+ public int ConvertBacks { get; private set; }
+
+ public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+ {
+ Converts++;
+
+ if (values is null)
+ return null;
+ string separator = parameter as string ?? " ";
+ StringBuilder sb = new StringBuilder();
+ int i = 0;
+
+ if (values.All(v => string.IsNullOrEmpty(v as string)))
+ return BindableProperty.UnsetValue;
+
+ foreach (var value in values)
+ {
+ if (value as string == "DoNothing")
+ return Binding.DoNothing;
+ if (value as string == "UnsetValue")
+ return BindableProperty.UnsetValue;
+ if (value as string == "null")
+ return null;
+
+ if (i != 0 && separator != null)
+ sb.Append(separator);
+ sb.Append(value?.ToString());
+ i++;
+ }
+ return sb.ToString();
+ }
+
+ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+ {
+ ConvertBacks++;
+
+ string s = value as string;
+ if (s == "null" || string.IsNullOrEmpty(s))
+ return null;
+
+ string separator = parameter as string ?? " ";
+
+ if (!targetTypes.All(t=>t==typeof(object)) && !targetTypes.All(t => t == typeof(string)))
+ // Normally we'd return null but throw exception just for unit test to catch
+ throw new Exception("Invalid targetTypes");
+
+ var array = s.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries).Cast
diff --git a/Xamarin.Forms.Core/AnimationExtensions.cs b/Xamarin.Forms.Core/AnimationExtensions.cs
index 49cf090eba9..629292479b7 100644
--- a/Xamarin.Forms.Core/AnimationExtensions.cs
+++ b/Xamarin.Forms.Core/AnimationExtensions.cs
@@ -176,7 +176,7 @@ static void AnimateInternal(IAnimatable self, string name, Func tr
var info = new Info { Rate = rate, Length = length, Easing = easing ?? Easing.Linear };
- var tweener = new Tweener(info.Length);
+ var tweener = new Tweener(info.Length, info.Rate);
tweener.Handle = key;
tweener.ValueUpdated += HandleTweenerUpdated;
tweener.Finished += HandleTweenerFinished;
diff --git a/Xamarin.Forms.Core/OnAppTheme.cs b/Xamarin.Forms.Core/AppThemeBinding.cs
similarity index 60%
rename from Xamarin.Forms.Core/OnAppTheme.cs
rename to Xamarin.Forms.Core/AppThemeBinding.cs
index 6c5d9acf03b..4d3cf2063e1 100644
--- a/Xamarin.Forms.Core/OnAppTheme.cs
+++ b/Xamarin.Forms.Core/AppThemeBinding.cs
@@ -2,14 +2,19 @@
namespace Xamarin.Forms
{
- class OnAppTheme : BindingBase
+ class AppThemeBinding : BindingBase
{
WeakReference _weakTarget;
BindableProperty _targetProperty;
- public OnAppTheme() => Application.Current.RequestedThemeChanged += (o,e) => Device.BeginInvokeOnMainThread(() => ApplyCore());
+ public AppThemeBinding() => Application.Current.RequestedThemeChanged += (o,e) => Device.BeginInvokeOnMainThread(() => ApplyCore());
- internal override BindingBase Clone() => new OnAppTheme { Light = Light, Dark = Dark, Default = Default };
+ internal override BindingBase Clone() => new AppThemeBinding {
+ Light = Light,
+ _isLightSet = _isLightSet,
+ Dark = Dark,
+ _isDarkSet = _isDarkSet,
+ Default = Default };
internal override void Apply(bool fromTarget)
{
@@ -40,14 +45,12 @@ void ApplyCore()
target?.SetValueCore(_targetProperty, GetValue());
}
- T _light;
- T _dark;
- T _default;
+ object _light;
+ object _dark;
bool _isLightSet;
bool _isDarkSet;
- bool _isDefaultSet;
- public T Light
+ public object Light
{
get => _light;
set
@@ -57,7 +60,7 @@ public T Light
}
}
- public T Dark
+ public object Dark
{
get => _dark;
set
@@ -67,25 +70,16 @@ public T Dark
}
}
- public T Default
- {
- get => _default;
- set
- {
- _default = value;
- _isDefaultSet = true;
- }
- }
+ public object Default { get; set; }
- T GetValue()
+ object GetValue()
{
- switch (Application.Current.RequestedTheme)
- {
- default:
- case OSAppTheme.Light:
- return _isLightSet ? Light : (_isDefaultSet ? Default : default);
- case OSAppTheme.Dark:
- return _isDarkSet ? Dark : (_isDefaultSet ? Default : default);
+ switch (Application.Current.RequestedTheme) {
+ default:
+ case OSAppTheme.Light:
+ return _isLightSet ? Light : Default;
+ case OSAppTheme.Dark:
+ return _isDarkSet ? Dark : Default;
}
}
}
diff --git a/Xamarin.Forms.Core/Application.cs b/Xamarin.Forms.Core/Application.cs
index 27f92572c7b..45603dc43f9 100644
--- a/Xamarin.Forms.Core/Application.cs
+++ b/Xamarin.Forms.Core/Application.cs
@@ -37,6 +37,8 @@ public Application()
SystemResources = DependencyService.Get().GetSystemResources();
SystemResources.ValuesChanged += OnParentResourcesChanged;
_platformConfigurationRegistry = new Lazy>(() => new PlatformConfigurationRegistry(this));
+ // Initialize this value, when the app loads
+ _lastAppTheme = RequestedTheme;
}
public void Quit()
@@ -176,6 +178,7 @@ public event EventHandler RequestedThemeChanged
OSAppTheme _lastAppTheme;
OSAppTheme _userAppTheme = OSAppTheme.Unspecified;
+
[EditorBrowsable(EditorBrowsableState.Never)]
public void TriggerThemeChanged(AppThemeChangedEventArgs args)
{
@@ -191,19 +194,19 @@ void TriggerThemeChangedActual(AppThemeChangedEventArgs args)
// On iOS the event is triggered more than once.
// To minimize that for us, we only do it when the theme actually changes and it's not currently firing
- if (!_themeChangedFiring && RequestedTheme != _lastAppTheme)
+ if (_themeChangedFiring || RequestedTheme == _lastAppTheme)
+ return;
+
+ try
{
- try
- {
- _themeChangedFiring = true;
- _lastAppTheme = RequestedTheme;
+ _themeChangedFiring = true;
+ _lastAppTheme = RequestedTheme;
- _weakEventManager.HandleEvent(this, args, nameof(RequestedThemeChanged));
- }
- finally
- {
- _themeChangedFiring = false;
- }
+ _weakEventManager.HandleEvent(this, args, nameof(RequestedThemeChanged));
+ }
+ finally
+ {
+ _themeChangedFiring = false;
}
}
diff --git a/Xamarin.Forms.Core/BindableProperty.cs b/Xamarin.Forms.Core/BindableProperty.cs
index 05996e579c8..eed24d52f6a 100644
--- a/Xamarin.Forms.Core/BindableProperty.cs
+++ b/Xamarin.Forms.Core/BindableProperty.cs
@@ -61,6 +61,8 @@ public sealed class BindableProperty
{ typeof(ulong), new[] { typeof(string), typeof(float), typeof(double), typeof(decimal) } },
};
+ public static readonly object UnsetValue = new object();
+
BindableProperty(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWay,
ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
CoerceValueDelegate coerceValue = null, BindablePropertyBindingChanging bindingChanging = null, bool isReadOnly = false, CreateDefaultValueDelegate defaultValueCreator = null)
diff --git a/Xamarin.Forms.Core/Binding.cs b/Xamarin.Forms.Core/Binding.cs
index 33ab46dedf2..1098913eb02 100644
--- a/Xamarin.Forms.Core/Binding.cs
+++ b/Xamarin.Forms.Core/Binding.cs
@@ -87,6 +87,8 @@ public object Source
}
}
+ public static readonly object DoNothing = new object();
+
[EditorBrowsable(EditorBrowsableState.Never)]
public string UpdateSourceEventName {
get { return _updateSourceEventName; }
@@ -106,7 +108,7 @@ public static Binding Create(Expression> property
return new Binding(GetBindingPath(propertyGetter), mode, converter, converterParameter, stringFormat);
}
-
+
internal override void Apply(bool fromTarget)
{
base.Apply(fromTarget);
@@ -143,25 +145,27 @@ async void ApplyRelativeSourceBinding(
BindableProperty targetProperty)
#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void
{
- if (!(targetObject is Element elem))
- throw new InvalidOperationException();
if (!(Source is RelativeBindingSource relativeSource))
return;
- object resolvedSource;
+ var relativeSourceTarget = RelativeSourceTargetOverride ?? targetObject as Element;
+ if (!(relativeSourceTarget is Element))
+ throw new InvalidOperationException();
+
+ object resolvedSource = null;
switch (relativeSource.Mode)
{
case RelativeBindingSourceMode.Self:
- resolvedSource = targetObject;
+ resolvedSource = relativeSourceTarget;
break;
case RelativeBindingSourceMode.TemplatedParent:
- resolvedSource = await TemplateUtilities.FindTemplatedParentAsync(elem);
+ resolvedSource = await TemplateUtilities.FindTemplatedParentAsync(relativeSourceTarget);
break;
case RelativeBindingSourceMode.FindAncestor:
case RelativeBindingSourceMode.FindAncestorBindingContext:
- ApplyAncestorTypeBinding(elem, targetProperty);
+ ApplyAncestorTypeBinding(targetObject, relativeSourceTarget, targetProperty);
return;
default:
@@ -172,15 +176,16 @@ async void ApplyRelativeSourceBinding(
}
void ApplyAncestorTypeBinding(
- Element target,
+ BindableObject actualTarget,
+ Element relativeSourceTarget,
BindableProperty targetProperty,
Element currentElement = null,
int currentLevel = 0,
List chain = null,
object lastMatchingBctx = null)
{
- currentElement = currentElement ?? target;
- chain = chain ?? new List { target };
+ currentElement = currentElement ?? relativeSourceTarget;
+ chain = chain ?? new List { relativeSourceTarget };
if (!(Source is RelativeBindingSource relativeSource))
return;
@@ -190,7 +195,7 @@ void ApplyAncestorTypeBinding(
{
// Couldn't find the desired ancestor type in the chain, but it may be added later,
// so apply with a null source for now.
- _expression.Apply(null, target, targetProperty);
+ _expression.Apply(null, actualTarget, targetProperty);
_expression.SubscribeToAncestryChanges(
chain,
relativeSource.Mode == RelativeBindingSourceMode.FindAncestorBindingContext,
@@ -206,7 +211,7 @@ void ApplyAncestorTypeBinding(
resolvedSource = currentElement.RealParent;
else
resolvedSource = currentElement.RealParent?.BindingContext;
- _expression.Apply(resolvedSource, target, targetProperty);
+ _expression.Apply(resolvedSource, actualTarget, targetProperty);
_expression.SubscribeToAncestryChanges(
chain,
relativeSource.Mode == RelativeBindingSourceMode.FindAncestorBindingContext,
@@ -215,7 +220,8 @@ void ApplyAncestorTypeBinding(
else
{
ApplyAncestorTypeBinding(
- target,
+ actualTarget,
+ relativeSourceTarget,
targetProperty,
currentElement.RealParent,
currentLevel,
@@ -230,7 +236,8 @@ void ApplyAncestorTypeBinding(
{
currentElement.ParentSet -= onElementParentSet;
ApplyAncestorTypeBinding(
- target,
+ actualTarget,
+ relativeSourceTarget,
targetProperty,
currentElement,
currentLevel,
@@ -276,7 +283,11 @@ bool ElementFitsAncestorTypeAndLevel(Element element, ref int level, ref object
internal override BindingBase Clone()
{
- return new Binding(Path, Mode, Converter, ConverterParameter, StringFormat, Source) {
+ return new Binding(Path, Mode) {
+ Converter = Converter,
+ ConverterParameter = ConverterParameter,
+ StringFormat = StringFormat,
+ Source = Source,
UpdateSourceEventName = UpdateSourceEventName,
TargetNullValue = TargetNullValue,
FallbackValue = FallbackValue,
@@ -301,7 +312,7 @@ internal override object GetTargetValue(object value, Type sourcePropertyType)
internal override void Unapply(bool fromBindingContextChanged = false)
{
- if (Source != null && fromBindingContextChanged && IsApplied)
+ if (Source != null && !(Source is RelativeBindingSource) && fromBindingContextChanged && IsApplied)
return;
base.Unapply(fromBindingContextChanged: fromBindingContextChanged);
diff --git a/Xamarin.Forms.Core/BindingBase.cs b/Xamarin.Forms.Core/BindingBase.cs
index 6f8cead1122..921b549e2d1 100644
--- a/Xamarin.Forms.Core/BindingBase.cs
+++ b/Xamarin.Forms.Core/BindingBase.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Runtime.CompilerServices;
+using Xamarin.Forms.Internals;
namespace Xamarin.Forms
{
@@ -12,6 +13,7 @@ public abstract class BindingBase
string _stringFormat;
object _targetNullValue;
object _fallbackValue;
+ WeakReference _relativeSourceTargetOverride;
internal BindingBase()
{
@@ -41,7 +43,6 @@ public string StringFormat
set
{
ThrowIfApplied();
-
_stringFormat = value;
}
}
@@ -69,6 +70,23 @@ public object FallbackValue {
internal bool IsApplied { get; private set; }
+ internal Element RelativeSourceTargetOverride
+ {
+ get
+ {
+ Element element = null;
+ _relativeSourceTargetOverride?.TryGetTarget(out element);
+ return element;
+ }
+ set
+ {
+ if (value != null)
+ _relativeSourceTargetOverride = new WeakReference(value);
+ else
+ _relativeSourceTargetOverride = null;
+ }
+ }
+
public static void DisableCollectionSynchronization(IEnumerable collection)
{
if (collection == null)
@@ -91,18 +109,12 @@ public static void EnableCollectionSynchronization(IEnumerable collection, objec
protected void ThrowIfApplied()
{
if (IsApplied)
- throw new InvalidOperationException("Can not change a binding while it's applied");
+ throw new InvalidOperationException("Cannot change a binding while it's applied");
}
- internal virtual void Apply(bool fromTarget)
- {
- IsApplied = true;
- }
+ internal virtual void Apply(bool fromTarget) => IsApplied = true;
- internal virtual void Apply(object context, BindableObject bindObj, BindableProperty targetProperty, bool fromBindingContextChanged = false)
- {
- IsApplied = true;
- }
+ internal virtual void Apply(object context, BindableObject bindObj, BindableProperty targetProperty, bool fromBindingContextChanged = false) => IsApplied = true;
internal abstract BindingBase Clone();
@@ -111,17 +123,39 @@ internal virtual object GetSourceValue(object value, Type targetPropertyType)
if (value == null && TargetNullValue != null)
return TargetNullValue;
- if (StringFormat != null)
- return string.Format(StringFormat, value);
+ if (StringFormat != null && TryFormat(StringFormat, value, out var formatted))
+ return formatted;
return value;
}
- internal virtual object GetTargetValue(object value, Type sourcePropertyType)
+ internal bool TryFormat(string format, object arg0, out string value)
{
- return value;
+ try {
+ value = string.Format(format, arg0);
+ return true;
+ } catch (FormatException) {
+ value = null;
+ Log.Warning("Binding", "FormatException");
+ return false;
+ }
}
+ internal bool TryFormat(string format, object[] args, out string value)
+ {
+ try {
+ value = string.Format(format, args);
+ return true;
+ }
+ catch (FormatException) {
+ value = null;
+ Log.Warning("Binding", "FormatException");
+ return false;
+ }
+ }
+
+ internal virtual object GetTargetValue(object value, Type sourcePropertyType) => value;
+
internal static bool TryGetSynchronizedCollection(IEnumerable collection, out CollectionSynchronizationContext synchronizationContext)
{
if (collection == null)
@@ -130,9 +164,6 @@ internal static bool TryGetSynchronizedCollection(IEnumerable collection, out Co
return SynchronizedCollections.TryGetValue(collection, out synchronizationContext);
}
- internal virtual void Unapply(bool fromBindingContextChanged = false)
- {
- IsApplied = false;
- }
+ internal virtual void Unapply(bool fromBindingContextChanged = false) => IsApplied = false;
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindingExpression.cs b/Xamarin.Forms.Core/BindingExpression.cs
index 60303c00c9a..f3773268bed 100644
--- a/Xamarin.Forms.Core/BindingExpression.cs
+++ b/Xamarin.Forms.Core/BindingExpression.cs
@@ -148,7 +148,7 @@ void ApplyCore(object sourceObject, BindableObject target, BindableProperty prop
if (!TryConvert(ref value, property, property.ReturnType, true))
{
- Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, property.ReturnType);
+ Log.Warning("Binding", "'{0}' can not be converted to type '{1}'.", value, property.ReturnType);
return;
}
@@ -160,7 +160,7 @@ void ApplyCore(object sourceObject, BindableObject target, BindableProperty prop
if (!TryConvert(ref value, property, part.SetterType, false))
{
- Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, part.SetterType);
+ Log.Warning("Binding", "'{0}' can not be converted to type '{1}'.", value, part.SetterType);
return;
}
@@ -431,8 +431,12 @@ internal static bool TryConvert(ref object value, BindableProperty targetPropert
{
if (value == null)
return !convertTo.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(convertTo) != null;
- if ((toTarget && targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
- return true;
+ try {
+ if ((toTarget && targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
+ return true;
+ } catch (InvalidOperationException) { //that's what TypeConverters ususally throw
+ return false;
+ }
object original = value;
try {
@@ -455,7 +459,7 @@ internal static bool TryConvert(ref object value, BindableProperty targetPropert
value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture);
return true;
}
- catch (Exception ex) when (ex is InvalidCastException || ex is FormatException || ex is OverflowException) {
+ catch (Exception ex) when (ex is InvalidCastException || ex is FormatException || ex is InvalidOperationException || ex is OverflowException) {
value = original;
return false;
}
diff --git a/Xamarin.Forms.Core/ColumnDefinitionCollectionTypeConverter.cs b/Xamarin.Forms.Core/ColumnDefinitionCollectionTypeConverter.cs
new file mode 100644
index 00000000000..5bb1effad8b
--- /dev/null
+++ b/Xamarin.Forms.Core/ColumnDefinitionCollectionTypeConverter.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Xaml.TypeConversion(typeof(ColumnDefinitionCollection))]
+ public class ColumnDefinitionCollectionTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null) {
+ var lengths = value.Split(',');
+ var coldefs = new ColumnDefinitionCollection();
+ var converter = new GridLengthTypeConverter();
+ foreach (var length in lengths)
+ coldefs.Add(new ColumnDefinition { Width = (GridLength)converter.ConvertFromInvariantString(length) });
+ return coldefs;
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(ColumnDefinitionCollection)));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Crc64.cs b/Xamarin.Forms.Core/Crc64.cs
new file mode 100644
index 00000000000..82732221811
--- /dev/null
+++ b/Xamarin.Forms.Core/Crc64.cs
@@ -0,0 +1,224 @@
+#if !NETSTANDARD1_0
+
+using System;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
+
+///
+/// https://github.com/xamarin/java.interop/blob/master/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/Crc64.cs
+///
+namespace Xamarin.Forms.Internals
+{
+ ///
+ /// CRC64 variant: crc-64-jones 64-bit
+ /// * Poly: 0xad93d23594c935a9
+ /// Changes beyond initial implementation:
+ /// * Starting Value: ulong.MaxValue
+ /// * XOR length in HashFinal()
+ ///
+ internal class Crc64 : HashAlgorithm
+ {
+ static Crc64 _instance;
+ public static Crc64 Instance
+ {
+ get
+ {
+ if (_instance == null)
+ _instance = new Crc64();
+
+ return _instance;
+ }
+ }
+
+ Crc64() { }
+
+ static readonly ulong[] Table = {
+ 0x0000000000000000, 0x7ad870c830358979,
+ 0xf5b0e190606b12f2, 0x8f689158505e9b8b,
+ 0xc038e5739841b68f, 0xbae095bba8743ff6,
+ 0x358804e3f82aa47d, 0x4f50742bc81f2d04,
+ 0xab28ecb46814fe75, 0xd1f09c7c5821770c,
+ 0x5e980d24087fec87, 0x24407dec384a65fe,
+ 0x6b1009c7f05548fa, 0x11c8790fc060c183,
+ 0x9ea0e857903e5a08, 0xe478989fa00bd371,
+ 0x7d08ff3b88be6f81, 0x07d08ff3b88be6f8,
+ 0x88b81eabe8d57d73, 0xf2606e63d8e0f40a,
+ 0xbd301a4810ffd90e, 0xc7e86a8020ca5077,
+ 0x4880fbd87094cbfc, 0x32588b1040a14285,
+ 0xd620138fe0aa91f4, 0xacf86347d09f188d,
+ 0x2390f21f80c18306, 0x594882d7b0f40a7f,
+ 0x1618f6fc78eb277b, 0x6cc0863448deae02,
+ 0xe3a8176c18803589, 0x997067a428b5bcf0,
+ 0xfa11fe77117cdf02, 0x80c98ebf2149567b,
+ 0x0fa11fe77117cdf0, 0x75796f2f41224489,
+ 0x3a291b04893d698d, 0x40f16bccb908e0f4,
+ 0xcf99fa94e9567b7f, 0xb5418a5cd963f206,
+ 0x513912c379682177, 0x2be1620b495da80e,
+ 0xa489f35319033385, 0xde51839b2936bafc,
+ 0x9101f7b0e12997f8, 0xebd98778d11c1e81,
+ 0x64b116208142850a, 0x1e6966e8b1770c73,
+ 0x8719014c99c2b083, 0xfdc17184a9f739fa,
+ 0x72a9e0dcf9a9a271, 0x08719014c99c2b08,
+ 0x4721e43f0183060c, 0x3df994f731b68f75,
+ 0xb29105af61e814fe, 0xc849756751dd9d87,
+ 0x2c31edf8f1d64ef6, 0x56e99d30c1e3c78f,
+ 0xd9810c6891bd5c04, 0xa3597ca0a188d57d,
+ 0xec09088b6997f879, 0x96d1784359a27100,
+ 0x19b9e91b09fcea8b, 0x636199d339c963f2,
+ 0xdf7adabd7a6e2d6f, 0xa5a2aa754a5ba416,
+ 0x2aca3b2d1a053f9d, 0x50124be52a30b6e4,
+ 0x1f423fcee22f9be0, 0x659a4f06d21a1299,
+ 0xeaf2de5e82448912, 0x902aae96b271006b,
+ 0x74523609127ad31a, 0x0e8a46c1224f5a63,
+ 0x81e2d7997211c1e8, 0xfb3aa75142244891,
+ 0xb46ad37a8a3b6595, 0xceb2a3b2ba0eecec,
+ 0x41da32eaea507767, 0x3b024222da65fe1e,
+ 0xa2722586f2d042ee, 0xd8aa554ec2e5cb97,
+ 0x57c2c41692bb501c, 0x2d1ab4dea28ed965,
+ 0x624ac0f56a91f461, 0x1892b03d5aa47d18,
+ 0x97fa21650afae693, 0xed2251ad3acf6fea,
+ 0x095ac9329ac4bc9b, 0x7382b9faaaf135e2,
+ 0xfcea28a2faafae69, 0x8632586aca9a2710,
+ 0xc9622c4102850a14, 0xb3ba5c8932b0836d,
+ 0x3cd2cdd162ee18e6, 0x460abd1952db919f,
+ 0x256b24ca6b12f26d, 0x5fb354025b277b14,
+ 0xd0dbc55a0b79e09f, 0xaa03b5923b4c69e6,
+ 0xe553c1b9f35344e2, 0x9f8bb171c366cd9b,
+ 0x10e3202993385610, 0x6a3b50e1a30ddf69,
+ 0x8e43c87e03060c18, 0xf49bb8b633338561,
+ 0x7bf329ee636d1eea, 0x012b592653589793,
+ 0x4e7b2d0d9b47ba97, 0x34a35dc5ab7233ee,
+ 0xbbcbcc9dfb2ca865, 0xc113bc55cb19211c,
+ 0x5863dbf1e3ac9dec, 0x22bbab39d3991495,
+ 0xadd33a6183c78f1e, 0xd70b4aa9b3f20667,
+ 0x985b3e827bed2b63, 0xe2834e4a4bd8a21a,
+ 0x6debdf121b863991, 0x1733afda2bb3b0e8,
+ 0xf34b37458bb86399, 0x8993478dbb8deae0,
+ 0x06fbd6d5ebd3716b, 0x7c23a61ddbe6f812,
+ 0x3373d23613f9d516, 0x49aba2fe23cc5c6f,
+ 0xc6c333a67392c7e4, 0xbc1b436e43a74e9d,
+ 0x95ac9329ac4bc9b5, 0xef74e3e19c7e40cc,
+ 0x601c72b9cc20db47, 0x1ac40271fc15523e,
+ 0x5594765a340a7f3a, 0x2f4c0692043ff643,
+ 0xa02497ca54616dc8, 0xdafce7026454e4b1,
+ 0x3e847f9dc45f37c0, 0x445c0f55f46abeb9,
+ 0xcb349e0da4342532, 0xb1eceec59401ac4b,
+ 0xfebc9aee5c1e814f, 0x8464ea266c2b0836,
+ 0x0b0c7b7e3c7593bd, 0x71d40bb60c401ac4,
+ 0xe8a46c1224f5a634, 0x927c1cda14c02f4d,
+ 0x1d148d82449eb4c6, 0x67ccfd4a74ab3dbf,
+ 0x289c8961bcb410bb, 0x5244f9a98c8199c2,
+ 0xdd2c68f1dcdf0249, 0xa7f41839ecea8b30,
+ 0x438c80a64ce15841, 0x3954f06e7cd4d138,
+ 0xb63c61362c8a4ab3, 0xcce411fe1cbfc3ca,
+ 0x83b465d5d4a0eece, 0xf96c151de49567b7,
+ 0x76048445b4cbfc3c, 0x0cdcf48d84fe7545,
+ 0x6fbd6d5ebd3716b7, 0x15651d968d029fce,
+ 0x9a0d8ccedd5c0445, 0xe0d5fc06ed698d3c,
+ 0xaf85882d2576a038, 0xd55df8e515432941,
+ 0x5a3569bd451db2ca, 0x20ed197575283bb3,
+ 0xc49581ead523e8c2, 0xbe4df122e51661bb,
+ 0x3125607ab548fa30, 0x4bfd10b2857d7349,
+ 0x04ad64994d625e4d, 0x7e7514517d57d734,
+ 0xf11d85092d094cbf, 0x8bc5f5c11d3cc5c6,
+ 0x12b5926535897936, 0x686de2ad05bcf04f,
+ 0xe70573f555e26bc4, 0x9ddd033d65d7e2bd,
+ 0xd28d7716adc8cfb9, 0xa85507de9dfd46c0,
+ 0x273d9686cda3dd4b, 0x5de5e64efd965432,
+ 0xb99d7ed15d9d8743, 0xc3450e196da80e3a,
+ 0x4c2d9f413df695b1, 0x36f5ef890dc31cc8,
+ 0x79a59ba2c5dc31cc, 0x037deb6af5e9b8b5,
+ 0x8c157a32a5b7233e, 0xf6cd0afa9582aa47,
+ 0x4ad64994d625e4da, 0x300e395ce6106da3,
+ 0xbf66a804b64ef628, 0xc5bed8cc867b7f51,
+ 0x8aeeace74e645255, 0xf036dc2f7e51db2c,
+ 0x7f5e4d772e0f40a7, 0x05863dbf1e3ac9de,
+ 0xe1fea520be311aaf, 0x9b26d5e88e0493d6,
+ 0x144e44b0de5a085d, 0x6e963478ee6f8124,
+ 0x21c640532670ac20, 0x5b1e309b16452559,
+ 0xd476a1c3461bbed2, 0xaeaed10b762e37ab,
+ 0x37deb6af5e9b8b5b, 0x4d06c6676eae0222,
+ 0xc26e573f3ef099a9, 0xb8b627f70ec510d0,
+ 0xf7e653dcc6da3dd4, 0x8d3e2314f6efb4ad,
+ 0x0256b24ca6b12f26, 0x788ec2849684a65f,
+ 0x9cf65a1b368f752e, 0xe62e2ad306bafc57,
+ 0x6946bb8b56e467dc, 0x139ecb4366d1eea5,
+ 0x5ccebf68aecec3a1, 0x2616cfa09efb4ad8,
+ 0xa97e5ef8cea5d153, 0xd3a62e30fe90582a,
+ 0xb0c7b7e3c7593bd8, 0xca1fc72bf76cb2a1,
+ 0x45775673a732292a, 0x3faf26bb9707a053,
+ 0x70ff52905f188d57, 0x0a2722586f2d042e,
+ 0x854fb3003f739fa5, 0xff97c3c80f4616dc,
+ 0x1bef5b57af4dc5ad, 0x61372b9f9f784cd4,
+ 0xee5fbac7cf26d75f, 0x9487ca0fff135e26,
+ 0xdbd7be24370c7322, 0xa10fceec0739fa5b,
+ 0x2e675fb4576761d0, 0x54bf2f7c6752e8a9,
+ 0xcdcf48d84fe75459, 0xb71738107fd2dd20,
+ 0x387fa9482f8c46ab, 0x42a7d9801fb9cfd2,
+ 0x0df7adabd7a6e2d6, 0x772fdd63e7936baf,
+ 0xf8474c3bb7cdf024, 0x829f3cf387f8795d,
+ 0x66e7a46c27f3aa2c, 0x1c3fd4a417c62355,
+ 0x935745fc4798b8de, 0xe98f353477ad31a7,
+ 0xa6df411fbfb21ca3, 0xdc0731d78f8795da,
+ 0x536fa08fdfd90e51, 0x29b7d047efec8728,
+ };
+
+ ulong crc = ulong.MaxValue;
+ ulong length = 0;
+
+ public override void Initialize()
+ {
+ crc = ulong.MaxValue;
+ length = 0;
+ }
+
+ protected override void HashCore(byte[] array, int ibStart, int cbSize)
+ {
+ for (int i = ibStart; i < cbSize; i++)
+ {
+ crc = Table[(byte)(crc ^ array[i])] ^ (crc >> 8);
+ }
+ length += (ulong)cbSize;
+ }
+
+ protected override byte[] HashFinal() => BitConverter.GetBytes(crc ^ length);
+
+
+
+ // https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/24343727#24343727
+ static readonly uint[] _lookup32 = CreateLookup32();
+
+ static uint[] CreateLookup32()
+ {
+ var result = new uint[256];
+ for (int i = 0; i < 256; i++)
+ {
+ string s = i.ToString("X2");
+ result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
+ }
+ return result;
+ }
+
+ static string ByteArrayToHexViaLookup32(byte[] bytes)
+ {
+ var lookup32 = _lookup32;
+ var result = new char[bytes.Length * 2];
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ var val = lookup32[bytes[i]];
+ result[2 * i] = (char)val;
+ result[2 * i + 1] = (char)(val >> 16);
+ }
+ return new string(result);
+ }
+
+ public static string GetHash(string input)
+ {
+ byte[] bytes = Instance.ComputeHash(Encoding.UTF8.GetBytes(input));
+ return ByteArrayToHexViaLookup32(bytes);
+ }
+
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/DependencyService.cs b/Xamarin.Forms.Core/DependencyService.cs
index 658162bf4ce..7c609e226fc 100644
--- a/Xamarin.Forms.Core/DependencyService.cs
+++ b/Xamarin.Forms.Core/DependencyService.cs
@@ -76,6 +76,17 @@ public static void Register() where T : class where TImpl : class, T
DependencyImplementations[targetType] = new DependencyData { ImplementorType = implementorType };
}
+ public static void RegisterSingleton(T instance) where T : class
+ {
+ Type targetType = typeof(T);
+ Type implementorType = typeof(T);
+ if (!DependencyTypes.Contains(targetType))
+ DependencyTypes.Add(targetType);
+
+ lock (s_dependencyLock)
+ DependencyImplementations[targetType] = new DependencyData { ImplementorType = implementorType, GlobalInstance = instance };
+ }
+
static Type FindImplementor(Type target) =>
DependencyTypes.FirstOrDefault(t => target.IsAssignableFrom(t));
diff --git a/Xamarin.Forms.Core/DoubleCollection.cs b/Xamarin.Forms.Core/DoubleCollection.cs
new file mode 100644
index 00000000000..b054e63c058
--- /dev/null
+++ b/Xamarin.Forms.Core/DoubleCollection.cs
@@ -0,0 +1,10 @@
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms
+{
+ [TypeConverter(typeof(DoubleCollectionConverter))]
+ public sealed class DoubleCollection : ObservableCollection
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/DoubleCollectionConverter.cs b/Xamarin.Forms.Core/DoubleCollectionConverter.cs
new file mode 100644
index 00000000000..31c0131067d
--- /dev/null
+++ b/Xamarin.Forms.Core/DoubleCollectionConverter.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public class DoubleCollectionConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ string[] doubles = value.Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ var doubleCollection = new DoubleCollection();
+
+ foreach (string d in doubles)
+ {
+ if (double.TryParse(d, NumberStyles.Number, CultureInfo.InvariantCulture, out double number))
+ doubleCollection.Add(number);
+ else
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", d, typeof(double)));
+ }
+
+ return doubleCollection;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Element.cs b/Xamarin.Forms.Core/Element.cs
index ab4be824bc5..721ede7d2cd 100644
--- a/Xamarin.Forms.Core/Element.cs
+++ b/Xamarin.Forms.Core/Element.cs
@@ -344,7 +344,7 @@ protected virtual void OnChildRemoved(Element child)
protected virtual void OnParentSet()
{
ParentSet?.Invoke(this, EventArgs.Empty);
- ApplyStyleSheetsOnParentSet();
+ ApplyStyleSheets();
(this as IPropertyPropagationController)?.PropagatePropertyChanged(null);
}
@@ -388,7 +388,7 @@ public IEnumerable Descendants()
internal virtual void OnParentResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
if (e == ResourcesChangedEventArgs.StyleSheets)
- ApplyStyleSheetsOnParentSet();
+ ApplyStyleSheets();
else
OnParentResourcesChanged(e.Values);
}
@@ -409,7 +409,10 @@ internal override void OnRemoveDynamicResource(BindableProperty property)
internal virtual void OnResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
- OnResourcesChanged(e.Values);
+ if (e == ResourcesChangedEventArgs.StyleSheets)
+ ApplyStyleSheets();
+ else
+ OnResourcesChanged(e.Values);
}
internal void OnResourcesChanged(IEnumerable> values)
diff --git a/Xamarin.Forms.Core/Element_StyleSheets.cs b/Xamarin.Forms.Core/Element_StyleSheets.cs
index 690fb47bc9c..a4676abd0e6 100644
--- a/Xamarin.Forms.Core/Element_StyleSheets.cs
+++ b/Xamarin.Forms.Core/Element_StyleSheets.cs
@@ -1,8 +1,6 @@
using System.Collections.Generic;
-using System.IO;
+using System.Linq;
using System.Reflection;
-
-using Xamarin.Forms.Internals;
using Xamarin.Forms.StyleSheets;
namespace Xamarin.Forms
@@ -18,14 +16,18 @@ public partial class Element : IStyleSelectable
internal string _cssFallbackTypeName;
string[] _styleSelectableNameAndBaseNames;
- string[] IStyleSelectable.NameAndBases {
- get {
- if (_styleSelectableNameAndBaseNames == null) {
+ string[] IStyleSelectable.NameAndBases
+ {
+ get
+ {
+ if (_styleSelectableNameAndBaseNames == null)
+ {
var list = new List();
if (_cssFallbackTypeName != null)
list.Add(_cssFallbackTypeName);
var t = GetType();
- while (t != typeof(BindableObject)) {
+ while (t != typeof(BindableObject))
+ {
list.Add(t.Name);
t = t.GetTypeInfo().BaseType;
}
@@ -38,21 +40,44 @@ string[] IStyleSelectable.NameAndBases {
IStyleSelectable IStyleSelectable.Parent => Parent;
//on parent set, or on parent stylesheet changed, reapply all
- internal void ApplyStyleSheetsOnParentSet()
+ internal void ApplyStyleSheets()
{
- var parent = Parent;
- if (parent == null)
- return;
var sheets = new List();
- while (parent != null) {
+ Element parent = this;
+ while (parent != null)
+ {
var resourceProvider = parent as IResourcesProvider;
var vpSheets = resourceProvider?.GetStyleSheets();
if (vpSheets != null)
sheets.AddRange(vpSheets);
parent = parent.Parent;
}
- for (var i = sheets.Count - 1; i >= 0; i--)
- ((IStyle)sheets[i]).Apply(this);
+
+ ApplyStyleSheets(sheets, this);
+ }
+
+ void ApplyStyleSheets(List sheets, Element element)
+ {
+ if (element == null)
+ return;
+
+ for (var i = (sheets?.Count ?? 0) - 1; i >= 0; i--)
+ {
+ ((IStyle)sheets[i]).Apply(element);
+ }
+
+ foreach (Element child in element.AllChildren)
+ {
+ var mergedSheets = sheets;
+ var resourceProvider = child as IResourcesProvider;
+ var childSheets = resourceProvider?.GetStyleSheets();
+ if (childSheets?.Any() ?? false)
+ {
+ mergedSheets = new List(childSheets);
+ mergedSheets.AddRange(sheets);
+ }
+ ApplyStyleSheets(mergedSheets, child);
+ }
}
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/ExperimentalFlags.cs b/Xamarin.Forms.Core/ExperimentalFlags.cs
index d68bd5ae1a1..4ed09c518b1 100644
--- a/Xamarin.Forms.Core/ExperimentalFlags.cs
+++ b/Xamarin.Forms.Core/ExperimentalFlags.cs
@@ -9,8 +9,6 @@ namespace Xamarin.Forms
{
internal static class ExperimentalFlags
{
- internal const string StateTriggersExperimental = "StateTriggers_Experimental";
- internal const string IndicatorViewExperimental = "IndicatorView_Experimental";
internal const string ShellUWPExperimental = "Shell_UWP_Experimental";
internal const string CarouselViewExperimental = "CarouselView_Experimental";
internal const string SwipeViewExperimental = "SwipeView_Experimental";
@@ -19,6 +17,7 @@ internal static class ExperimentalFlags
internal const string AppThemeExperimental = "AppTheme_Experimental";
internal const string ExpanderExperimental = "Expander_Experimental";
internal const string RadioButtonExperimental = "RadioButton_Experimental";
+ internal const string ShapesExperimental = "Shapes_Experimental";
[EditorBrowsable(EditorBrowsableState.Never)]
public static void VerifyFlagEnabled(
diff --git a/Xamarin.Forms.Core/Grid.cs b/Xamarin.Forms.Core/Grid.cs
index 0af641afd22..27f8d003381 100644
--- a/Xamarin.Forms.Core/Grid.cs
+++ b/Xamarin.Forms.Core/Grid.cs
@@ -71,6 +71,7 @@ public IPlatformElementConfiguration On() where T : IConfigPlatform
get { return _children; }
}
+ [TypeConverter(typeof(ColumnDefinitionCollectionTypeConverter))]
public ColumnDefinitionCollection ColumnDefinitions
{
get { return (ColumnDefinitionCollection)GetValue(ColumnDefinitionsProperty); }
@@ -83,6 +84,7 @@ public double ColumnSpacing
set { SetValue(ColumnSpacingProperty, value); }
}
+ [TypeConverter(typeof(RowDefinitionCollectionTypeConverter))]
public RowDefinitionCollection RowDefinitions
{
get { return (RowDefinitionCollection)GetValue(RowDefinitionsProperty); }
diff --git a/Xamarin.Forms.Core/IMultiValueConverter.cs b/Xamarin.Forms.Core/IMultiValueConverter.cs
new file mode 100644
index 00000000000..6ae634f8851
--- /dev/null
+++ b/Xamarin.Forms.Core/IMultiValueConverter.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace Xamarin.Forms
+{
+ //
+ // (For Documentation Authors)
+ // Results of different possible return values from Convert and ConvertBack:
+ //
+ // | Binding.DoNothing Binding.UnsetValue null
+ // ----------------------------------------------------------------------------------------------------------
+ // Convert: | No update MultiBinding.FallbackValue MultiBinding.TargetNullValue
+ // ConvertBack: | No update to source[i] No update at all No update at all
+ // (at position i)
+ //
+ public interface IMultiValueConverter
+ {
+ object Convert(object[] values, Type targetType, object parameter, CultureInfo culture);
+
+ object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture);
+ }
+}
diff --git a/Xamarin.Forms.Core/IPlatformServices.cs b/Xamarin.Forms.Core/IPlatformServices.cs
index be9ba12852f..b0d161bfd1f 100644
--- a/Xamarin.Forms.Core/IPlatformServices.cs
+++ b/Xamarin.Forms.Core/IPlatformServices.cs
@@ -19,6 +19,9 @@ public interface IPlatformServices
Assembly[] GetAssemblies();
+ string GetHash(string input);
+
+ [Obsolete("GetMD5Hash is obsolete as of version 4.7.0")]
string GetMD5Hash(string input);
double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes);
diff --git a/Xamarin.Forms.Core/IndicatorView.cs b/Xamarin.Forms.Core/IndicatorView.cs
index 95b491a4a91..25aa923d247 100644
--- a/Xamarin.Forms.Core/IndicatorView.cs
+++ b/Xamarin.Forms.Core/IndicatorView.cs
@@ -39,7 +39,7 @@ public class IndicatorView : TemplatedView
public IndicatorView()
{
- ExperimentalFlags.VerifyFlagEnabled(nameof(IndicatorView), ExperimentalFlags.IndicatorViewExperimental);
+
}
public IndicatorShape IndicatorsShape
diff --git a/Xamarin.Forms.Core/MergedStyle.cs b/Xamarin.Forms.Core/MergedStyle.cs
index b294e3fdf49..85e6970fdbd 100644
--- a/Xamarin.Forms.Core/MergedStyle.cs
+++ b/Xamarin.Forms.Core/MergedStyle.cs
@@ -70,7 +70,7 @@ public IList StyleClass
//reapply the css stylesheets
if (Target is Element targetelement)
- targetelement.ApplyStyleSheetsOnParentSet();
+ targetelement.ApplyStyleSheets();
}
}
}
diff --git a/Xamarin.Forms.Core/MultiBinding.cs b/Xamarin.Forms.Core/MultiBinding.cs
new file mode 100644
index 00000000000..13f6214c43b
--- /dev/null
+++ b/Xamarin.Forms.Core/MultiBinding.cs
@@ -0,0 +1,225 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty(nameof(Bindings))]
+ public sealed class MultiBinding : BindingBase
+ {
+ IMultiValueConverter _converter;
+ object _converterParameter;
+ IList _bindings;
+ BindableProperty _targetProperty;
+ BindableObject _targetObject;
+ BindableObject _proxyObject;
+ BindableProperty[] _bpProxies;
+ bool _applying;
+
+ public IMultiValueConverter Converter
+ {
+ get { return _converter; }
+ set
+ {
+ ThrowIfApplied();
+ _converter = value;
+ }
+ }
+
+ public object ConverterParameter
+ {
+ get { return _converterParameter; }
+ set
+ {
+ ThrowIfApplied();
+ _converterParameter = value;
+ }
+ }
+
+ public IList Bindings
+ {
+ get => _bindings ?? (_bindings = new List());
+ set
+ {
+ ThrowIfApplied();
+ _bindings = value;
+ }
+ }
+
+ internal override BindingBase Clone()
+ {
+ var bindingsclone = new List(Bindings.Count);
+ foreach (var b in Bindings)
+ bindingsclone.Add(b.Clone());
+
+ return new MultiBinding() {
+ Converter = Converter,
+ ConverterParameter = ConverterParameter,
+ Bindings = bindingsclone,
+ FallbackValue = FallbackValue,
+ Mode = Mode,
+ TargetNullValue = TargetNullValue,
+ StringFormat = StringFormat,
+ };
+ }
+
+ internal override void Apply(bool fromTarget)
+ {
+ if (_applying)
+ return;
+
+ base.Apply(fromTarget);
+
+ if (this.GetRealizedMode(_targetProperty) == BindingMode.OneTime)
+ return;
+
+ if (fromTarget && this.GetRealizedMode(_targetProperty) == BindingMode.OneWay)
+ return;
+
+ if (!fromTarget && this.GetRealizedMode(_targetProperty) == BindingMode.OneWayToSource)
+ return;
+
+ if (!fromTarget)
+ {
+ var value = GetSourceValue(GetValueArray(), _targetProperty.ReturnType);
+ if (value != Binding.DoNothing) {
+ _applying = true;
+ if (!BindingExpression.TryConvert(ref value, _targetProperty, _targetProperty.ReturnType, true))
+ {
+ Log.Warning("MultiBinding", "'{0}' can not be converted to type '{1}'.", value, _targetProperty.ReturnType);
+ return;
+ }
+ _targetObject.SetValueCore(_targetProperty, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted);
+ _applying = false;
+ }
+ }
+ else
+ {
+ try {
+ _applying = true;
+
+ //https://docs.microsoft.com/en-us/dotnet/api/system.windows.data.imultivalueconverter.convertback?view=netframework-4.8#remarks
+ if (!(GetTargetValue(_targetObject.GetValue(_targetProperty), null) is object[] values)) //converter failed
+ return;
+ for (var i = 0; i < Math.Min(_bpProxies.Length, values.Length); i++)
+ {
+ if (ReferenceEquals(values[i], Binding.DoNothing) || ReferenceEquals(values[i], BindableProperty.UnsetValue))
+ continue;
+ _proxyObject.SetValueCore(_bpProxies[i], values[i], SetValueFlags.None);
+ }
+ }
+ finally {
+ _applying = false;
+ }
+
+ }
+ }
+
+ internal override void Apply(object context, BindableObject targetObject, BindableProperty targetProperty, bool fromBindingContextChanged = false)
+ {
+ if (_bindings == null)
+ throw new InvalidOperationException("Bindings is null");
+
+ if (Converter == null && StringFormat == null)
+ throw new InvalidOperationException("Cannot apply MultiBinding because both Converter and StringFormat are null.");
+
+ base.Apply(context, targetObject, targetProperty, fromBindingContextChanged);
+
+ if (!ReferenceEquals(_targetObject, targetObject)) {
+ _targetObject = targetObject;
+ _proxyObject = new ProxyElement() { Parent = targetObject as Element };
+ _targetProperty = targetProperty;
+
+ if (_bpProxies == null) {
+ _bpProxies = new BindableProperty[Bindings.Count];
+ _applying = true;
+ var bindingMode = Mode == BindingMode.Default ? targetProperty.DefaultBindingMode : Mode;
+ for (var i = 0; i < Bindings.Count; i++) {
+ var binding = Bindings[i];
+ binding.RelativeSourceTargetOverride = targetObject as Element;
+ var bp = _bpProxies[i] = BindableProperty.Create($"mb-proxy{i}", typeof(object), typeof(MultiBinding), null, bindingMode, propertyChanged: OnBindingChanged);
+ _proxyObject.SetBinding(bp, binding);
+ }
+ _applying = false;
+ }
+ }
+ _proxyObject.BindingContext = context;
+
+ if (this.GetRealizedMode(_targetProperty) == BindingMode.OneWayToSource)
+ return;
+
+ var value = GetSourceValue(GetValueArray(), _targetProperty.ReturnType);
+ if (value != Binding.DoNothing) {
+ _applying = true;
+ if (!BindingExpression.TryConvert(ref value, _targetProperty, _targetProperty.ReturnType, true))
+ {
+ Log.Warning("MultiBinding", "'{0}' can not be converted to type '{1}'.", value, _targetProperty.ReturnType);
+ return;
+ }
+ _targetObject.SetValueCore(_targetProperty, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted);
+ _applying = false;
+ }
+ }
+
+ class ProxyElement : Element
+ {
+ }
+
+ object[] GetValueArray()
+ {
+ var valuearray = new object[_bpProxies.Length];
+ for (var i = 0; i < _bpProxies.Length; i++)
+ valuearray[i] = _proxyObject.GetValue(_bpProxies[i]);
+ return valuearray;
+ }
+
+ internal override object GetSourceValue(object value, Type targetPropertyType)
+ {
+ var valuearray = value as object[];
+ if (valuearray != null && Converter != null)
+ value = Converter.Convert(valuearray, targetPropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
+
+ if (valuearray != null && StringFormat != null && TryFormat(StringFormat, valuearray, out var formatted))
+ return formatted;
+
+ if (ReferenceEquals(BindableProperty.UnsetValue, value))
+ return FallbackValue;
+
+ return base.GetSourceValue(value, targetPropertyType);
+ }
+
+ internal override object GetTargetValue(object value, Type sourcePropertyType)
+ {
+ if (Converter != null) {
+ var values = GetValueArray();
+ var types = new Type[_bpProxies.Length];
+ for (var i = 0; i < _bpProxies.Length; i++)
+ types[i] = values[i]?.GetType() ?? typeof(object);
+ return Converter.ConvertBack(value, types, ConverterParameter, CultureInfo.CurrentUICulture);
+ }
+
+ return base.GetTargetValue(value, sourcePropertyType);
+ }
+
+ void OnBindingChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (!_applying)
+ Apply(fromTarget: false);
+ }
+
+ internal override void Unapply(bool fromBindingContextChanged = false)
+ {
+ if (!fromBindingContextChanged) {
+ if (_bpProxies != null && _proxyObject != null)
+ foreach (var proxybp in _bpProxies)
+ _proxyObject.RemoveBinding(proxybp);
+
+ _bpProxies = null;
+ _proxyObject = null;
+ }
+
+ base.Unapply(fromBindingContextChanged: fromBindingContextChanged);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core/ObservableWrapper.cs b/Xamarin.Forms.Core/ObservableWrapper.cs
index 3b31a0f888e..2dff148bbac 100644
--- a/Xamarin.Forms.Core/ObservableWrapper.cs
+++ b/Xamarin.Forms.Core/ObservableWrapper.cs
@@ -7,7 +7,7 @@
namespace Xamarin.Forms
{
- internal class ObservableWrapper : IList, INotifyCollectionChanged where TTrack : Element where TRestrict : TTrack
+ internal class ObservableWrapper : IList, IList, INotifyCollectionChanged where TTrack : Element where TRestrict : TTrack
{
readonly ObservableCollection _list;
@@ -263,5 +263,52 @@ int ToOuterIndex(int innerIndex)
return outerIndex;
}
+
+ #region IList
+ public int Add(object value)
+ {
+ Add((TRestrict)value);
+ return IndexOf(value);
+ }
+
+ public bool Contains(object value)
+ {
+ return Contains((TRestrict)value);
+ }
+
+ public int IndexOf(object value)
+ {
+ return IndexOf((TRestrict)value);
+ }
+
+ public void Insert(int index, object value)
+ {
+ Insert(index, (TRestrict)value);
+ }
+
+ public void Remove(object value)
+ {
+ Remove((TRestrict)value);
+ }
+
+ public void CopyTo(Array array, int index)
+ {
+ CopyTo(array.Cast().ToArray(), index);
+ }
+
+ public bool IsFixedSize => ((IList)_list).IsFixedSize;
+
+ public bool IsSynchronized => ((IList)_list).IsSynchronized;
+
+ public object SyncRoot => ((IList)_list).SyncRoot;
+
+ object IList.this[int index]
+ {
+ get => (this as IList)[index];
+ set => (this as IList)[index] = (TRestrict)value;
+ }
+
+ #endregion
+
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Profiler.cs b/Xamarin.Forms.Core/Profiler.cs
index f00a45b66b1..38b87df5506 100644
--- a/Xamarin.Forms.Core/Profiler.cs
+++ b/Xamarin.Forms.Core/Profiler.cs
@@ -2,13 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
-using System.IO;
-using System.Reflection;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Internals
{
@@ -26,24 +20,45 @@ public struct Datum
public int Depth;
public int Line;
}
- public static List Data = new List(Capacity);
+ public static List Data;
- static Stack Stack = new Stack(Capacity);
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static bool IsEnabled { get; private set; } = false;
+
+ static Stack Stack;
static int Depth = 0;
static bool Running = false;
- static Stopwatch Stopwatch = new Stopwatch();
+ static Stopwatch Stopwatch;
readonly long _start;
readonly string _name;
readonly int _slot;
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void Enable()
+ {
+ if (!IsEnabled)
+ {
+ IsEnabled = true;
+ Data = new List(Capacity);
+ Stack = new Stack(Capacity);
+ Stopwatch = new Stopwatch();
+ }
+ }
+
public static void Start()
{
+ if (!IsEnabled)
+ return;
+
Running = true;
}
public static void Stop()
{
+ if (!IsEnabled)
+ return;
+
// unwind stack
Running = false;
while (Stack.Count > 0)
@@ -54,7 +69,7 @@ public static void FrameBegin(
[CallerMemberName] string name = "",
[CallerLineNumber] int line = 0)
{
- if (!Running)
+ if (!IsEnabled || !Running)
return;
FrameBeginBody(name, null, line);
@@ -63,7 +78,7 @@ public static void FrameBegin(
public static void FrameEnd(
[CallerMemberName] string name = "")
{
- if (!Running)
+ if (!IsEnabled || !Running)
return;
FrameEndBody(name);
@@ -73,7 +88,7 @@ public static void FramePartition(
string id,
[CallerLineNumber] int line = 0)
{
- if (!Running)
+ if (!IsEnabled || !Running)
return;
FramePartitionBody(id, line);
@@ -135,6 +150,8 @@ static void FramePartitionBody(
public void Dispose()
{
+ if (!IsEnabled)
+ return;
if (Running && _start == 0)
return;
diff --git a/Xamarin.Forms.Core/Properties/AssemblyInfo.cs b/Xamarin.Forms.Core/Properties/AssemblyInfo.cs
index 4c184f301e0..983dd4f00fc 100644
--- a/Xamarin.Forms.Core/Properties/AssemblyInfo.cs
+++ b/Xamarin.Forms.Core/Properties/AssemblyInfo.cs
@@ -36,7 +36,9 @@
[assembly: InternalsVisibleTo("Xamarin.Forms.DualScreen.UnitTests")]
[assembly: Preserve]
+[assembly: XmlnsDefinition("http://xamarin.com/schemas/2014/forms", "Xamarin.Forms.Shapes")]
[assembly: XmlnsDefinition("http://xamarin.com/schemas/2014/forms", "Xamarin.Forms")]
+[assembly: XmlnsDefinition("http://xamarin.com/schemas/2014/forms/design", "Xamarin.Forms.Shapes")]
[assembly: XmlnsDefinition("http://xamarin.com/schemas/2014/forms/design", "Xamarin.Forms")]
[assembly: XmlnsPrefix("http://xamarin.com/schemas/2014/forms", "xf")]
[assembly: XmlnsPrefix("http://xamarin.com/schemas/2014/forms/design", "d")]
diff --git a/Xamarin.Forms.Core/Registrar.cs b/Xamarin.Forms.Core/Registrar.cs
index dd8eafc2954..0d051f4c9f1 100644
--- a/Xamarin.Forms.Core/Registrar.cs
+++ b/Xamarin.Forms.Core/Registrar.cs
@@ -254,7 +254,10 @@ static Registrar()
}
internal static Dictionary Effects { get; } = new Dictionary();
- internal static Dictionary> StyleProperties { get; } = new Dictionary>();
+ internal static Dictionary> StyleProperties => LazyStyleProperties.Value;
+
+ static bool DisableCSS = false;
+ static readonly Lazy>> LazyStyleProperties = new Lazy>>(LoadStyleSheets);
public static IEnumerable ExtraAssemblies { get; set; }
@@ -275,24 +278,29 @@ public static void RegisterRenderers(HandlerAttribute[] attributes)
}
}
- public static void RegisterStylesheets()
+ public static void RegisterStylesheets(InitializationFlags flags)
{
- var assembly = typeof(StylePropertyAttribute).GetTypeInfo().Assembly;
+ if ((flags & InitializationFlags.DisableCss) == InitializationFlags.DisableCss)
+ DisableCSS = true;
+ }
-#if NETSTANDARD2_0
- object[] styleAttributes = assembly.GetCustomAttributes(typeof(StylePropertyAttribute), true);
-#else
- object[] styleAttributes = assembly.GetCustomAttributes(typeof(StyleSheets.StylePropertyAttribute)).ToArray();
-#endif
- var stylePropertiesLength = styleAttributes.Length;
+ static Dictionary> LoadStyleSheets()
+ {
+ var properties = new Dictionary>();
+ if (DisableCSS)
+ return properties;
+ var assembly = typeof(StylePropertyAttribute).GetTypeInfo().Assembly;
+ var styleAttributes = assembly.GetCustomAttributesSafe(typeof(StylePropertyAttribute));
+ var stylePropertiesLength = styleAttributes?.Length ?? 0;
for (var i = 0; i < stylePropertiesLength; i++)
{
var attribute = (StylePropertyAttribute)styleAttributes[i];
- if (StyleProperties.TryGetValue(attribute.CssPropertyName, out var attrList))
+ if (properties.TryGetValue(attribute.CssPropertyName, out var attrList))
attrList.Add(attribute);
else
- StyleProperties[attribute.CssPropertyName] = new List { attribute };
+ properties[attribute.CssPropertyName] = new List { attribute };
}
+ return properties;
}
public static void RegisterEffects(string resolutionName, ExportEffectAttribute[] effectAttributes)
@@ -332,8 +340,8 @@ public static void RegisterAll(Type[] attrTypes, InitializationFlags flags)
Profile.FramePartition("Reflect");
foreach (Assembly assembly in assemblies)
{
- var assemblyName = assembly.GetName().Name;
- Profile.FrameBegin(assemblyName);
+ string frameName = Profile.IsEnabled ? assembly.GetName().Name : "Assembly";
+ Profile.FrameBegin(frameName);
foreach (Type attrType in attrTypes)
{
@@ -361,7 +369,7 @@ public static void RegisterAll(Type[] attrTypes, InitializationFlags flags)
object[] effectAttributes = assembly.GetCustomAttributesSafe(typeof (ExportEffectAttribute));
if (effectAttributes == null || effectAttributes.Length == 0)
{
- Profile.FrameEnd(assemblyName);
+ Profile.FrameEnd(frameName);
continue;
}
@@ -374,12 +382,10 @@ public static void RegisterAll(Type[] attrTypes, InitializationFlags flags)
Array.Copy(effectAttributes, typedEffectAttributes, effectAttributes.Length);
RegisterEffects(resolutionName, typedEffectAttributes);
- Profile.FrameEnd(assemblyName);
+ Profile.FrameEnd(frameName);
}
- if ((flags & InitializationFlags.DisableCss) == 0)
- RegisterStylesheets();
-
+ RegisterStylesheets(flags);
Profile.FramePartition("DependencyService.Initialize");
DependencyService.Initialize(assemblies);
diff --git a/Xamarin.Forms.Core/ResourceDictionary.cs b/Xamarin.Forms.Core/ResourceDictionary.cs
index 230ba86caa1..f96f60e2954 100644
--- a/Xamarin.Forms.Core/ResourceDictionary.cs
+++ b/Xamarin.Forms.Core/ResourceDictionary.cs
@@ -201,7 +201,7 @@ public object this[string index]
return _innerDictionary[index];
if (_mergedInstance != null && _mergedInstance.ContainsKey(index))
return _mergedInstance[index];
- if (MergedDictionaries != null)
+ if (_mergedDictionaries != null)
foreach (var dict in MergedDictionaries.Reverse())
if (dict.ContainsKey(index))
return dict[index];
@@ -241,7 +241,7 @@ public IEnumerator> GetEnumerator()
internal IEnumerable> MergedResources {
get {
- if (MergedDictionaries != null)
+ if (_mergedDictionaries != null)
{
for (int i = _mergedDictionaries.Count - 1; i >= 0; i--)
{
@@ -267,7 +267,7 @@ internal bool TryGetValueAndSource(string key, out object value, out ResourceDic
source = this;
return _innerDictionary.TryGetValue(key, out value)
|| (_mergedInstance != null && _mergedInstance.TryGetValueAndSource(key, out value, out source))
- || (MergedDictionaries != null && TryGetMergedDictionaryValue(key, out value, out source));
+ || (_mergedDictionaries != null && TryGetMergedDictionaryValue(key, out value, out source));
}
bool TryGetMergedDictionaryValue(string key, out object value, out ResourceDictionary source)
diff --git a/Xamarin.Forms.Core/RowDefinitionCollectionTypeConverter.cs b/Xamarin.Forms.Core/RowDefinitionCollectionTypeConverter.cs
new file mode 100644
index 00000000000..cd012411b74
--- /dev/null
+++ b/Xamarin.Forms.Core/RowDefinitionCollectionTypeConverter.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Xaml.TypeConversion(typeof(RowDefinitionCollection))]
+ public class RowDefinitionCollectionTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null) {
+ var lengths = value.Split(',');
+ var coldefs = new RowDefinitionCollection();
+ var converter = new GridLengthTypeConverter();
+ foreach (var length in lengths)
+ coldefs.Add(new RowDefinition { Height = (GridLength)converter.ConvertFromInvariantString(length) });
+ return coldefs;
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(RowDefinitionCollection)));
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/ArcSegment.cs b/Xamarin.Forms.Core/Shapes/ArcSegment.cs
new file mode 100644
index 00000000000..9f98c8413d9
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/ArcSegment.cs
@@ -0,0 +1,65 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class ArcSegment : PathSegment
+ {
+ public ArcSegment()
+ {
+
+ }
+
+ public ArcSegment(Point point, Size size, double rotationAngle, SweepDirection sweepDirection, bool isLargeArc)
+ {
+ Point = point;
+ Size = size;
+ RotationAngle = rotationAngle;
+ SweepDirection = sweepDirection;
+ IsLargeArc = isLargeArc;
+ }
+
+ public static readonly BindableProperty PointProperty =
+ BindableProperty.Create(nameof(Point), typeof(Point), typeof(ArcSegment), new Point(0, 0));
+
+ public static readonly BindableProperty SizeProperty =
+ BindableProperty.Create(nameof(Size), typeof(Size), typeof(ArcSegment), new Size(0, 0));
+
+ public static readonly BindableProperty RotationAngleProperty =
+ BindableProperty.Create(nameof(RotationAngle), typeof(double), typeof(ArcSegment), 0.0);
+
+ public static readonly BindableProperty SweepDirectionProperty =
+ BindableProperty.Create(nameof(SweepDirection), typeof(SweepDirection), typeof(ArcSegment), SweepDirection.CounterClockwise);
+
+ public static readonly BindableProperty IsLargeArcProperty =
+ BindableProperty.Create(nameof(IsLargeArc), typeof(bool), typeof(ArcSegment), false);
+
+ public Point Point
+ {
+ set { SetValue(PointProperty, value); }
+ get { return (Point)GetValue(PointProperty); }
+ }
+
+ [TypeConverter(typeof(SizeTypeConverter))]
+ public Size Size
+ {
+ set { SetValue(SizeProperty, value); }
+ get { return (Size)GetValue(SizeProperty); }
+ }
+
+ public double RotationAngle
+ {
+ set { SetValue(RotationAngleProperty, value); }
+ get { return (double)GetValue(RotationAngleProperty); }
+ }
+
+ public SweepDirection SweepDirection
+ {
+ set { SetValue(SweepDirectionProperty, value); }
+ get { return (SweepDirection)GetValue(SweepDirectionProperty); }
+ }
+
+ public bool IsLargeArc
+ {
+ set { SetValue(IsLargeArcProperty, value); }
+ get { return (bool)GetValue(IsLargeArcProperty); }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core/Shapes/BezierSegment.cs b/Xamarin.Forms.Core/Shapes/BezierSegment.cs
new file mode 100644
index 00000000000..81f545fd298
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/BezierSegment.cs
@@ -0,0 +1,44 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class BezierSegment : PathSegment
+ {
+ public BezierSegment()
+ {
+
+ }
+
+ public BezierSegment(Point point1, Point point2, Point point3)
+ {
+ Point1 = point1;
+ Point2 = point2;
+ Point3 = point3;
+ }
+
+ public static readonly BindableProperty Point1Property =
+ BindableProperty.Create(nameof(Point1), typeof(Point), typeof(BezierSegment), new Point(0, 0));
+
+ public static readonly BindableProperty Point2Property =
+ BindableProperty.Create(nameof(Point2), typeof(Point), typeof(BezierSegment), new Point(0, 0));
+
+ public static readonly BindableProperty Point3Property =
+ BindableProperty.Create(nameof(Point3), typeof(Point), typeof(BezierSegment), new Point(0, 0));
+
+ public Point Point1
+ {
+ set { SetValue(Point1Property, value); }
+ get { return (Point)GetValue(Point1Property); }
+ }
+
+ public Point Point2
+ {
+ set { SetValue(Point2Property, value); }
+ get { return (Point)GetValue(Point2Property); }
+ }
+
+ public Point Point3
+ {
+ set { SetValue(Point3Property, value); }
+ get { return (Point)GetValue(Point3Property); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/CompositeTransform.cs b/Xamarin.Forms.Core/Shapes/CompositeTransform.cs
new file mode 100644
index 00000000000..2a2ac6c645f
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/CompositeTransform.cs
@@ -0,0 +1,140 @@
+namespace Xamarin.Forms.Shapes
+{
+ public sealed class CompositeTransform : Transform
+ {
+ public static readonly BindableProperty CenterXProperty =
+ BindableProperty.Create(nameof(CenterX), typeof(double), typeof(CompositeTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty CenterYProperty =
+ BindableProperty.Create(nameof(CenterY), typeof(double), typeof(CompositeTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty ScaleXProperty =
+ BindableProperty.Create(nameof(ScaleX), typeof(double), typeof(CompositeTransform), 1.0, propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty ScaleYProperty =
+ BindableProperty.Create(nameof(ScaleY), typeof(double), typeof(CompositeTransform), 1.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty SkewXProperty =
+ BindableProperty.Create(nameof(SkewX), typeof(double), typeof(CompositeTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty SkewYProperty =
+ BindableProperty.Create(nameof(SkewY), typeof(double), typeof(CompositeTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty RotationProperty =
+ BindableProperty.Create(nameof(Rotation), typeof(double), typeof(CompositeTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty TranslateXProperty =
+ BindableProperty.Create(nameof(TranslateX), typeof(double), typeof(CompositeTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty TranslateYProperty =
+ BindableProperty.Create(nameof(TranslateY), typeof(double), typeof(CompositeTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public double CenterX
+ {
+ set { SetValue(CenterXProperty, value); }
+ get { return (double)GetValue(CenterXProperty); }
+ }
+
+ public double CenterY
+ {
+ set { SetValue(CenterYProperty, value); }
+ get { return (double)GetValue(CenterYProperty); }
+ }
+
+ public double ScaleX
+ {
+ set { SetValue(ScaleXProperty, value); }
+ get { return (double)GetValue(ScaleXProperty); }
+ }
+
+ public double ScaleY
+ {
+ set { SetValue(ScaleYProperty, value); }
+ get { return (double)GetValue(ScaleYProperty); }
+ }
+
+ public double SkewX
+ {
+ set { SetValue(SkewXProperty, value); }
+ get { return (double)GetValue(SkewXProperty); }
+ }
+
+ public double SkewY
+ {
+ set { SetValue(SkewYProperty, value); }
+ get { return (double)GetValue(SkewYProperty); }
+ }
+
+ public double Rotation
+ {
+ set { SetValue(RotationProperty, value); }
+ get { return (double)GetValue(RotationProperty); }
+ }
+
+ public double TranslateX
+ {
+ set { SetValue(TranslateXProperty, value); }
+ get { return (double)GetValue(TranslateXProperty); }
+ }
+
+ public double TranslateY
+ {
+ set { SetValue(TranslateYProperty, value); }
+ get { return (double)GetValue(TranslateYProperty); }
+ }
+
+ static void OnTransformPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as CompositeTransform).OnTransformPropertyChanged();
+ }
+
+ void OnTransformPropertyChanged()
+ {
+ TransformGroup xformGroup = new TransformGroup
+ {
+ Children =
+ {
+ new TranslateTransform
+ {
+ X = -CenterX,
+ Y = -CenterY
+ },
+ new ScaleTransform
+ {
+ ScaleX = ScaleX,
+ ScaleY = ScaleY
+ },
+ new SkewTransform
+ {
+ AngleX = SkewX,
+ AngleY = SkewY
+ },
+ new RotateTransform
+ {
+ Angle = Rotation
+ },
+ new TranslateTransform
+ {
+ X = CenterX,
+ Y = CenterY
+ },
+ new TranslateTransform
+ {
+ X = TranslateX,
+ Y = TranslateY
+ }
+ }
+ };
+
+ Value = xformGroup.Value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Ellipse.cs b/Xamarin.Forms.Core/Shapes/Ellipse.cs
new file mode 100644
index 00000000000..71bf5559e20
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Ellipse.cs
@@ -0,0 +1,13 @@
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms.Shapes
+{
+ [RenderWith(typeof(_EllipseRenderer))]
+ public sealed class Ellipse : Shape
+ {
+ public Ellipse()
+ {
+ Aspect = Stretch.Fill;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/EllipseGeometry.cs b/Xamarin.Forms.Core/Shapes/EllipseGeometry.cs
new file mode 100644
index 00000000000..74852ffab44
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/EllipseGeometry.cs
@@ -0,0 +1,32 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class EllipseGeometry : Geometry
+ {
+ public static readonly BindableProperty CenterProperty =
+ BindableProperty.Create(nameof(Center), typeof(Point), typeof(EllipseGeometry), new Point());
+
+ public static readonly BindableProperty RadiusXProperty =
+ BindableProperty.Create(nameof(RadiusX), typeof(double), typeof(EllipseGeometry), 0.0);
+
+ public static readonly BindableProperty RadiusYProperty =
+ BindableProperty.Create(nameof(RadiusY), typeof(double), typeof(EllipseGeometry), 0.0);
+
+ public Point Center
+ {
+ set { SetValue(CenterProperty, value); }
+ get { return (Point)GetValue(CenterProperty); }
+ }
+
+ public double RadiusX
+ {
+ set { SetValue(RadiusXProperty, value); }
+ get { return (double)GetValue(RadiusXProperty); }
+ }
+
+ public double RadiusY
+ {
+ set { SetValue(RadiusYProperty, value); }
+ get { return (double)GetValue(RadiusYProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/FillRule.cs b/Xamarin.Forms.Core/Shapes/FillRule.cs
new file mode 100644
index 00000000000..516364f1f3d
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/FillRule.cs
@@ -0,0 +1,8 @@
+namespace Xamarin.Forms.Shapes
+{
+ public enum FillRule
+ {
+ EvenOdd,
+ Nonzero
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Geometry.cs b/Xamarin.Forms.Core/Shapes/Geometry.cs
new file mode 100644
index 00000000000..77ea85ca0f5
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Geometry.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms.Shapes
+{
+ public abstract class Geometry : BindableObject
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/GeometryCollection.cs b/Xamarin.Forms.Core/Shapes/GeometryCollection.cs
new file mode 100644
index 00000000000..bb653fbbb42
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/GeometryCollection.cs
@@ -0,0 +1,9 @@
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms.Shapes
+{
+ public sealed class GeometryCollection : ObservableCollection
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/GeometryGroup.cs b/Xamarin.Forms.Core/Shapes/GeometryGroup.cs
new file mode 100644
index 00000000000..1d4bf4e783e
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/GeometryGroup.cs
@@ -0,0 +1,29 @@
+namespace Xamarin.Forms.Shapes
+{
+ [ContentProperty("Children")]
+ public sealed class GeometryGroup : Geometry
+ {
+ public static readonly BindableProperty ChildrenProperty =
+ BindableProperty.Create(nameof(Children), typeof(GeometryCollection), typeof(GeometryGroup), null);
+
+ public static readonly BindableProperty FillRuleProperty =
+ BindableProperty.Create(nameof(FillRule), typeof(FillRule), typeof(GeometryGroup), FillRule.EvenOdd);
+
+ public GeometryGroup()
+ {
+ Children = new GeometryCollection();
+ }
+
+ public GeometryCollection Children
+ {
+ set { SetValue(ChildrenProperty, value); }
+ get { return (GeometryCollection)GetValue(ChildrenProperty); }
+ }
+
+ public FillRule FillRule
+ {
+ set { SetValue(FillRuleProperty, value); }
+ get { return (FillRule)GetValue(FillRuleProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/GeometryHelper.cs b/Xamarin.Forms.Core/Shapes/GeometryHelper.cs
new file mode 100644
index 00000000000..3938787c6f3
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/GeometryHelper.cs
@@ -0,0 +1,336 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Shapes
+{
+ public static class GeometryHelper
+ {
+ static readonly List Points = new List();
+
+ public static PathGeometry FlattenGeometry(Geometry geoSrc, double tolerance)
+ {
+ // Return empty PathGeometry if Geometry is null
+ if (geoSrc == null)
+ return new PathGeometry();
+
+ PathGeometry pathGeoDst = new PathGeometry();
+ FlattenGeometry(pathGeoDst, geoSrc, tolerance, Matrix.Identity);
+
+ return pathGeoDst;
+ }
+
+ public static void FlattenGeometry(PathGeometry pathGeoDst, Geometry geoSrc, double tolerance, Matrix matxPrevious)
+ {
+ Matrix matx = matxPrevious;
+
+ if (geoSrc is GeometryGroup)
+ {
+ foreach (Geometry geoChild in (geoSrc as GeometryGroup).Children)
+ {
+ FlattenGeometry(pathGeoDst, geoChild, tolerance, matx);
+ }
+ }
+ else if (geoSrc is LineGeometry)
+ {
+ LineGeometry lineGeoSrc = geoSrc as LineGeometry;
+ PathFigure figDst = new PathFigure();
+ PolyLineSegment segDst = new PolyLineSegment();
+
+ figDst.StartPoint = matx.Transform(lineGeoSrc.StartPoint);
+ segDst.Points.Add(matx.Transform(lineGeoSrc.EndPoint));
+
+ figDst.Segments.Add(segDst);
+ pathGeoDst.Figures.Add(figDst);
+ }
+ else if (geoSrc is RectangleGeometry)
+ {
+ RectangleGeometry rectGeoSrc = geoSrc as RectangleGeometry;
+ PathFigure figDst = new PathFigure();
+ PolyLineSegment segDst = new PolyLineSegment();
+
+ figDst.StartPoint = matx.Transform(new Point(rectGeoSrc.Rect.Left, rectGeoSrc.Rect.Top));
+ segDst.Points.Add(matx.Transform(new Point(rectGeoSrc.Rect.Right, rectGeoSrc.Rect.Top)));
+ segDst.Points.Add(matx.Transform(new Point(rectGeoSrc.Rect.Right, rectGeoSrc.Rect.Bottom)));
+ segDst.Points.Add(matx.Transform(new Point(rectGeoSrc.Rect.Left, rectGeoSrc.Rect.Bottom)));
+ segDst.Points.Add(matx.Transform(new Point(rectGeoSrc.Rect.Left, rectGeoSrc.Rect.Top)));
+
+ figDst.IsClosed = true;
+ figDst.Segments.Add(segDst);
+ pathGeoDst.Figures.Add(figDst);
+ }
+ else if (geoSrc is EllipseGeometry)
+ {
+ EllipseGeometry elipGeoSrc = geoSrc as EllipseGeometry;
+ PathFigure figDst = new PathFigure();
+ PolyLineSegment segDst = new PolyLineSegment();
+
+ int max = (int)(4 * (elipGeoSrc.RadiusX + elipGeoSrc.RadiusY) / tolerance);
+
+ for (int i = 0; i < max; i++)
+ {
+ double x = elipGeoSrc.Center.X + elipGeoSrc.RadiusX * Math.Sin(i * 2 * Math.PI / max);
+ double y = elipGeoSrc.Center.Y - elipGeoSrc.RadiusY * Math.Cos(i * 2 * Math.PI / max);
+ Point pt = matx.Transform(new Point(x, y));
+
+ if (i == 0)
+ figDst.StartPoint = pt;
+ else
+ segDst.Points.Add(pt);
+ }
+
+ figDst.IsClosed = true;
+ figDst.Segments.Add(segDst);
+ pathGeoDst.Figures.Add(figDst);
+ }
+ else if (geoSrc is PathGeometry)
+ {
+ PathGeometry pathGeoSrc = geoSrc as PathGeometry;
+ pathGeoDst.FillRule = pathGeoSrc.FillRule;
+
+ foreach (PathFigure figSrc in pathGeoSrc.Figures)
+ {
+ PathFigure figDst = new PathFigure
+ {
+ IsFilled = figSrc.IsFilled,
+ IsClosed = figSrc.IsClosed,
+ StartPoint = matx.Transform(figSrc.StartPoint)
+ };
+ Point ptLast = figDst.StartPoint;
+
+ foreach (PathSegment segSrc in figSrc.Segments)
+ {
+ PolyLineSegment segDst = new PolyLineSegment();
+
+ if (segSrc is LineSegment)
+ {
+ LineSegment lineSegSrc = segSrc as LineSegment;
+ ptLast = matx.Transform(lineSegSrc.Point);
+ segDst.Points.Add(ptLast);
+ }
+ else if (segSrc is PolyLineSegment)
+ {
+ PolyLineSegment polySegSrc = segSrc as PolyLineSegment;
+
+ foreach (Point pt in polySegSrc.Points)
+ {
+ ptLast = matx.Transform(pt);
+ segDst.Points.Add(ptLast);
+ }
+ }
+ else if (segSrc is BezierSegment)
+ {
+ BezierSegment bezSeg = segSrc as BezierSegment;
+ Point pt0 = ptLast;
+ Point pt1 = matx.Transform(bezSeg.Point1);
+ Point pt2 = matx.Transform(bezSeg.Point2);
+ Point pt3 = matx.Transform(bezSeg.Point3);
+
+ Points.Clear();
+ FlattenCubicBezier(Points, pt0, pt1, pt2, pt3, tolerance);
+
+ for (int i = 1; i < Points.Count; i++)
+ segDst.Points.Add(Points[i]);
+
+ ptLast = Points[Points.Count - 1];
+ }
+ else if (segSrc is PolyBezierSegment)
+ {
+ PolyBezierSegment polyBezSeg = segSrc as PolyBezierSegment;
+
+ for (int bez = 0; bez < polyBezSeg.Points.Count; bez += 3)
+ {
+ if (bez + 2 > polyBezSeg.Points.Count - 1)
+ break;
+
+ Point pt0 = ptLast;
+ Point pt1 = matx.Transform(polyBezSeg.Points[bez]);
+ Point pt2 = matx.Transform(polyBezSeg.Points[bez + 1]);
+ Point pt3 = matx.Transform(polyBezSeg.Points[bez + 2]);
+
+ Points.Clear();
+ FlattenCubicBezier(Points, pt0, pt1, pt2, pt3, tolerance);
+
+ for (int i = 1; i < Points.Count; i++)
+ segDst.Points.Add(Points[i]);
+
+ ptLast = Points[Points.Count - 1];
+ }
+ }
+ else if (segSrc is QuadraticBezierSegment)
+ {
+ QuadraticBezierSegment quadBezSeg = segSrc as QuadraticBezierSegment;
+ Point pt0 = ptLast;
+ Point pt1 = matx.Transform(quadBezSeg.Point1);
+ Point pt2 = matx.Transform(quadBezSeg.Point2);
+
+ Points.Clear();
+ FlattenQuadraticBezier(Points, pt0, pt1, pt2, tolerance);
+
+ for (int i = 1; i < Points.Count; i++)
+ segDst.Points.Add(Points[i]);
+
+ ptLast = Points[Points.Count - 1];
+ }
+ else if (segSrc is PolyQuadraticBezierSegment)
+ {
+ PolyQuadraticBezierSegment polyQuadBezSeg = segSrc as PolyQuadraticBezierSegment;
+
+ for (int bez = 0; bez < polyQuadBezSeg.Points.Count; bez += 2)
+ {
+ if (bez + 1 > polyQuadBezSeg.Points.Count - 1)
+ break;
+
+ Point pt0 = ptLast;
+ Point pt1 = matx.Transform(polyQuadBezSeg.Points[bez]);
+ Point pt2 = matx.Transform(polyQuadBezSeg.Points[bez + 1]);
+
+ Points.Clear();
+ FlattenQuadraticBezier(Points, pt0, pt1, pt2, tolerance);
+
+ for (int i = 1; i < Points.Count; i++)
+ segDst.Points.Add(Points[i]);
+
+ ptLast = Points[Points.Count - 1];
+ }
+ }
+ else if (segSrc is ArcSegment)
+ {
+ ArcSegment arcSeg = segSrc as ArcSegment;
+ Points.Clear();
+
+ FlattenArc(Points, ptLast, arcSeg.Point,
+ arcSeg.Size.Width, arcSeg.Size.Height,
+ arcSeg.RotationAngle,
+ arcSeg.IsLargeArc,
+ arcSeg.SweepDirection == SweepDirection.CounterClockwise,
+ tolerance);
+
+ // Set ptLast while transferring points
+ for (int i = 1; i < Points.Count; i++)
+ segDst.Points.Add(ptLast = Points[i]);
+ }
+
+ figDst.Segments.Add(segDst);
+ }
+
+ pathGeoDst.Figures.Add(figDst);
+ }
+ }
+ }
+
+ public static void FlattenCubicBezier(List points, Point ptStart, Point ptCtrl1, Point ptCtrl2, Point ptEnd, double tolerance)
+ {
+ int max = (int)((ptCtrl1.Distance(ptStart) + ptCtrl2.Distance(ptCtrl1) + ptEnd.Distance(ptCtrl2)) / tolerance);
+
+ for (int i = 0; i <= max; i++)
+ {
+ double t = (double)i / max;
+
+ double x = (1 - t) * (1 - t) * (1 - t) * ptStart.X +
+ 3 * t * (1 - t) * (1 - t) * ptCtrl1.X +
+ 3 * t * t * (1 - t) * ptCtrl2.X +
+ t * t * t * ptEnd.X;
+
+ double y = (1 - t) * (1 - t) * (1 - t) * ptStart.Y +
+ 3 * t * (1 - t) * (1 - t) * ptCtrl1.Y +
+ 3 * t * t * (1 - t) * ptCtrl2.Y +
+ t * t * t * ptEnd.Y;
+
+ points.Add(new Point(x, y));
+ }
+ }
+
+ public static void FlattenQuadraticBezier(List points, Point ptStart, Point ptCtrl, Point ptEnd, double tolerance)
+ {
+ int max = (int)((ptCtrl.Distance(ptStart) + ptEnd.Distance(ptCtrl)) / tolerance);
+
+ for (int i = 0; i <= max; i++)
+ {
+ double t = (double)i / max;
+
+ double x = (1 - t) * (1 - t) * ptStart.X +
+ 2 * t * (1 - t) * ptCtrl.X +
+ t * t * ptEnd.X;
+
+ double y = (1 - t) * (1 - t) * ptStart.Y +
+ 2 * t * (1 - t) * ptCtrl.Y +
+ t * t * ptEnd.Y;
+
+ points.Add(new Point(x, y));
+ }
+ }
+
+ public static void FlattenArc(List points, Point pt1, Point pt2, double radiusX, double radiusY, double angleRotation,
+ bool isLargeArc, bool isCounterclockwise, double tolerance)
+ {
+ // Adjust for different radii and rotation angle
+ Matrix matx = new Matrix();
+ matx.Rotate(-angleRotation);
+ matx.Scale(radiusY / radiusX, 1);
+ pt1 = matx.Transform(pt1);
+ pt2 = matx.Transform(pt2);
+
+ // Get info about chord that connects both points
+ Point midPoint = new Point((pt1.X + pt2.X) / 2, (pt1.Y + pt2.Y) / 2);
+ Vector2 vect = new Vector2(pt2.X - pt1.X, pt2.Y - pt1.Y);
+ double halfChord = vect.Length / 2;
+
+ // Get vector from chord to center
+ Vector2 vectRotated;
+
+ if (isLargeArc == isCounterclockwise)
+ vectRotated = new Vector2(-vect.Y, vect.X);
+ else
+ vectRotated = new Vector2(vect.Y, -vect.X);
+
+ vectRotated = vectRotated.Normalized;
+
+ // Distance from chord to center
+ double centerDistance = Math.Sqrt(radiusY * radiusY - halfChord * halfChord);
+
+ // Calculate center point
+ Point center = midPoint + centerDistance * vectRotated;
+
+ // Get angles from center to the two points
+ double angle1 = Math.Atan2(pt1.Y - center.Y, pt1.X - center.X);
+ double angle2 = Math.Atan2(pt2.Y - center.Y, pt2.X - center.X);
+
+ double sweep = Math.Abs(angle2 - angle1);
+ bool reverseArc;
+
+ if (Math.IEEERemainder(sweep + 0.000005, Math.PI) < 0.000010)
+ {
+ reverseArc = isCounterclockwise == angle1 < angle2;
+ }
+ else
+ {
+ bool isAcute = sweep < Math.PI;
+ reverseArc = isLargeArc == isAcute;
+ }
+
+ if (reverseArc)
+ {
+ if (angle1 < angle2)
+ angle1 += 2 * Math.PI;
+ else
+ angle2 += 2 * Math.PI;
+ }
+
+ // Invert matrix for final point calculation
+ matx.Invert();
+
+ // Calculate number of points for polyline approximation
+ int max = (int)((4 * (radiusX + radiusY) * Math.Abs(angle2 - angle1) / (2 * Math.PI)) / tolerance);
+
+ for (int i = 0; i <= max; i++)
+ {
+ double angle = ((max - i) * angle1 + i * angle2) / max;
+ double x = center.X + radiusY * Math.Cos(angle);
+ double y = center.Y + radiusY * Math.Sin(angle);
+
+ Point pt = matx.Transform(new Point(x, y));
+ points.Add(pt);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Line.cs b/Xamarin.Forms.Core/Shapes/Line.cs
new file mode 100644
index 00000000000..ba025bdeeb8
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Line.cs
@@ -0,0 +1,44 @@
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms.Shapes
+{
+ [RenderWith(typeof(_LineRenderer))]
+ public sealed class Line : Shape
+ {
+ public static readonly BindableProperty X1Property =
+ BindableProperty.Create(nameof(X1), typeof(double), typeof(Line), 0.0d);
+
+ public static readonly BindableProperty Y1Property =
+ BindableProperty.Create(nameof(Y1), typeof(double), typeof(Line), 0.0d);
+
+ public static readonly BindableProperty X2Property =
+ BindableProperty.Create(nameof(X2), typeof(double), typeof(Line), 0.0d);
+
+ public static readonly BindableProperty Y2Property =
+ BindableProperty.Create(nameof(Y2), typeof(double), typeof(Line), 0.0d);
+
+ public double X1
+ {
+ set { SetValue(X1Property, value); }
+ get { return (double)GetValue(X1Property); }
+ }
+
+ public double Y1
+ {
+ set { SetValue(Y1Property, value); }
+ get { return (double)GetValue(Y1Property); }
+ }
+
+ public double X2
+ {
+ set { SetValue(X2Property, value); }
+ get { return (double)GetValue(X2Property); }
+ }
+
+ public double Y2
+ {
+ set { SetValue(Y2Property, value); }
+ get { return (double)GetValue(Y2Property); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/LineGeometry.cs b/Xamarin.Forms.Core/Shapes/LineGeometry.cs
new file mode 100644
index 00000000000..a965d080d1d
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/LineGeometry.cs
@@ -0,0 +1,23 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class LineGeometry : Geometry
+ {
+ public static readonly BindableProperty StartPointProperty =
+ BindableProperty.Create(nameof(StartPoint), typeof(Point), typeof(LineGeometry), new Point());
+
+ public static readonly BindableProperty EndPointProperty =
+ BindableProperty.Create(nameof(EndPoint), typeof(Point), typeof(LineGeometry), new Point());
+
+ public Point StartPoint
+ {
+ set { SetValue(StartPointProperty, value); }
+ get { return (Point)GetValue(StartPointProperty); }
+ }
+
+ public Point EndPoint
+ {
+ set { SetValue(EndPointProperty, value); }
+ get { return (Point)GetValue(EndPointProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/LineSegment.cs b/Xamarin.Forms.Core/Shapes/LineSegment.cs
new file mode 100644
index 00000000000..f1f592dc637
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/LineSegment.cs
@@ -0,0 +1,24 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class LineSegment : PathSegment
+ {
+ public LineSegment()
+ {
+
+ }
+
+ public LineSegment(Point point)
+ {
+ Point = point;
+ }
+
+ public static readonly BindableProperty PointProperty =
+ BindableProperty.Create(nameof(Point), typeof(Point), typeof(LineSegment), new Point(0, 0));
+
+ public Point Point
+ {
+ set { SetValue(PointProperty, value); }
+ get { return (Point)GetValue(PointProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Matrix.cs b/Xamarin.Forms.Core/Shapes/Matrix.cs
new file mode 100644
index 00000000000..16a6f1925f4
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Matrix.cs
@@ -0,0 +1,863 @@
+using System;
+using System.ComponentModel;
+
+namespace Xamarin.Forms.Shapes
+{
+ internal enum MatrixTypes
+ {
+ Identity = 0,
+ Translation = 1,
+ Scaling = 2,
+ Unknown = 4
+ }
+
+ [TypeConverter(typeof(MatrixTypeConverter))]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public struct Matrix
+ {
+ internal double _m11;
+ internal double _m12;
+ internal double _m21;
+ internal double _m22;
+ internal double _offsetX;
+ internal double _offsetY;
+ internal MatrixTypes _type;
+ internal int _padding;
+
+ static Matrix IdentityMatrix = CreateIdentity();
+
+ public Matrix(double m11, double m12,
+ double m21, double m22,
+ double offsetX, double offsetY)
+ {
+ _m11 = m11;
+ _m12 = m12;
+ _m21 = m21;
+ _m22 = m22;
+ _offsetX = offsetX;
+ _offsetY = offsetY;
+ _type = MatrixTypes.Unknown;
+ _padding = 0;
+
+ DeriveMatrixType();
+ }
+
+ public static Matrix Identity { get { return IdentityMatrix; } }
+
+ public void SetIdentity()
+ {
+ _type = MatrixTypes.Identity;
+ }
+
+ public bool IsIdentity
+ {
+ get
+ {
+ return _type == MatrixTypes.Identity ||
+ (_m11 == 1 && _m12 == 0 && _m21 == 0 && _m22 == 1 && _offsetX == 0 && _offsetY == 0);
+ }
+ }
+
+ public static Matrix operator *(Matrix trans1, Matrix trans2)
+ {
+ MatrixUtil.MultiplyMatrix(ref trans1, ref trans2);
+ return trans1;
+ }
+
+ public static Matrix Multiply(Matrix trans1, Matrix trans2)
+ {
+ MatrixUtil.MultiplyMatrix(ref trans1, ref trans2);
+ return trans1;
+ }
+
+ public void Append(Matrix matrix)
+ {
+ this *= matrix;
+ }
+
+ public void Prepend(Matrix matrix)
+ {
+ this = matrix * this;
+ }
+
+ public void Rotate(double angle)
+ {
+ angle %= 360.0;
+ this *= CreateRotationRadians(angle * (Math.PI / 180.0));
+ }
+
+ public void RotatePrepend(double angle)
+ {
+ angle %= 360.0;
+ this = CreateRotationRadians(angle * (Math.PI / 180.0)) * this;
+ }
+
+ public void RotateAt(double angle, double centerX, double centerY)
+ {
+ angle %= 360.0;
+ this *= CreateRotationRadians(angle * (Math.PI / 180.0), centerX, centerY);
+ }
+
+ public void RotateAtPrepend(double angle, double centerX, double centerY)
+ {
+ angle %= 360.0;
+ this = CreateRotationRadians(angle * (Math.PI / 180.0), centerX, centerY) * this;
+ }
+
+ public void Scale(double scaleX, double scaleY)
+ {
+ this *= CreateScaling(scaleX, scaleY);
+ }
+
+ public void ScalePrepend(double scaleX, double scaleY)
+ {
+ this = CreateScaling(scaleX, scaleY) * this;
+ }
+
+ public void ScaleAt(double scaleX, double scaleY, double centerX, double centerY)
+ {
+ this *= CreateScaling(scaleX, scaleY, centerX, centerY);
+ }
+
+ public void ScaleAtPrepend(double scaleX, double scaleY, double centerX, double centerY)
+ {
+ this = CreateScaling(scaleX, scaleY, centerX, centerY) * this;
+ }
+
+ public void Skew(double skewX, double skewY)
+ {
+ skewX %= 360;
+ skewY %= 360;
+ this *= CreateSkewRadians(skewX * (Math.PI / 180.0),
+ skewY * (Math.PI / 180.0));
+ }
+
+ public void SkewPrepend(double skewX, double skewY)
+ {
+ skewX %= 360;
+ skewY %= 360;
+ this = CreateSkewRadians(skewX * (Math.PI / 180.0),
+ skewY * (Math.PI / 180.0)) * this;
+ }
+
+ public void Translate(double offsetX, double offsetY)
+ {
+ //
+ // / a b 0 \ / 1 0 0 \ / a b 0 \
+ // | c d 0 | * | 0 1 0 | = | c d 0 |
+ // \ e f 1 / \ x y 1 / \ e+x f+y 1 /
+ //
+ // (where e = _offsetX and f == _offsetY)
+ //
+
+ if (_type == MatrixTypes.Identity)
+ {
+ SetMatrix(1, 0,
+ 0, 1,
+ offsetX, offsetY,
+ MatrixTypes.Translation);
+ }
+ else if (_type == MatrixTypes.Unknown)
+ {
+ _offsetX += offsetX;
+ _offsetY += offsetY;
+ }
+ else
+ {
+ _offsetX += offsetX;
+ _offsetY += offsetY;
+
+ _type |= MatrixTypes.Translation;
+ }
+ }
+
+ public void TranslatePrepend(double offsetX, double offsetY)
+ {
+ this = CreateTranslation(offsetX, offsetY) * this;
+ }
+
+ public Point Transform(Point point)
+ {
+ Point newPoint = point;
+
+ double x = newPoint.X;
+ double y = newPoint.Y;
+
+ MultiplyPoint(ref x, ref y);
+
+ return new Point(x, y);
+ }
+
+ public void Transform(Point[] points)
+ {
+ if (points != null)
+ {
+ for (int i = 0; i < points.Length; i++)
+ {
+ var point = points[i];
+ double x = point.X;
+ double y = point.Y;
+
+ MultiplyPoint(ref x, ref y);
+
+ points[i] = new Point(x, y);
+ }
+ }
+ }
+
+ public Vector2 Transform(Vector2 vector)
+ {
+ Vector2 newVector = vector;
+
+ double x = newVector.X;
+ double y = newVector.Y;
+
+ MultiplyVector(ref x, ref y);
+
+ return new Vector2(x, y);
+ }
+
+ public void Transform(Vector2[] vectors)
+ {
+ if (vectors != null)
+ {
+ for (int i = 0; i < vectors.Length; i++)
+ {
+ var vector = vectors[i];
+ double x = vector.X;
+ double y = vector.Y;
+
+ MultiplyVector(ref x, ref y);
+
+ vectors[i] = new Vector2(x, y);
+ }
+ }
+ }
+
+ public double Determinant
+ {
+ get
+ {
+ switch (_type)
+ {
+ case MatrixTypes.Identity:
+ case MatrixTypes.Translation:
+ return 1.0;
+ case MatrixTypes.Scaling:
+ case MatrixTypes.Scaling | MatrixTypes.Translation:
+ return _m11 * _m22;
+ default:
+ return (_m11 * _m22) - (_m12 * _m21);
+ }
+ }
+ }
+
+ public bool HasInverse { get { return Determinant != 0; } }
+
+ public void Invert()
+ {
+ double determinant = Determinant;
+
+ if (determinant == 0)
+ {
+ throw new InvalidOperationException();
+ }
+
+ switch (_type)
+ {
+ case MatrixTypes.Identity:
+ break;
+ case MatrixTypes.Scaling:
+ {
+ _m11 = 1.0 / _m11;
+ _m22 = 1.0 / _m22;
+ }
+ break;
+ case MatrixTypes.Translation:
+ _offsetX = -_offsetX;
+ _offsetY = -_offsetY;
+ break;
+ case MatrixTypes.Scaling | MatrixTypes.Translation:
+ {
+ _m11 = 1.0 / _m11;
+ _m22 = 1.0 / _m22;
+ _offsetX = -_offsetX * _m11;
+ _offsetY = -_offsetY * _m22;
+ }
+ break;
+ default:
+ {
+ double invdet = 1.0 / determinant;
+ SetMatrix(_m22 * invdet,
+ -_m12 * invdet,
+ -_m21 * invdet,
+ _m11 * invdet,
+ (_m21 * _offsetY - _offsetX * _m22) * invdet,
+ (_offsetX * _m12 - _m11 * _offsetY) * invdet,
+ MatrixTypes.Unknown);
+ }
+ break;
+ }
+ }
+
+ public double M11
+ {
+ get
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ return 1.0;
+ }
+ else
+ {
+ return _m11;
+ }
+ }
+ set
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ SetMatrix(value, 0,
+ 0, 1,
+ 0, 0,
+ MatrixTypes.Scaling);
+ }
+ else
+ {
+ _m11 = value;
+ if (_type != MatrixTypes.Unknown)
+ {
+ _type |= MatrixTypes.Scaling;
+ }
+ }
+ }
+ }
+
+ public double M12
+ {
+ get
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ return 0;
+ }
+ else
+ {
+ return _m12;
+ }
+ }
+ set
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ SetMatrix(1, value,
+ 0, 1,
+ 0, 0,
+ MatrixTypes.Unknown);
+ }
+ else
+ {
+ _m12 = value;
+ _type = MatrixTypes.Unknown;
+ }
+ }
+ }
+
+ public double M21
+ {
+ get
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ return 0;
+ }
+ else
+ {
+ return _m21;
+ }
+ }
+ set
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ SetMatrix(1, 0,
+ value, 1,
+ 0, 0,
+ MatrixTypes.Unknown);
+ }
+ else
+ {
+ _m21 = value;
+ _type = MatrixTypes.Unknown;
+ }
+ }
+ }
+
+ public double M22
+ {
+ get
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ return 1.0;
+ }
+ else
+ {
+ return _m22;
+ }
+ }
+ set
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ SetMatrix(1, 0,
+ 0, value,
+ 0, 0,
+ MatrixTypes.Scaling);
+ }
+ else
+ {
+ _m22 = value;
+ if (_type != MatrixTypes.Unknown)
+ {
+ _type |= MatrixTypes.Scaling;
+ }
+ }
+ }
+ }
+
+ public double OffsetX
+ {
+ get
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ return 0;
+ }
+ else
+ {
+ return _offsetX;
+ }
+ }
+ set
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ SetMatrix(1, 0,
+ 0, 1,
+ value, 0,
+ MatrixTypes.Translation);
+ }
+ else
+ {
+ _offsetX = value;
+ if (_type != MatrixTypes.Unknown)
+ {
+ _type |= MatrixTypes.Translation;
+ }
+ }
+ }
+ }
+
+ public double OffsetY
+ {
+ get
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ return 0;
+ }
+ else
+ {
+ return _offsetY;
+ }
+ }
+ set
+ {
+ if (_type == MatrixTypes.Identity)
+ {
+ SetMatrix(1, 0,
+ 0, 1,
+ 0, value,
+ MatrixTypes.Translation);
+ }
+ else
+ {
+ _offsetY = value;
+ if (_type != MatrixTypes.Unknown)
+ {
+ _type |= MatrixTypes.Translation;
+ }
+ }
+ }
+ }
+
+ internal void MultiplyVector(ref double x, ref double y)
+ {
+ switch (_type)
+ {
+ case MatrixTypes.Identity:
+ case MatrixTypes.Translation:
+ return;
+ case MatrixTypes.Scaling:
+ case MatrixTypes.Scaling | MatrixTypes.Translation:
+ x *= _m11;
+ y *= _m22;
+ break;
+ default:
+ double xadd = y * _m21;
+ double yadd = x * _m12;
+ x *= _m11;
+ x += xadd;
+ y *= _m22;
+ y += yadd;
+ break;
+ }
+ }
+
+ internal void MultiplyPoint(ref double x, ref double y)
+ {
+ switch (_type)
+ {
+ case MatrixTypes.Identity:
+ return;
+ case MatrixTypes.Translation:
+ x += _offsetX;
+ y += _offsetY;
+ return;
+ case MatrixTypes.Scaling:
+ x *= _m11;
+ y *= _m22;
+ return;
+ case MatrixTypes.Scaling | MatrixTypes.Translation:
+ x *= _m11;
+ x += _offsetX;
+ y *= _m22;
+ y += _offsetY;
+ break;
+ default:
+ double xadd = y * _m21 + _offsetX;
+ double yadd = x * _m12 + _offsetY;
+ x *= _m11;
+ x += xadd;
+ y *= _m22;
+ y += yadd;
+ break;
+ }
+ }
+
+ internal static Matrix CreateRotationRadians(double angle)
+ {
+ return CreateRotationRadians(angle, 0, 0);
+ }
+
+ internal static Matrix CreateRotationRadians(double angle, double centerX, double centerY)
+ {
+ Matrix matrix = new Matrix();
+
+ double sin = Math.Sin(angle);
+ double cos = Math.Cos(angle);
+ double dx = (centerX * (1.0 - cos)) + (centerY * sin);
+ double dy = (centerY * (1.0 - cos)) - (centerX * sin);
+
+ matrix.SetMatrix(cos, sin,
+ -sin, cos,
+ dx, dy,
+ MatrixTypes.Unknown);
+
+ return matrix;
+ }
+
+ internal static Matrix CreateScaling(double scaleX, double scaleY, double centerX, double centerY)
+ {
+ Matrix matrix = new Matrix();
+
+ matrix.SetMatrix(scaleX, 0,
+ 0, scaleY,
+ centerX - scaleX * centerX, centerY - scaleY * centerY,
+ MatrixTypes.Scaling | MatrixTypes.Translation);
+
+ return matrix;
+ }
+
+ internal static Matrix CreateScaling(double scaleX, double scaleY)
+ {
+ Matrix matrix = new Matrix();
+ matrix.SetMatrix(scaleX, 0,
+ 0, scaleY,
+ 0, 0,
+ MatrixTypes.Scaling);
+ return matrix;
+ }
+
+ internal static Matrix CreateSkewRadians(double skewX, double skewY)
+ {
+ Matrix matrix = new Matrix();
+
+ matrix.SetMatrix(1.0, Math.Tan(skewY),
+ Math.Tan(skewX), 1.0,
+ 0.0, 0.0,
+ MatrixTypes.Unknown);
+
+ return matrix;
+ }
+
+ ///
+ /// Sets the transformation to the given translation specified by the offset vector.
+ ///
+ /// The offset in X
+ /// The offset in Y
+ internal static Matrix CreateTranslation(double offsetX, double offsetY)
+ {
+ Matrix matrix = new Matrix();
+
+ matrix.SetMatrix(1, 0,
+ 0, 1,
+ offsetX, offsetY,
+ MatrixTypes.Translation);
+
+ return matrix;
+ }
+
+ static Matrix CreateIdentity()
+ {
+ Matrix matrix = new Matrix();
+ matrix.SetMatrix(1, 0,
+ 0, 1,
+ 0, 0,
+ MatrixTypes.Identity);
+ return matrix;
+ }
+
+ void SetMatrix(double m11, double m12,
+ double m21, double m22,
+ double offsetX, double offsetY,
+ MatrixTypes type)
+ {
+ _m11 = m11;
+ _m12 = m12;
+ _m21 = m21;
+ _m22 = m22;
+ _offsetX = offsetX;
+ _offsetY = offsetY;
+ _type = type;
+ }
+
+ void DeriveMatrixType()
+ {
+ _type = 0;
+
+ if (!(_m21 == 0 && _m12 == 0))
+ {
+ _type = MatrixTypes.Unknown;
+ return;
+ }
+
+ if (!(_m11 == 1 && _m22 == 1))
+ {
+ _type = MatrixTypes.Scaling;
+ }
+
+ if (!(_offsetX == 0 && _offsetY == 0))
+ {
+ _type |= MatrixTypes.Translation;
+ }
+
+ if (0 == (_type & (MatrixTypes.Translation | MatrixTypes.Scaling)))
+ {
+ _type = MatrixTypes.Identity;
+ }
+ return;
+ }
+ }
+
+ internal static class MatrixUtil
+ {
+ internal static void TransformRect(ref Xamarin.Forms.Rectangle rect, ref Matrix matrix)
+ {
+ if (rect.IsEmpty)
+ {
+ return;
+ }
+
+ MatrixTypes matrixType = matrix._type;
+
+ if (matrixType == MatrixTypes.Identity)
+ {
+ return;
+ }
+
+ // Scaling
+ if (0 != (matrixType & MatrixTypes.Scaling))
+ {
+ rect.X *= matrix._m11;
+ rect.Y *= matrix._m22;
+ rect.Width *= matrix._m11;
+ rect.Height *= matrix._m22;
+
+ if (rect.Width < 0.0)
+ {
+ rect.X += rect.Width;
+ rect.Width = -rect.Width;
+ }
+
+ if (rect.Height < 0.0)
+ {
+ rect.Y += rect.Height;
+ rect.Height = -rect.Height;
+ }
+ }
+
+ // Translation
+ if (0 != (matrixType & MatrixTypes.Translation))
+ {
+ // X
+ rect.X += matrix._offsetX;
+
+ // Y
+ rect.X += matrix._offsetY;
+ }
+
+ if (matrixType == MatrixTypes.Unknown)
+ {
+ Point point0 = matrix.Transform(new Point(rect.Right, rect.Top));
+ Point point1 = matrix.Transform(new Point(rect.Right, rect.Top));
+ Point point2 = matrix.Transform(new Point(rect.Right, rect.Bottom));
+ Point point3 = matrix.Transform(new Point(rect.Left, rect.Bottom));
+
+ rect.X = Math.Min(Math.Min(point0.X, point1.X), Math.Min(point2.X, point3.X));
+ rect.Y = Math.Min(Math.Min(point0.Y, point1.Y), Math.Min(point2.Y, point3.Y));
+
+ rect.Width = Math.Max(Math.Max(point0.X, point1.X), Math.Max(point2.X, point3.X)) - rect.X;
+ rect.Height = Math.Max(Math.Max(point0.Y, point1.Y), Math.Max(point2.Y, point3.Y)) - rect.Y;
+ }
+ }
+
+ internal static void MultiplyMatrix(ref Matrix matrix1, ref Matrix matrix2)
+ {
+ MatrixTypes type1 = matrix1._type;
+ MatrixTypes type2 = matrix2._type;
+
+ if (type2 == MatrixTypes.Identity)
+ {
+ return;
+ }
+
+ if (type1 == MatrixTypes.Identity)
+ {
+ matrix1 = matrix2;
+ return;
+ }
+
+ if (type2 == MatrixTypes.Translation)
+ {
+ matrix1._offsetX += matrix2._offsetX;
+ matrix1._offsetY += matrix2._offsetY;
+
+ if (type1 != MatrixTypes.Unknown)
+ {
+ matrix1._type |= MatrixTypes.Translation;
+ }
+
+ return;
+ }
+
+ // Check for the first value being a translate
+ if (type1 == MatrixTypes.Translation)
+ {
+ double offsetX = matrix1._offsetX;
+ double offsetY = matrix1._offsetY;
+
+ matrix1 = matrix2;
+
+ matrix1._offsetX = offsetX * matrix2._m11 + offsetY * matrix2._m21 + matrix2._offsetX;
+ matrix1._offsetY = offsetX * matrix2._m12 + offsetY * matrix2._m22 + matrix2._offsetY;
+
+ if (type2 == MatrixTypes.Unknown)
+ {
+ matrix1._type = MatrixTypes.Unknown;
+ }
+ else
+ {
+ matrix1._type = MatrixTypes.Scaling | MatrixTypes.Translation;
+ }
+ return;
+ }
+
+ // trans1._type | trans2._type
+ // 7 6 5 4 | 3 2 1 0
+ int combinedType = ((int)type1 << 4) | (int)type2;
+
+ switch (combinedType)
+ {
+ case 34: // S * S
+ // 2 multiplications
+ matrix1._m11 *= matrix2._m11;
+ matrix1._m22 *= matrix2._m22;
+ return;
+
+ case 35: // S * S|T
+ matrix1._m11 *= matrix2._m11;
+ matrix1._m22 *= matrix2._m22;
+ matrix1._offsetX = matrix2._offsetX;
+ matrix1._offsetY = matrix2._offsetY;
+
+ // Transform set to Translate and Scale
+ matrix1._type = MatrixTypes.Translation | MatrixTypes.Scaling;
+ return;
+
+ case 50: // S|T * S
+ matrix1._m11 *= matrix2._m11;
+ matrix1._m22 *= matrix2._m22;
+ matrix1._offsetX *= matrix2._m11;
+ matrix1._offsetY *= matrix2._m22;
+ return;
+
+ case 51: // S|T * S|T
+ matrix1._m11 *= matrix2._m11;
+ matrix1._m22 *= matrix2._m22;
+ matrix1._offsetX = matrix2._m11 * matrix1._offsetX + matrix2._offsetX;
+ matrix1._offsetY = matrix2._m22 * matrix1._offsetY + matrix2._offsetY;
+ return;
+ case 36: // S * U
+ case 52: // S|T * U
+ case 66: // U * S
+ case 67: // U * S|T
+ case 68: // U * U
+ matrix1 = new Matrix(
+ matrix1._m11 * matrix2._m11 + matrix1._m12 * matrix2._m21,
+ matrix1._m11 * matrix2._m12 + matrix1._m12 * matrix2._m22,
+
+ matrix1._m21 * matrix2._m11 + matrix1._m22 * matrix2._m21,
+ matrix1._m21 * matrix2._m12 + matrix1._m22 * matrix2._m22,
+
+ matrix1._offsetX * matrix2._m11 + matrix1._offsetY * matrix2._m21 + matrix2._offsetX,
+ matrix1._offsetX * matrix2._m12 + matrix1._offsetY * matrix2._m22 + matrix2._offsetY);
+ return;
+ default:
+ break;
+ }
+ }
+
+ internal static void PrependOffset(ref Matrix matrix, double offsetX, double offsetY)
+ {
+ if (matrix._type == MatrixTypes.Identity)
+ {
+ matrix = new Matrix(1, 0, 0, 1, offsetX, offsetY)
+ {
+ _type = MatrixTypes.Translation
+ };
+ }
+ else
+ {
+ matrix._offsetX += matrix._m11 * offsetX + matrix._m21 * offsetY;
+ matrix._offsetY += matrix._m12 * offsetX + matrix._m22 * offsetY;
+
+ if (matrix._type != MatrixTypes.Unknown)
+ {
+ matrix._type |= MatrixTypes.Translation;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/MatrixTransform.cs b/Xamarin.Forms.Core/Shapes/MatrixTransform.cs
new file mode 100644
index 00000000000..9bf9e384c4e
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/MatrixTransform.cs
@@ -0,0 +1,25 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class MatrixTransform : Transform
+ {
+ public static readonly BindableProperty MatrixProperty =
+ BindableProperty.Create(nameof(Matrix), typeof(Matrix), typeof(MatrixTransform), new Matrix(),
+ propertyChanged: OnTransformPropertyChanged);
+
+ public Matrix Matrix
+ {
+ set { SetValue(MatrixProperty, value); }
+ get { return (Matrix)GetValue(MatrixProperty); }
+ }
+
+ static void OnTransformPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as MatrixTransform).OnTransformPropertyChanged();
+ }
+
+ void OnTransformPropertyChanged()
+ {
+ Value = Matrix;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core/Shapes/MatrixTypeConverter.cs b/Xamarin.Forms.Core/Shapes/MatrixTypeConverter.cs
new file mode 100644
index 00000000000..8ec53e0079c
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/MatrixTypeConverter.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Xamarin.Forms.Shapes
+{
+ public class MatrixTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ return CreateMatrix(value);
+ }
+
+ internal static Matrix CreateMatrix(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ throw new ArgumentException("Argument is null or empty");
+
+ string[] strs = value.Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
+
+ if (strs.Length != 6)
+ throw new ArgumentException("Argument must have six numbers");
+
+ double[] values = new double[6];
+
+ for (int i = 0; i < 6; i++)
+ if (!double.TryParse(strs[i], out values[i]))
+ throw new ArgumentException("Argument must be numeric values");
+
+ return new Matrix(values[0], values[1], values[2], values[3], values[4], values[5]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Path.cs b/Xamarin.Forms.Core/Shapes/Path.cs
new file mode 100644
index 00000000000..072d0d6051a
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Path.cs
@@ -0,0 +1,69 @@
+using System.ComponentModel;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms.Shapes
+{
+ [RenderWith(typeof(_PathRenderer))]
+ public class Path : Shape
+ {
+ public static readonly BindableProperty DataProperty =
+ BindableProperty.Create(nameof(Data), typeof(Geometry), typeof(Path), null,
+ propertyChanged: OnGeometryPropertyChanged);
+
+ public static readonly BindableProperty RenderTransformProperty =
+ BindableProperty.Create(nameof(RenderTransform), typeof(Transform), typeof(Path), null,
+ propertyChanged: OnTransformPropertyChanged);
+
+ [TypeConverter(typeof(PathGeometryConverter))]
+ public Geometry Data
+ {
+ set { SetValue(DataProperty, value); }
+ get { return (Geometry)GetValue(DataProperty); }
+ }
+
+ public Transform RenderTransform
+ {
+ set { SetValue(RenderTransformProperty, value); }
+ get { return (Transform)GetValue(RenderTransformProperty); }
+ }
+
+ static void OnGeometryPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (oldValue != null)
+ {
+ (oldValue as Geometry).PropertyChanged -= (bindable as Path).OnGeometryPropertyChanged;
+ }
+
+ if (newValue != null)
+ {
+ (newValue as Geometry).PropertyChanged += (bindable as Path).OnGeometryPropertyChanged;
+ }
+ }
+
+ static void OnTransformPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (oldValue != null)
+ {
+ (oldValue as Transform).PropertyChanged -= (bindable as Path).OnTransformPropertyChanged;
+ }
+
+ if (newValue != null)
+ {
+ (newValue as Transform).PropertyChanged += (bindable as Path).OnTransformPropertyChanged;
+ }
+ }
+
+ void OnGeometryPropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ OnPropertyChanged(nameof(Geometry));
+ }
+
+ void OnTransformPropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ if (args.PropertyName == Transform.ValueProperty.PropertyName)
+ {
+ OnPropertyChanged(nameof(RenderTransform));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PathFigure.cs b/Xamarin.Forms.Core/Shapes/PathFigure.cs
new file mode 100644
index 00000000000..51759cd1c4f
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PathFigure.cs
@@ -0,0 +1,59 @@
+using System;
+
+namespace Xamarin.Forms.Shapes
+{
+ [ContentProperty("Segments")]
+ public sealed class PathFigure : BindableObject, IAnimatable
+ {
+ public PathFigure()
+ {
+ Segments = new PathSegmentCollection();
+ }
+
+ public static readonly BindableProperty SegmentsProperty =
+ BindableProperty.Create(nameof(Segments), typeof(PathSegmentCollection), typeof(PathFigure), null);
+
+ public static readonly BindableProperty StartPointProperty =
+ BindableProperty.Create(nameof(StartPoint), typeof(Point), typeof(PathFigure), new Point(0, 0));
+
+ public static readonly BindableProperty IsClosedProperty =
+ BindableProperty.Create(nameof(IsClosed), typeof(bool), typeof(PathFigure), false);
+
+ public static readonly BindableProperty IsFilledProperty =
+ BindableProperty.Create(nameof(IsFilled), typeof(bool), typeof(PathFigure), true);
+
+ public PathSegmentCollection Segments
+ {
+ set { SetValue(SegmentsProperty, value); }
+ get { return (PathSegmentCollection)GetValue(SegmentsProperty); }
+ }
+
+ public Point StartPoint
+ {
+ set { SetValue(StartPointProperty, value); }
+ get { return (Point)GetValue(StartPointProperty); }
+ }
+
+ public bool IsClosed
+ {
+ set { SetValue(IsClosedProperty, value); }
+ get { return (bool)GetValue(IsClosedProperty); }
+ }
+
+ public bool IsFilled
+ {
+ set { SetValue(IsFilledProperty, value); }
+ get { return (bool)GetValue(IsFilledProperty); }
+ }
+
+ public void BatchBegin()
+ {
+
+ }
+
+ public void BatchCommit()
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PathFigureCollection.cs b/Xamarin.Forms.Core/Shapes/PathFigureCollection.cs
new file mode 100644
index 00000000000..b9dcb1d8bb2
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PathFigureCollection.cs
@@ -0,0 +1,9 @@
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms.Shapes
+{
+ public sealed class PathFigureCollection : ObservableCollection
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PathFigureCollectionConverter.cs b/Xamarin.Forms.Core/Shapes/PathFigureCollectionConverter.cs
new file mode 100644
index 00000000000..47d326f62f0
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PathFigureCollectionConverter.cs
@@ -0,0 +1,567 @@
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+
+namespace Xamarin.Forms.Shapes
+{
+ public class PathFigureCollectionConverter : TypeConverter
+ {
+ const bool AllowSign = true;
+ const bool AllowComma = true;
+
+ static bool _figureStarted;
+ static string _pathString;
+ static int _pathLength;
+ static int _curIndex;
+ static Point _lastStart;
+ static Point _lastPoint;
+ static Point _secondLastPoint;
+ static char _token;
+
+ public override object ConvertFromInvariantString(string value)
+ {
+ PathFigureCollection pathFigureCollection = new PathFigureCollection();
+
+ ParseStringToPathFigureCollection(pathFigureCollection, value);
+
+ return pathFigureCollection;
+ }
+
+ public static void ParseStringToPathFigureCollection(PathFigureCollection pathFigureCollection, string pathString)
+ {
+ if (pathString != null)
+ {
+ int curIndex = 0;
+
+ while ((curIndex < pathString.Length) && char.IsWhiteSpace(pathString, curIndex))
+ {
+ curIndex++;
+ }
+
+ if (curIndex < pathString.Length)
+ {
+ if (pathString[curIndex] == 'F')
+ {
+ curIndex++;
+
+ while ((curIndex < pathString.Length) && char.IsWhiteSpace(pathString, curIndex))
+ {
+ curIndex++;
+ }
+
+ // If we ran out of text, this is an error, because 'F' cannot be specified without 0 or 1
+ // Also, if the next token isn't 0 or 1, this too is illegal
+ if ((curIndex == pathString.Length) ||
+ ((pathString[curIndex] != '0') &&
+ (pathString[curIndex] != '1')))
+ {
+ throw new FormatException("IllegalToken");
+ }
+
+ // Increment curIndex to point to the next char
+ curIndex++;
+ }
+ }
+
+ ParseToPathFigureCollection(pathFigureCollection, pathString, curIndex);
+ }
+ }
+
+ static void ParseToPathFigureCollection(PathFigureCollection pathFigureCollection, string pathString, int startIndex)
+ {
+ PathFigure pathFigure = null;
+
+ _pathString = pathString;
+ _pathLength = pathString.Length;
+ _curIndex = startIndex;
+
+ _secondLastPoint = new Point(0, 0);
+ _lastPoint = new Point(0, 0);
+ _lastStart = new Point(0, 0);
+
+ _figureStarted = false;
+
+ bool first = true;
+
+ char last_cmd = ' ';
+
+ while (ReadToken()) // Empty path is allowed in XAML
+ {
+ char cmd = _token;
+
+ if (first)
+ {
+ if ((cmd != 'M') && (cmd != 'm')) // Path starts with M|m
+ {
+ ThrowBadToken();
+ }
+
+ first = false;
+ }
+
+ switch (cmd)
+ {
+ case 'm':
+ case 'M':
+ // XAML allows multiple points after M/m
+ _lastPoint = ReadPoint(cmd, !AllowComma);
+
+ pathFigure = new PathFigure
+ {
+ StartPoint = _lastPoint
+ };
+ pathFigureCollection.Add(pathFigure);
+
+ _figureStarted = true;
+ _lastStart = _lastPoint;
+ last_cmd = 'M';
+
+ while (IsNumber(AllowComma))
+ {
+ _lastPoint = ReadPoint(cmd, !AllowComma);
+
+ LineSegment lineSegment = new LineSegment
+ {
+ Point = _lastPoint
+ };
+ pathFigure.Segments.Add(lineSegment);
+
+ last_cmd = 'L';
+ }
+ break;
+
+ case 'l':
+ case 'L':
+ case 'h':
+ case 'H':
+ case 'v':
+ case 'V':
+ EnsureFigure();
+
+ do
+ {
+ switch (cmd)
+ {
+ case 'l':
+ _lastPoint = ReadPoint(cmd, !AllowComma);
+ break;
+ case 'L':
+ _lastPoint = ReadPoint(cmd, !AllowComma);
+ break;
+ case 'h':
+ _lastPoint.X += ReadNumber(!AllowComma);
+ break;
+ case 'H':
+ _lastPoint.X = ReadNumber(!AllowComma);
+ break;
+ case 'v':
+ _lastPoint.Y += ReadNumber(!AllowComma);
+ break;
+ case 'V':
+ _lastPoint.Y = ReadNumber(!AllowComma);
+ break;
+ }
+
+ pathFigure.Segments.Add(new LineSegment
+ {
+ Point = _lastPoint
+ });
+ }
+ while (IsNumber(AllowComma));
+
+ last_cmd = 'L';
+ break;
+
+ case 'c':
+ case 'C': // Cubic Bezier
+ case 's':
+ case 'S': // Smooth cublic Bezier
+ EnsureFigure();
+
+ do
+ {
+ Point p;
+
+ if ((cmd == 's') || (cmd == 'S'))
+ {
+ if (last_cmd == 'C')
+ {
+ p = Reflect();
+ }
+ else
+ {
+ p = _lastPoint;
+ }
+
+ _secondLastPoint = ReadPoint(cmd, !AllowComma);
+ }
+ else
+ {
+ p = ReadPoint(cmd, !AllowComma);
+
+ _secondLastPoint = ReadPoint(cmd, AllowComma);
+ }
+
+ _lastPoint = ReadPoint(cmd, AllowComma);
+
+ BezierSegment bezierSegment = new BezierSegment
+ {
+ Point1 = p,
+ Point2 = _secondLastPoint,
+ Point3 = _lastPoint
+ };
+
+ pathFigure.Segments.Add(bezierSegment);
+
+ last_cmd = 'C';
+ }
+ while (IsNumber(AllowComma));
+
+ break;
+
+ case 'q':
+ case 'Q': // Quadratic Bezier
+ case 't':
+ case 'T': // Smooth quadratic Bezier
+ EnsureFigure();
+
+ do
+ {
+ if ((cmd == 't') || (cmd == 'T'))
+ {
+ if (last_cmd == 'Q')
+ {
+ _secondLastPoint = Reflect();
+ }
+ else
+ {
+ _secondLastPoint = _lastPoint;
+ }
+
+ _lastPoint = ReadPoint(cmd, !AllowComma);
+ }
+ else
+ {
+ _secondLastPoint = ReadPoint(cmd, !AllowComma);
+ _lastPoint = ReadPoint(cmd, AllowComma);
+ }
+
+ QuadraticBezierSegment quadraticBezierSegment = new QuadraticBezierSegment
+ {
+ Point1 = _secondLastPoint,
+ Point2 = _lastPoint
+ };
+
+ pathFigure.Segments.Add(quadraticBezierSegment);
+
+ last_cmd = 'Q';
+ }
+ while (IsNumber(AllowComma));
+
+ break;
+
+ case 'a':
+ case 'A':
+ EnsureFigure();
+
+ do
+ {
+ // A 3,4 5, 0, 0, 6,7
+ double w = ReadNumber(!AllowComma);
+ double h = ReadNumber(AllowComma);
+ double rotation = ReadNumber(AllowComma);
+ bool large = ReadBool();
+ bool sweep = ReadBool();
+
+ _lastPoint = ReadPoint(cmd, AllowComma);
+
+ ArcSegment arcSegment = new ArcSegment
+ {
+ Size = new Size(w, h),
+ RotationAngle = rotation,
+ IsLargeArc = large,
+ SweepDirection = sweep ? SweepDirection.Clockwise : SweepDirection.CounterClockwise,
+ Point = _lastPoint
+ };
+
+ pathFigure.Segments.Add(arcSegment);
+ }
+ while (IsNumber(AllowComma));
+
+ last_cmd = 'A';
+ break;
+
+ case 'z':
+ case 'Z':
+ EnsureFigure();
+ pathFigure.IsClosed = true;
+ _figureStarted = false;
+ last_cmd = 'Z';
+
+ _lastPoint = _lastStart; // Set reference point to be first point of current figure
+ break;
+
+ default:
+ ThrowBadToken();
+ break;
+ }
+ }
+ }
+
+ static void EnsureFigure()
+ {
+ if (!_figureStarted)
+ _figureStarted = true;
+ }
+
+ static Point Reflect()
+ {
+ return new Point(
+ 2 * _lastPoint.X - _secondLastPoint.X,
+ 2 * _lastPoint.Y - _secondLastPoint.Y);
+ }
+
+ static bool More()
+ {
+ return _curIndex < _pathLength;
+ }
+
+ static bool SkipWhiteSpace(bool allowComma)
+ {
+ bool commaMet = false;
+
+ while (More())
+ {
+ char ch = _pathString[_curIndex];
+
+ switch (ch)
+ {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ break;
+
+ case ',':
+ if (allowComma)
+ {
+ commaMet = true;
+ allowComma = false; // One comma only
+ }
+ else
+ {
+ ThrowBadToken();
+ }
+ break;
+
+ default:
+ // Avoid calling IsWhiteSpace for ch in (' ' .. 'z']
+ if (((ch > ' ') && (ch <= 'z')) || !char.IsWhiteSpace(ch))
+ {
+ return commaMet;
+ }
+ break;
+ }
+
+ _curIndex++;
+ }
+
+ return commaMet;
+ }
+
+ static bool ReadBool()
+ {
+ SkipWhiteSpace(AllowComma);
+
+ if (More())
+ {
+ _token = _pathString[_curIndex++];
+
+ if (_token == '0')
+ {
+ return false;
+ }
+ else if (_token == '1')
+ {
+ return true;
+ }
+ }
+
+ ThrowBadToken();
+
+ return false;
+ }
+
+ static bool ReadToken()
+ {
+ SkipWhiteSpace(!AllowComma);
+
+ // Check for end of string
+ if (More())
+ {
+ _token = _pathString[_curIndex++];
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ static void ThrowBadToken()
+ {
+ throw new FormatException(string.Format("UnexpectedToken \"{0}\" into {1}", _pathString, _curIndex - 1));
+ }
+
+ static Point ReadPoint(char cmd, bool allowcomma)
+ {
+ double x = ReadNumber(allowcomma);
+ double y = ReadNumber(AllowComma);
+
+ if (cmd >= 'a') // 'A' < 'a'. lower case for relative
+ {
+ x += _lastPoint.X;
+ y += _lastPoint.Y;
+ }
+
+ return new Point(x, y);
+ }
+
+ static bool IsNumber(bool allowComma)
+ {
+ bool commaMet = SkipWhiteSpace(allowComma);
+
+ if (More())
+ {
+ _token = _pathString[_curIndex];
+
+ // Valid start of a number
+ if ((_token == '.') || (_token == '-') || (_token == '+') || ((_token >= '0') && (_token <= '9'))
+ || (_token == 'I') // Infinity
+ || (_token == 'N')) // NaN
+ {
+ return true;
+ }
+ }
+
+ if (commaMet) // Only allowed between numbers
+ {
+ ThrowBadToken();
+ }
+
+ return false;
+ }
+
+ static double ReadNumber(bool allowComma)
+ {
+ if (!IsNumber(allowComma))
+ {
+ ThrowBadToken();
+ }
+
+ bool simple = true;
+ int start = _curIndex;
+
+ // Allow for a sign
+ //
+ // There are numbers that cannot be preceded with a sign, for instance, -NaN, but it's
+ // fine to ignore that at this point, since the CLR parser will catch this later.
+ if (More() && ((_pathString[_curIndex] == '-') || _pathString[_curIndex] == '+'))
+ {
+ _curIndex++;
+ }
+
+ // Check for Infinity (or -Infinity).
+ if (More() && (_pathString[_curIndex] == 'I'))
+ {
+ // Don't bother reading the characters, as the CLR parser will
+ // do this for us later.
+ _curIndex = Math.Min(_curIndex + 8, _pathLength); // "Infinity" has 8 characters
+ simple = false;
+ }
+ // Check for NaN
+ else if (More() && (_pathString[_curIndex] == 'N'))
+ {
+ //
+ // Don't bother reading the characters, as the CLR parser will
+ // do this for us later.
+ //
+ _curIndex = Math.Min(_curIndex + 3, _pathLength); // "NaN" has 3 characters
+ simple = false;
+ }
+ else
+ {
+ SkipDigits(!AllowSign);
+
+ // Optional period, followed by more digits
+ if (More() && (_pathString[_curIndex] == '.'))
+ {
+ simple = false;
+ _curIndex++;
+ SkipDigits(!AllowSign);
+ }
+
+ // Exponent
+ if (More() && ((_pathString[_curIndex] == 'E') || (_pathString[_curIndex] == 'e')))
+ {
+ simple = false;
+ _curIndex++;
+ SkipDigits(AllowSign);
+ }
+ }
+
+ if (simple && (_curIndex <= (start + 8))) // 32-bit integer
+ {
+ int sign = 1;
+
+ if (_pathString[start] == '+')
+ {
+ start++;
+ }
+ else if (_pathString[start] == '-')
+ {
+ start++;
+ sign = -1;
+ }
+
+ int value = 0;
+
+ while (start < _curIndex)
+ {
+ value = value * 10 + (_pathString[start] - '0');
+ start++;
+ }
+
+ return value * sign;
+ }
+ else
+ {
+ string subString = _pathString.Substring(start, _curIndex - start);
+
+ try
+ {
+ return Convert.ToDouble(subString, CultureInfo.InvariantCulture);
+ }
+ catch (FormatException)
+ {
+ throw new FormatException(string.Format("UnexpectedToken \"{0}\" into {1}", start, _pathString));
+ }
+ }
+ }
+
+ static void SkipDigits(bool signAllowed)
+ {
+ // Allow for a sign
+ if (signAllowed && More() && ((_pathString[_curIndex] == '-') || _pathString[_curIndex] == '+'))
+ {
+ _curIndex++;
+ }
+
+ while (More() && (_pathString[_curIndex] >= '0') && (_pathString[_curIndex] <= '9'))
+ {
+ _curIndex++;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PathGeometry.cs b/Xamarin.Forms.Core/Shapes/PathGeometry.cs
new file mode 100644
index 00000000000..faff4729b8d
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PathGeometry.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Xamarin.Forms.Shapes
+{
+ [ContentProperty("Figures")]
+ public sealed class PathGeometry : Geometry
+ {
+ public PathGeometry()
+ {
+ Figures = new PathFigureCollection();
+ }
+
+ public static readonly BindableProperty FiguresProperty =
+ BindableProperty.Create(nameof(Figures), typeof(PathFigureCollection), typeof(PathGeometry), null);
+
+ public static readonly BindableProperty FillRuleProperty =
+ BindableProperty.Create(nameof(FillRule), typeof(FillRule), typeof(PathGeometry), FillRule.EvenOdd);
+
+ [TypeConverter(typeof(PathFigureCollectionConverter))]
+ public PathFigureCollection Figures
+ {
+ set { SetValue(FiguresProperty, value); }
+ get { return (PathFigureCollection)GetValue(FiguresProperty); }
+ }
+
+ public FillRule FillRule
+ {
+ set { SetValue(FillRuleProperty, value); }
+ get { return (FillRule)GetValue(FillRuleProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PathGeometryConverter.cs b/Xamarin.Forms.Core/Shapes/PathGeometryConverter.cs
new file mode 100644
index 00000000000..3b79736a064
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PathGeometryConverter.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Xamarin.Forms.Shapes
+{
+ public class PathGeometryConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ PathGeometry pathGeometry = new PathGeometry();
+
+ PathFigureCollectionConverter.ParseStringToPathFigureCollection(pathGeometry.Figures, value);
+
+ return pathGeometry;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PathSegment.cs b/Xamarin.Forms.Core/Shapes/PathSegment.cs
new file mode 100644
index 00000000000..9a49c16697d
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PathSegment.cs
@@ -0,0 +1,15 @@
+namespace Xamarin.Forms.Shapes
+{
+ public abstract class PathSegment : BindableObject, IAnimatable
+ {
+ public void BatchBegin()
+ {
+
+ }
+
+ public void BatchCommit()
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PathSegmentCollection.cs b/Xamarin.Forms.Core/Shapes/PathSegmentCollection.cs
new file mode 100644
index 00000000000..f846e17ed7b
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PathSegmentCollection.cs
@@ -0,0 +1,9 @@
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms.Shapes
+{
+ public sealed class PathSegmentCollection : ObservableCollection
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PenLineCap.cs b/Xamarin.Forms.Core/Shapes/PenLineCap.cs
new file mode 100644
index 00000000000..bd3cd49e4a2
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PenLineCap.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms.Shapes
+{
+ public enum PenLineCap
+ {
+ Flat,
+ Square,
+ Round
+ }
+}
diff --git a/Xamarin.Forms.Core/Shapes/PenLineJoin.cs b/Xamarin.Forms.Core/Shapes/PenLineJoin.cs
new file mode 100644
index 00000000000..4ce3306f806
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PenLineJoin.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms.Shapes
+{
+ public enum PenLineJoin
+ {
+ Miter,
+ Bevel,
+ Round
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PointCollection.cs b/Xamarin.Forms.Core/Shapes/PointCollection.cs
new file mode 100644
index 00000000000..86f1d16cdca
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PointCollection.cs
@@ -0,0 +1,10 @@
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms.Shapes
+{
+ [TypeConverter(typeof(PointCollectionConverter))]
+ public sealed class PointCollection : ObservableCollection
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PointCollectionConverter.cs b/Xamarin.Forms.Core/Shapes/PointCollectionConverter.cs
new file mode 100644
index 00000000000..c65a01a116e
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PointCollectionConverter.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms.Shapes
+{
+ public class PointCollectionConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ string[] points = value.Split(new char[] { ' ', ',' });
+ var pointCollection = new PointCollection();
+ double x = 0;
+ bool hasX = false;
+
+ foreach (string point in points)
+ {
+ if (string.IsNullOrWhiteSpace(point))
+ continue;
+
+ if (double.TryParse(point, NumberStyles.Number, CultureInfo.InvariantCulture, out double number))
+ {
+ if (!hasX)
+ {
+ x = number;
+ hasX = true;
+ }
+ else
+ {
+ pointCollection.Add(new Point(x, number));
+ hasX = false;
+ }
+ }
+ else
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", point, typeof(double)));
+ }
+
+ if (hasX)
+ throw new InvalidOperationException(string.Format("Cannot convert string into PointCollection"));
+
+ return pointCollection;
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PolyBezierSegment.cs b/Xamarin.Forms.Core/Shapes/PolyBezierSegment.cs
new file mode 100644
index 00000000000..1326b0e8bd3
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PolyBezierSegment.cs
@@ -0,0 +1,24 @@
+namespace Xamarin.Forms.Shapes
+{
+ public sealed class PolyBezierSegment : PathSegment
+ {
+ public PolyBezierSegment()
+ {
+ Points = new PointCollection();
+ }
+
+ public PolyBezierSegment(PointCollection points)
+ {
+ Points = points;
+ }
+
+ public static readonly BindableProperty PointsProperty =
+ BindableProperty.Create(nameof(Points), typeof(PointCollection), typeof(PolyBezierSegment), null);
+
+ public PointCollection Points
+ {
+ set { SetValue(PointsProperty, value); }
+ get { return (PointCollection)GetValue(PointsProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PolyLineSegment.cs b/Xamarin.Forms.Core/Shapes/PolyLineSegment.cs
new file mode 100644
index 00000000000..4b2dbccb79a
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PolyLineSegment.cs
@@ -0,0 +1,24 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class PolyLineSegment : PathSegment
+ {
+ public PolyLineSegment()
+ {
+ Points = new PointCollection();
+ }
+
+ public PolyLineSegment(PointCollection points)
+ {
+ Points = points;
+ }
+
+ public static readonly BindableProperty PointsProperty =
+ BindableProperty.Create(nameof(Points), typeof(PointCollection), typeof(PolyLineSegment), null);
+
+ public PointCollection Points
+ {
+ set { SetValue(PointsProperty, value); }
+ get { return (PointCollection)GetValue(PointsProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/PolyQuadraticBezierSegment.cs b/Xamarin.Forms.Core/Shapes/PolyQuadraticBezierSegment.cs
new file mode 100644
index 00000000000..afdbc9e310c
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/PolyQuadraticBezierSegment.cs
@@ -0,0 +1,19 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class PolyQuadraticBezierSegment : PathSegment
+ {
+ public PolyQuadraticBezierSegment()
+ {
+ Points = new PointCollection();
+ }
+
+ public static readonly BindableProperty PointsProperty =
+ BindableProperty.Create(nameof(Points), typeof(PointCollection), typeof(PolyQuadraticBezierSegment), null);
+
+ public PointCollection Points
+ {
+ set { SetValue(PointsProperty, value); }
+ get { return (PointCollection)GetValue(PointsProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Polygon.cs b/Xamarin.Forms.Core/Shapes/Polygon.cs
new file mode 100644
index 00000000000..2d7d2cc8ad1
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Polygon.cs
@@ -0,0 +1,27 @@
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms.Shapes
+{
+ [RenderWith(typeof(_PolygonRenderer))]
+ public sealed class Polygon : Shape
+ {
+ public static readonly BindableProperty PointsProperty =
+ BindableProperty.Create(nameof(Points), typeof(PointCollection), typeof(Polygon), null, defaultValueCreator: bindable => new PointCollection());
+
+ public static readonly BindableProperty FillRuleProperty =
+ BindableProperty.Create(nameof(FillRule), typeof(FillRule), typeof(Polygon), FillRule.EvenOdd);
+
+ public PointCollection Points
+ {
+ set { SetValue(PointsProperty, value); }
+ get { return (PointCollection)GetValue(PointsProperty); }
+ }
+
+ public FillRule FillRule
+ {
+ set { SetValue(FillRuleProperty, value); }
+ get { return (FillRule)GetValue(FillRuleProperty); }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Polyline.cs b/Xamarin.Forms.Core/Shapes/Polyline.cs
new file mode 100644
index 00000000000..ed7152ef2a9
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Polyline.cs
@@ -0,0 +1,26 @@
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms.Shapes
+{
+ [RenderWith(typeof(_PolylineRenderer))]
+ public sealed class Polyline : Shape
+ {
+ public static readonly BindableProperty PointsProperty =
+ BindableProperty.Create(nameof(Points), typeof(PointCollection), typeof(Polyline), null, defaultValueCreator: bindable => new PointCollection());
+
+ public static readonly BindableProperty FillRuleProperty =
+ BindableProperty.Create(nameof(FillRule), typeof(FillRule), typeof(Polyline), FillRule.EvenOdd);
+
+ public PointCollection Points
+ {
+ set { SetValue(PointsProperty, value); }
+ get { return (PointCollection)GetValue(PointsProperty); }
+ }
+
+ public FillRule FillRule
+ {
+ set { SetValue(FillRuleProperty, value); }
+ get { return (FillRule)GetValue(FillRuleProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/QuadraticBezierSegment.cs b/Xamarin.Forms.Core/Shapes/QuadraticBezierSegment.cs
new file mode 100644
index 00000000000..074f259ecd5
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/QuadraticBezierSegment.cs
@@ -0,0 +1,34 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class QuadraticBezierSegment : PathSegment
+ {
+ public QuadraticBezierSegment()
+ {
+
+ }
+
+ public QuadraticBezierSegment(Point point1, Point point2)
+ {
+ Point1 = point1;
+ Point2 = point2;
+ }
+
+ public static readonly BindableProperty Point1Property =
+ BindableProperty.Create(nameof(Point1), typeof(Point), typeof(QuadraticBezierSegment), new Point(0, 0));
+
+ public static readonly BindableProperty Point2Property =
+ BindableProperty.Create(nameof(Point2), typeof(Point), typeof(QuadraticBezierSegment), new Point(0, 0));
+
+ public Point Point1
+ {
+ set { SetValue(Point1Property, value); }
+ get { return (Point)GetValue(Point1Property); }
+ }
+
+ public Point Point2
+ {
+ set { SetValue(Point2Property, value); }
+ get { return (Point)GetValue(Point2Property); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Rectangle.cs b/Xamarin.Forms.Core/Shapes/Rectangle.cs
new file mode 100644
index 00000000000..9e9fb2187ac
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Rectangle.cs
@@ -0,0 +1,31 @@
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms.Shapes
+{
+ [RenderWith(typeof(_RectangleRenderer))]
+ public sealed class Rectangle : Shape
+ {
+ public Rectangle()
+ {
+ Aspect = Stretch.Fill;
+ }
+
+ public static readonly BindableProperty RadiusXProperty =
+ BindableProperty.Create(nameof(RadiusX), typeof(double), typeof(Rectangle), 0.0d);
+
+ public static readonly BindableProperty RadiusYProperty =
+ BindableProperty.Create(nameof(RadiusY), typeof(double), typeof(Rectangle), 0.0d);
+
+ public double RadiusX
+ {
+ set { SetValue(RadiusXProperty, value); }
+ get { return (double)GetValue(RadiusXProperty); }
+ }
+
+ public double RadiusY
+ {
+ set { SetValue(RadiusYProperty, value); }
+ get { return (double)GetValue(RadiusYProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/RectangleGeometry.cs b/Xamarin.Forms.Core/Shapes/RectangleGeometry.cs
new file mode 100644
index 00000000000..f845a16bb71
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/RectangleGeometry.cs
@@ -0,0 +1,16 @@
+using FormsRect = Xamarin.Forms.Rectangle;
+
+namespace Xamarin.Forms.Shapes
+{
+ public class RectangleGeometry : Geometry
+ {
+ public static readonly BindableProperty RectProperty =
+ BindableProperty.Create(nameof(Rect), typeof(FormsRect), typeof(RectangleGeometry), new FormsRect());
+
+ public FormsRect Rect
+ {
+ set { SetValue(RectProperty, value); }
+ get { return (FormsRect)GetValue(RectProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/RotateTransform.cs b/Xamarin.Forms.Core/Shapes/RotateTransform.cs
new file mode 100644
index 00000000000..36607ee7dce
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/RotateTransform.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace Xamarin.Forms.Shapes
+{
+ public class RotateTransform : Transform
+ {
+ public static readonly BindableProperty AngleProperty =
+ BindableProperty.Create(nameof(Angle), typeof(double), typeof(RotateTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty CenterXProperty =
+ BindableProperty.Create(nameof(CenterX), typeof(double), typeof(RotateTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty CenterYProperty =
+ BindableProperty.Create(nameof(CenterY), typeof(double), typeof(RotateTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public double Angle
+ {
+ set { SetValue(AngleProperty, value); }
+ get { return (double)GetValue(AngleProperty); }
+ }
+
+ public double CenterX
+ {
+ set { SetValue(CenterXProperty, value); }
+ get { return (double)GetValue(CenterXProperty); }
+ }
+
+ public double CenterY
+ {
+ set { SetValue(CenterYProperty, value); }
+ get { return (double)GetValue(CenterYProperty); }
+ }
+
+ static void OnTransformPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as RotateTransform).OnTransformPropertyChanged();
+ }
+
+ void OnTransformPropertyChanged()
+ {
+ double radians = Math.PI * Angle / 180;
+ double sin = Math.Sin(radians);
+ double cos = Math.Cos(radians);
+
+ Value = new Matrix(cos, sin, -sin, cos, CenterX * (1 - cos) + CenterY * sin, CenterY * (1 - cos) - CenterX * sin);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/ScaleTransform.cs b/Xamarin.Forms.Core/Shapes/ScaleTransform.cs
new file mode 100644
index 00000000000..56580d50c63
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/ScaleTransform.cs
@@ -0,0 +1,55 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class ScaleTransform : Transform
+ {
+ public static readonly BindableProperty ScaleXProperty =
+ BindableProperty.Create(nameof(ScaleX), typeof(double), typeof(ScaleTransform), 1.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty ScaleYProperty =
+ BindableProperty.Create(nameof(ScaleY), typeof(double), typeof(ScaleTransform), 1.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty CenterXProperty =
+ BindableProperty.Create(nameof(CenterX), typeof(double), typeof(ScaleTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty CenterYProperty =
+ BindableProperty.Create(nameof(CenterY), typeof(double), typeof(ScaleTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public double ScaleX
+ {
+ set { SetValue(ScaleXProperty, value); }
+ get { return (double)GetValue(ScaleXProperty); }
+ }
+
+ public double ScaleY
+ {
+ set { SetValue(ScaleYProperty, value); }
+ get { return (double)GetValue(ScaleYProperty); }
+ }
+
+ public double CenterX
+ {
+ set { SetValue(CenterXProperty, value); }
+ get { return (double)GetValue(CenterXProperty); }
+ }
+
+ public double CenterY
+ {
+ set { SetValue(CenterYProperty, value); }
+ get { return (double)GetValue(CenterYProperty); }
+ }
+
+ static void OnTransformPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as ScaleTransform).OnTransformPropertyChanged();
+ }
+
+ void OnTransformPropertyChanged()
+ {
+ Value = new Matrix(ScaleX, 0, 0, ScaleY, CenterX * (1 - ScaleX), CenterY * (1 - ScaleY));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Shape.cs b/Xamarin.Forms.Core/Shapes/Shape.cs
new file mode 100644
index 00000000000..228604785d8
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Shape.cs
@@ -0,0 +1,83 @@
+namespace Xamarin.Forms.Shapes
+{
+ public abstract class Shape : View
+ {
+ public Shape()
+ {
+ ExperimentalFlags.VerifyFlagEnabled(nameof(Shape), ExperimentalFlags.ShapesExperimental);
+ }
+
+ public static readonly BindableProperty FillProperty =
+ BindableProperty.Create(nameof(Fill), typeof(Color), typeof(Shape), null);
+
+ public static readonly BindableProperty StrokeProperty =
+ BindableProperty.Create(nameof(Stroke), typeof(Color), typeof(Shape), null);
+
+ public static readonly BindableProperty StrokeThicknessProperty =
+ BindableProperty.Create(nameof(StrokeThickness), typeof(double), typeof(Shape), 1.0);
+
+ public static readonly BindableProperty StrokeDashArrayProperty =
+ BindableProperty.Create(nameof(StrokeDashArray), typeof(DoubleCollection), typeof(Shape), null,
+ defaultValueCreator: bindable => new DoubleCollection());
+
+ public static readonly BindableProperty StrokeDashOffsetProperty =
+ BindableProperty.Create(nameof(StrokeDashOffset), typeof(double), typeof(Shape), 0.0);
+
+ public static readonly BindableProperty StrokeLineCapProperty =
+ BindableProperty.Create(nameof(StrokeLineCap), typeof(PenLineCap), typeof(Shape), PenLineCap.Flat);
+
+ public static readonly BindableProperty StrokeLineJoinProperty =
+ BindableProperty.Create(nameof(StrokeLineJoin), typeof(PenLineJoin), typeof(Shape), PenLineJoin.Miter);
+
+ public static readonly BindableProperty AspectProperty =
+ BindableProperty.Create(nameof(Aspect), typeof(Stretch), typeof(Shape), Stretch.None);
+
+ public Color Fill
+ {
+ set { SetValue(FillProperty, value); }
+ get { return (Color)GetValue(FillProperty); }
+ }
+
+ public Color Stroke
+ {
+ set { SetValue(StrokeProperty, value); }
+ get { return (Color)GetValue(StrokeProperty); }
+ }
+
+ public double StrokeThickness
+ {
+ set { SetValue(StrokeThicknessProperty, value); }
+ get { return (double)GetValue(StrokeThicknessProperty); }
+ }
+
+ public DoubleCollection StrokeDashArray
+ {
+ set { SetValue(StrokeDashArrayProperty, value); }
+ get { return (DoubleCollection)GetValue(StrokeDashArrayProperty); }
+ }
+
+ public double StrokeDashOffset
+ {
+ set { SetValue(StrokeDashOffsetProperty, value); }
+ get { return (double)GetValue(StrokeDashOffsetProperty); }
+ }
+
+ public PenLineCap StrokeLineCap
+ {
+ set { SetValue(StrokeLineCapProperty, value); }
+ get { return (PenLineCap)GetValue(StrokeLineCapProperty); }
+ }
+
+ public PenLineJoin StrokeLineJoin
+ {
+ set { SetValue(StrokeLineJoinProperty, value); }
+ get { return (PenLineJoin)GetValue(StrokeLineJoinProperty); }
+ }
+
+ public Stretch Aspect
+ {
+ set { SetValue(AspectProperty, value); }
+ get { return (Stretch)GetValue(AspectProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/SkewTransform.cs b/Xamarin.Forms.Core/Shapes/SkewTransform.cs
new file mode 100644
index 00000000000..dd60efb841e
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/SkewTransform.cs
@@ -0,0 +1,62 @@
+using System;
+
+namespace Xamarin.Forms.Shapes
+{
+ public class SkewTransform : Transform
+ {
+ public static readonly BindableProperty AngleXProperty =
+ BindableProperty.Create(nameof(AngleX), typeof(double), typeof(SkewTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty AngleYProperty =
+ BindableProperty.Create(nameof(AngleY), typeof(double), typeof(SkewTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty CenterXProperty =
+ BindableProperty.Create(nameof(CenterX), typeof(double), typeof(SkewTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty CenterYProperty =
+ BindableProperty.Create(nameof(CenterY), typeof(double), typeof(SkewTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public double AngleX
+ {
+ set { SetValue(AngleXProperty, value); }
+ get { return (double)GetValue(AngleXProperty); }
+ }
+
+ public double AngleY
+ {
+ set { SetValue(AngleYProperty, value); }
+ get { return (double)GetValue(AngleYProperty); }
+ }
+
+ public double CenterX
+ {
+ set { SetValue(CenterXProperty, value); }
+ get { return (double)GetValue(CenterXProperty); }
+ }
+
+ public double CenterY
+ {
+ set { SetValue(CenterYProperty, value); }
+ get { return (double)GetValue(CenterYProperty); }
+ }
+
+ static void OnTransformPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as SkewTransform).OnTransformPropertyChanged();
+ }
+
+ void OnTransformPropertyChanged()
+ {
+ double radiansX = Math.PI * AngleX / 180;
+ double radiansY = Math.PI * AngleY / 180;
+ double tanX = Math.Tan(radiansX);
+ double tanY = Math.Tan(radiansY);
+
+ Value = new Matrix(1, tanY, tanX, 1, -CenterY * tanX, -CenterX * tanY);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Transform.cs b/Xamarin.Forms.Core/Shapes/Transform.cs
new file mode 100644
index 00000000000..9a38d97118a
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Transform.cs
@@ -0,0 +1,15 @@
+namespace Xamarin.Forms.Shapes
+{
+ [TypeConverter(typeof(TransformTypeConverter))]
+ public class Transform : BindableObject
+ {
+ internal static readonly BindableProperty ValueProperty =
+ BindableProperty.Create(nameof(Value), typeof(Matrix), typeof(Transform), new Matrix());
+
+ public Matrix Value
+ {
+ set { SetValue(ValueProperty, value); }
+ get { return (Matrix)GetValue(ValueProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/TransformCollection.cs b/Xamarin.Forms.Core/Shapes/TransformCollection.cs
new file mode 100644
index 00000000000..b6dd09d3b16
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/TransformCollection.cs
@@ -0,0 +1,9 @@
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms.Shapes
+{
+ public sealed class TransformCollection : ObservableCollection
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/TransformGroup.cs b/Xamarin.Forms.Core/Shapes/TransformGroup.cs
new file mode 100644
index 00000000000..f943b270813
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/TransformGroup.cs
@@ -0,0 +1,71 @@
+using System.Collections.Specialized;
+using System.ComponentModel;
+
+namespace Xamarin.Forms.Shapes
+{
+ [ContentProperty("Children")]
+ public sealed class TransformGroup : Transform
+ {
+ public static readonly BindableProperty ChildrenProperty =
+ BindableProperty.Create(nameof(Children), typeof(TransformCollection), typeof(TransformGroup), null,
+ propertyChanged: OnTransformGroupChanged);
+
+ public TransformGroup()
+ {
+ Children = new TransformCollection();
+ }
+
+ public TransformCollection Children
+ {
+ set { SetValue(ChildrenProperty, value); }
+ get { return (TransformCollection)GetValue(ChildrenProperty); }
+ }
+
+ static void OnTransformGroupChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (oldValue != null)
+ {
+ (oldValue as TransformCollection).CollectionChanged -= (bindable as TransformGroup).OnChildrenCollectionChanged;
+ }
+
+ if (newValue != null)
+ {
+ (newValue as TransformCollection).CollectionChanged += (bindable as TransformGroup).OnChildrenCollectionChanged;
+ }
+
+ (bindable as TransformGroup).UpdateTransformMatrix();
+ }
+
+ void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {
+ if (args.NewItems != null)
+ foreach (INotifyPropertyChanged item in args.NewItems)
+ {
+ item.PropertyChanged += OnTransformPropertyChanged;
+ }
+
+ if (args.OldItems != null)
+ foreach (INotifyPropertyChanged item in args.OldItems)
+ {
+ item.PropertyChanged -= OnTransformPropertyChanged;
+ }
+
+ UpdateTransformMatrix();
+ }
+
+ void OnTransformPropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ UpdateTransformMatrix();
+ }
+
+ void UpdateTransformMatrix()
+ {
+ var matrix = new Matrix();
+
+ foreach (Transform child in Children)
+ matrix = Matrix.Multiply(matrix, child.Value);
+
+ Value = matrix;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/TransformTypeConverter.cs b/Xamarin.Forms.Core/Shapes/TransformTypeConverter.cs
new file mode 100644
index 00000000000..ff46b919d80
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/TransformTypeConverter.cs
@@ -0,0 +1,13 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class TransformTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ return new MatrixTransform
+ {
+ Matrix = MatrixTypeConverter.CreateMatrix(value)
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/TranslateTransform.cs b/Xamarin.Forms.Core/Shapes/TranslateTransform.cs
new file mode 100644
index 00000000000..eb26f95f9d3
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/TranslateTransform.cs
@@ -0,0 +1,35 @@
+namespace Xamarin.Forms.Shapes
+{
+ public class TranslateTransform : Transform
+ {
+ public static readonly BindableProperty XProperty =
+ BindableProperty.Create(nameof(X), typeof(double), typeof(TranslateTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public static readonly BindableProperty YProperty =
+ BindableProperty.Create(nameof(Y), typeof(double), typeof(TranslateTransform), 0.0,
+ propertyChanged: OnTransformPropertyChanged);
+
+ public double X
+ {
+ set { SetValue(XProperty, value); }
+ get { return (double)GetValue(XProperty); }
+ }
+
+ public double Y
+ {
+ set { SetValue(YProperty, value); }
+ get { return (double)GetValue(YProperty); }
+ }
+
+ static void OnTransformPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as TranslateTransform).OnTransformPropertyChanged();
+ }
+
+ void OnTransformPropertyChanged()
+ {
+ Value = new Matrix(1, 0, 0, 1, X, Y);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shapes/Vector2.cs b/Xamarin.Forms.Core/Shapes/Vector2.cs
new file mode 100644
index 00000000000..49f6a6903fe
--- /dev/null
+++ b/Xamarin.Forms.Core/Shapes/Vector2.cs
@@ -0,0 +1,117 @@
+using System;
+using System.ComponentModel;
+
+namespace Xamarin.Forms.Shapes
+{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public struct Vector2
+ {
+ public Vector2(double x, double y)
+ : this()
+ {
+ X = x;
+ Y = y;
+ }
+
+ public Vector2(Point p)
+ : this()
+ {
+ X = p.X;
+ Y = p.Y;
+ }
+
+ public Vector2(double angle)
+ : this()
+ {
+ X = Math.Cos(Math.PI * angle / 180);
+ Y = Math.Sin(Math.PI * angle / 180);
+ }
+
+ public double X { private set; get; }
+ public double Y { private set; get; }
+
+ public double LengthSquared
+ {
+ get { return X * X + Y * Y; }
+ }
+
+ public double Length
+ {
+ get { return Math.Sqrt(LengthSquared); }
+ }
+
+ public Vector2 Normalized
+ {
+ get
+ {
+ double length = Length;
+
+ if (length != 0)
+ {
+ return new Vector2(X / length, Y / length);
+ }
+ return new Vector2();
+ }
+ }
+
+ public static double AngleBetween(Vector2 v1, Vector2 v2)
+ {
+ return 180 * (Math.Atan2(v2.Y, v2.X) - Math.Atan2(v1.Y, v1.X)) / Math.PI;
+ }
+
+ public static Vector2 operator +(Vector2 v1, Vector2 v2)
+ {
+ return new Vector2(v1.X + v2.X, v1.Y + v2.Y);
+ }
+
+ public static Point operator +(Vector2 v, Point p)
+ {
+ return new Point(v.X + p.X, v.Y + p.Y);
+ }
+
+ public static Point operator +(Point p, Vector2 v)
+ {
+ return new Point(v.X + p.X, v.Y + p.Y);
+ }
+
+ public static Vector2 operator -(Vector2 v1, Vector2 v2)
+ {
+ return new Vector2(v1.X - v2.X, v1.Y - v2.Y);
+ }
+
+ public static Point operator -(Point p, Vector2 v)
+ {
+ return new Point(p.X - v.X, p.Y - v.Y);
+ }
+
+ public static Vector2 operator *(Vector2 v, double d)
+ {
+ return new Vector2(d * v.X, d * v.Y);
+ }
+
+ public static Vector2 operator *(double d, Vector2 v)
+ {
+ return new Vector2(d * v.X, d * v.Y);
+ }
+
+ public static Vector2 operator /(Vector2 v, double d)
+ {
+ return new Vector2(v.X / d, v.Y / d);
+ }
+
+ public static Vector2 operator -(Vector2 v)
+ {
+ return new Vector2(-v.X, -v.Y);
+ }
+
+ public static explicit operator Point(Vector2 v)
+ {
+ return new Point(v.X, v.Y);
+ }
+
+ public override string ToString()
+ {
+ return string.Format("({0} {1})", X, Y);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shell/BaseShellItem.cs b/Xamarin.Forms.Core/Shell/BaseShellItem.cs
index f4034eae247..d06396ced4f 100644
--- a/Xamarin.Forms.Core/Shell/BaseShellItem.cs
+++ b/Xamarin.Forms.Core/Shell/BaseShellItem.cs
@@ -151,14 +151,14 @@ internal void OnAppearing(Action action)
action();
else
{
- if(Navigation.ModalStack.Count > 0)
+ if (Navigation.ModalStack.Count > 0)
{
Navigation.ModalStack[Navigation.ModalStack.Count - 1]
.OnAppearing(action);
-
+
return;
}
- else if(Navigation.NavigationStack.Count > 1)
+ else if (Navigation.NavigationStack.Count > 1)
{
Navigation.NavigationStack[Navigation.NavigationStack.Count - 1]
.OnAppearing(action);
@@ -320,9 +320,9 @@ internal static DataTemplate CreateDefaultFlyoutItemCell(IStyleSelectable styleS
Class = DefaultFlyoutItemLayoutStyle,
};
-
+
var groups = new VisualStateGroupList();
-
+
var commonGroup = new VisualStateGroup();
commonGroup.Name = "CommonStates";
groups.Add(commonGroup);
@@ -381,7 +381,7 @@ internal static DataTemplate CreateDefaultFlyoutItemCell(IStyleSelectable styleS
if (Device.RuntimePlatform == Device.UWP)
{
defaultImageClass.Setters.Add(new Setter { Property = Image.HorizontalOptionsProperty, Value = LayoutOptions.Start });
- defaultImageClass.Setters.Add(new Setter { Property = Image.MarginProperty, Value = new Thickness(12, 0, 12, 0) });
+ defaultImageClass.Setters.Add(new Setter { Property = Image.MarginProperty, Value = new Thickness(12, 0, 12, 0) });
}
Binding imageBinding = new Binding(iconBinding);
diff --git a/Xamarin.Forms.Core/Shell/IShellItemController.cs b/Xamarin.Forms.Core/Shell/IShellItemController.cs
index d72a31a8893..876663d2547 100644
--- a/Xamarin.Forms.Core/Shell/IShellItemController.cs
+++ b/Xamarin.Forms.Core/Shell/IShellItemController.cs
@@ -9,5 +9,6 @@ public interface IShellItemController : IElementController
ReadOnlyCollection GetItems();
event NotifyCollectionChangedEventHandler ItemsCollectionChanged;
+ bool ShowTabs { get; }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shell/Shell.cs b/Xamarin.Forms.Core/Shell/Shell.cs
index 8f74ead2e42..edacccf4c59 100644
--- a/Xamarin.Forms.Core/Shell/Shell.cs
+++ b/Xamarin.Forms.Core/Shell/Shell.cs
@@ -146,6 +146,10 @@ static void OnFlyoutBehaviorChanged(BindableObject bindable, object oldValue, ob
BindableProperty.CreateAttached("UnselectedColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnColorValueChanged);
+ //public static readonly BindableProperty FlyoutBackdropColorProperty =
+ // BindableProperty.CreateAttached("FlyoutBackdropColor", typeof(Color), typeof(Shell), Color.Default,
+ // propertyChanged: OnColorValueChanged);
+
public static Color GetBackgroundColor(BindableObject obj) => (Color)obj.GetValue(BackgroundColorProperty);
public static void SetBackgroundColor(BindableObject obj, Color value) => obj.SetValue(BackgroundColorProperty, value);
@@ -176,6 +180,9 @@ static void OnFlyoutBehaviorChanged(BindableObject bindable, object oldValue, ob
public static Color GetUnselectedColor(BindableObject obj) => (Color)obj.GetValue(UnselectedColorProperty);
public static void SetUnselectedColor(BindableObject obj, Color value) => obj.SetValue(UnselectedColorProperty, value);
+ //public static Color GetFlyoutBackdropColor(BindableObject obj) => (Color)obj.GetValue(FlyoutBackdropColorProperty);
+ //public static void SetFlyoutBackdropColor(BindableObject obj, Color value) => obj.SetValue(FlyoutBackdropColorProperty, value);
+
static void OnColorValueChanged(BindableObject bindable, object oldValue, object newValue)
{
var item = (Element)bindable;
@@ -316,6 +323,7 @@ void IShellController.AppearanceChanged(Element source, bool appearanceSet)
observer.OnAppearanceChanged(GetAppearanceForPivot(pivot));
break;
}
+
leaf = leaf.Parent;
}
}
@@ -425,7 +433,7 @@ void IShellController.UpdateCurrentState(ShellNavigationSource source)
ProcessNavigated(new ShellNavigatedEventArgs(oldState, CurrentState, source));
}
ReadOnlyCollection IShellController.GetItems() =>
- new ReadOnlyCollection(((ShellItemCollection)Items).VisibleItems.ToList());
+ new ReadOnlyCollection(((ShellItemCollection)Items).VisibleItemsReadOnly.ToList());
event NotifyCollectionChangedEventHandler IShellController.ItemsCollectionChanged
{
@@ -623,28 +631,24 @@ internal static void ApplyQueryAttributes(Element element, IDictionary sectionStack, IReadOnlyList modalStack)
{
- StringBuilder stateBuilder = new StringBuilder($"//");
- Dictionary queryData = new Dictionary();
+ List routeStack = new List();
bool stackAtRoot = sectionStack == null || sectionStack.Count <= 1;
if (shellItem != null)
{
var shellItemRoute = shellItem.Route;
- stateBuilder.Append(shellItemRoute);
- stateBuilder.Append("/");
+ routeStack.Add(shellItemRoute);
if (shellSection != null)
{
var shellSectionRoute = shellSection.Route;
- stateBuilder.Append(shellSectionRoute);
- stateBuilder.Append("/");
+ routeStack.Add(shellSectionRoute);
if (shellContent != null)
{
var shellContentRoute = shellContent.Route;
- stateBuilder.Append(shellContentRoute);
- stateBuilder.Append("/");
+ routeStack.Add(shellContentRoute);
}
if (!stackAtRoot)
@@ -652,37 +656,63 @@ ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellS
for (int i = 1; i < sectionStack.Count; i++)
{
var page = sectionStack[i];
- stateBuilder.Append(Routing.GetRoute(page));
- if (i < sectionStack.Count - 1)
- stateBuilder.Append("/");
+ routeStack.AddRange(CollapsePath(Routing.GetRoute(page), routeStack));
}
}
if (modalStack != null && modalStack.Count > 0)
{
- if (!stackAtRoot && sectionStack.Count > 0)
- stateBuilder.Append("/");
-
for (int i = 0; i < modalStack.Count; i++)
{
var topPage = modalStack[i];
- if (i > 0)
- stateBuilder.Append("/");
-
- stateBuilder.Append(Routing.GetRoute(topPage));
+ routeStack.AddRange(CollapsePath(Routing.GetRoute(topPage), routeStack));
for (int j = 1; j < topPage.Navigation.NavigationStack.Count; j++)
{
- stateBuilder.Append("/");
- stateBuilder.Append(Routing.GetRoute(topPage.Navigation.NavigationStack[j]));
+ routeStack.AddRange(CollapsePath(Routing.GetRoute(topPage.Navigation.NavigationStack[j]), routeStack));
}
}
}
}
}
- return stateBuilder.ToString();
+ if(routeStack.Count > 0)
+ routeStack.Insert(0, "/");
+
+ return String.Join("/", routeStack);
+
+
+ List CollapsePath(string myRoute, List currentRouteStack)
+ {
+ for (var i = currentRouteStack.Count - 1; i >= 0; i--)
+ {
+ var route = currentRouteStack[i];
+ if (Routing.IsImplicit(route) || Routing.IsDefault(route))
+ currentRouteStack.RemoveAt(i);
+ }
+
+ var paths = myRoute.Split('/').ToList();
+
+ // collapse similar leaves
+ int walkBackCurrentStackIndex = currentRouteStack.Count - (paths.Count - 1);
+
+ while(paths.Count > 1 && walkBackCurrentStackIndex >= 0)
+ {
+ if (paths[0] == currentRouteStack[walkBackCurrentStackIndex])
+ {
+ paths.RemoveAt(0);
+ }
+ else
+ {
+ break;
+ }
+
+ walkBackCurrentStackIndex++;
+ }
+
+ return paths;
+ }
}
public static readonly BindableProperty CurrentItemProperty =
@@ -739,7 +769,7 @@ void Initialize()
if (CurrentItem != null)
SetCurrentItem();
- ShellController.ItemsCollectionChanged += (s, e) =>
+ ((ShellElementCollection)Items).VisibleItemsChangedInternal += (s, e) =>
{
SetCurrentItem();
SendStructureChanged();
@@ -835,6 +865,12 @@ public Color FlyoutBackgroundColor
set => SetValue(FlyoutBackgroundColorProperty, value);
}
+ //public Color FlyoutBackdropColor
+ //{
+ // get => (Color)GetValue(FlyoutBackdropColorProperty);
+ // set => SetValue(FlyoutBackdropColorProperty, value);
+ //}
+
public FlyoutBehavior FlyoutBehavior
{
get => (FlyoutBehavior)GetValue(FlyoutBehaviorProperty);
@@ -1079,12 +1115,16 @@ static void OnCurrentItemChanged(BindableObject bindable, object oldValue, objec
static void OnCurrentItemChanging(BindableObject bindable, object oldValue, object newValue)
{
var shell = (Shell)bindable;
+ var shellItem = (ShellItem)newValue;
+
+ if (!shell.Items.Contains(shellItem))
+ shell.Items.Add(shellItem);
+
if (!shell._accumulateNavigatedEvents)
{
// We are not in the middle of a GoToAsync so this is a user requested change.
// We need to emit the Navigating event since GoToAsync wont be emitting it.
- var shellItem = (ShellItem)newValue;
var shellSection = shellItem.CurrentItem;
var shellContent = shellSection.CurrentItem;
var stack = shellSection.Stack;
@@ -1200,15 +1240,14 @@ internal FlyoutBehavior GetEffectiveFlyoutBehavior()
(o) => rootItem = rootItem ?? o as ShellItem);
}
- T GetEffectiveValue(BindableProperty property, T defaultValue)
+ internal T GetEffectiveValue(BindableProperty property, T defaultValue)
{
- return GetEffectiveValue(property, () => defaultValue, null);
+ return GetEffectiveValue(property, () => defaultValue, null);
}
-
- T GetEffectiveValue(BindableProperty property, Func defaultValue, Action observer)
- {
- Element element = GetVisiblePage() ?? CurrentContent;
+ internal T GetEffectiveValue(BindableProperty property, Func defaultValue, Action observer, Element element = null)
+ {
+ element = element ?? GetVisiblePage() ?? CurrentContent;
while (element != this && element != null)
{
observer?.Invoke(element);
diff --git a/Xamarin.Forms.Core/Shell/ShellAppearance.cs b/Xamarin.Forms.Core/Shell/ShellAppearance.cs
index bf096cd28f4..b98fcfd1260 100644
--- a/Xamarin.Forms.Core/Shell/ShellAppearance.cs
+++ b/Xamarin.Forms.Core/Shell/ShellAppearance.cs
@@ -17,7 +17,8 @@ public class ShellAppearance : IShellAppearanceElement
Shell.TabBarTitleColorProperty,
Shell.TabBarUnselectedColorProperty,
Shell.TitleColorProperty,
- Shell.UnselectedColorProperty
+ Shell.UnselectedColorProperty,
+ //Shell.FlyoutBackdropColorProperty
};
Color?[] _colorArray = new Color?[s_ingestArray.Length];
@@ -42,6 +43,8 @@ public class ShellAppearance : IShellAppearanceElement
public Color UnselectedColor => _colorArray[9].Value;
+ //public Color FlyoutBackdropColor => _colorArray[10].Value;
+
Color IShellAppearanceElement.EffectiveTabBarBackgroundColor =>
!TabBarBackgroundColor.IsDefault ? TabBarBackgroundColor : BackgroundColor;
@@ -64,33 +67,24 @@ internal ShellAppearance()
public override bool Equals(object obj)
{
- var appearance = obj as ShellAppearance;
- return appearance != null &&
- EqualityComparer.Default.Equals(BackgroundColor, appearance.BackgroundColor) &&
- EqualityComparer.Default.Equals(DisabledColor, appearance.DisabledColor) &&
- EqualityComparer.Default.Equals(ForegroundColor, appearance.ForegroundColor) &&
- EqualityComparer.Default.Equals(TabBarBackgroundColor, appearance.TabBarBackgroundColor) &&
- EqualityComparer.Default.Equals(TabBarDisabledColor, appearance.TabBarDisabledColor) &&
- EqualityComparer.Default.Equals(TabBarForegroundColor, appearance.TabBarForegroundColor) &&
- EqualityComparer.Default.Equals(TabBarTitleColor, appearance.TabBarTitleColor) &&
- EqualityComparer.Default.Equals(TabBarUnselectedColor, appearance.TabBarUnselectedColor) &&
- EqualityComparer.Default.Equals(TitleColor, appearance.TitleColor) &&
- EqualityComparer.Default.Equals(UnselectedColor, appearance.UnselectedColor);
+ if(!(obj is ShellAppearance appearance))
+ return false;
+
+ for(int i = 0; i < _colorArray.Length; i++)
+ {
+ if (!EqualityComparer.Default.Equals(_colorArray[i].Value, appearance._colorArray[i].Value))
+ return false;
+ }
+
+ return true;
}
public override int GetHashCode()
{
var hashCode = -1988429770;
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(BackgroundColor);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(DisabledColor);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ForegroundColor);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TabBarBackgroundColor);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TabBarDisabledColor);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TabBarForegroundColor);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TabBarTitleColor);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TabBarUnselectedColor);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TitleColor);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(UnselectedColor);
+ for (int i = 0; i < _colorArray.Length; i++)
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_colorArray[i].Value);
+
return hashCode;
}
diff --git a/Xamarin.Forms.Core/Shell/ShellContentCollection.cs b/Xamarin.Forms.Core/Shell/ShellContentCollection.cs
index b0d50afd67c..ecfc476d0dc 100644
--- a/Xamarin.Forms.Core/Shell/ShellContentCollection.cs
+++ b/Xamarin.Forms.Core/Shell/ShellContentCollection.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -7,83 +6,17 @@
namespace Xamarin.Forms
{
- internal sealed class ShellContentCollection : IList, INotifyCollectionChanged
+ internal sealed class ShellContentCollection : ShellElementCollection
{
- public event NotifyCollectionChangedEventHandler VisibleItemsChanged;
- public event NotifyCollectionChangedEventHandler VisibleItemsChangedInternal;
- public ReadOnlyCollection VisibleItems { get; }
-
- ObservableCollection _inner = new ObservableCollection();
- ObservableCollection _visibleContents = new ObservableCollection();
- bool _pauseCollectionChanged;
- List _notifyCollectionChangedEventArgs;
-
- public ShellContentCollection()
- {
- _notifyCollectionChangedEventArgs = new List();
- _inner.CollectionChanged += InnerCollectionChanged;
- VisibleItems = new ReadOnlyCollection(_visibleContents);
- _visibleContents.CollectionChanged += (_, args) =>
- {
- if(_pauseCollectionChanged)
- {
- _notifyCollectionChangedEventArgs.Add(args);
- return;
- }
-
- OnVisibleItemsChanged(args);
- };
- }
-
- void OnVisibleItemsChanged(NotifyCollectionChangedEventArgs args)
- {
- VisibleItemsChangedInternal?.Invoke(VisibleItems, args);
- VisibleItemsChanged?.Invoke(VisibleItems, args);
- }
-
- void PauseCollectionChanged() => _pauseCollectionChanged = true;
-
- void ResumeCollectionChanged()
+ public ShellContentCollection() : base()
{
- _pauseCollectionChanged = false;
-
- var pendingEvents = _notifyCollectionChangedEventArgs.ToList();
- _notifyCollectionChangedEventArgs.Clear();
-
- foreach(var args in pendingEvents)
- OnVisibleItemsChanged(args);
+ Inner = new ObservableCollection();
}
- void InnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ protected override bool IsShellElementVisible(BaseShellItem item)
{
- if (e.NewItems != null)
- {
- foreach (ShellContent element in e.NewItems)
- {
- if (element is IShellContentController controller)
- controller.IsPageVisibleChanged += OnIsPageVisibleChanged;
- CheckVisibility(element);
- }
- }
-
- if (e.OldItems != null)
- {
- Removing(e.OldItems);
- }
-
- CollectionChanged?.Invoke(this, e);
- }
-
- void Removing(IEnumerable items)
- {
- foreach (ShellContent element in items)
- {
- if (_visibleContents.Contains(element))
- _visibleContents.Remove(element);
-
- if (element is IShellContentController controller)
- controller.IsPageVisibleChanged -= OnIsPageVisibleChanged;
- }
+ IShellContentController controller = (IShellContentController)item;
+ return controller.Page == null || controller.Page.IsVisible;
}
void OnIsPageVisibleChanged(object sender, EventArgs e)
@@ -91,87 +24,16 @@ void OnIsPageVisibleChanged(object sender, EventArgs e)
CheckVisibility((ShellContent)sender);
}
- void CheckVisibility(ShellContent shellContent)
+ protected override void OnElementControllerInserting(IElementController element)
{
- if (shellContent is IShellContentController controller)
- {
- // Assume incoming page will be visible
- if (controller.Page == null || controller.Page.IsVisible)
- {
- if (_visibleContents.Contains(shellContent))
- return;
-
- int visibleIndex = 0;
- for (var i = 0; i < _inner.Count; i++)
- {
- var item = _inner[i];
-
- if (item == shellContent)
- {
- _visibleContents.Insert(visibleIndex, shellContent);
- break;
- }
-
- visibleIndex++;
- }
- }
- else
- {
- _visibleContents.Remove(shellContent);
- }
- }
- else if (_visibleContents.Contains(shellContent))
- {
- _visibleContents.Remove(shellContent);
- }
+ if (element is IShellContentController controller)
+ controller.IsPageVisibleChanged += OnIsPageVisibleChanged;
}
- public event NotifyCollectionChangedEventHandler CollectionChanged;
-
- public int Count => _inner.Count;
-
- public bool IsReadOnly => ((IList)_inner).IsReadOnly;
-
- public ShellContent this[int index]
+ protected override void OnElementControllerRemoving(IElementController element)
{
- get => _inner[index];
- set => _inner[index] = value;
- }
-
- public void Add(ShellContent item) => _inner.Add(item);
-
- public void Clear()
- {
- var list = _inner.ToList();
- try
- {
- PauseCollectionChanged();
- Removing(_inner);
- }
- finally
- {
- ResumeCollectionChanged();
- }
-
- _inner.Clear();
-
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, list));
+ if (element is IShellContentController controller)
+ controller.IsPageVisibleChanged -= OnIsPageVisibleChanged;
}
-
- public bool Contains(ShellContent item) => _inner.Contains(item);
-
- public void CopyTo(ShellContent[] array, int arrayIndex) => _inner.CopyTo(array, arrayIndex);
-
- public IEnumerator GetEnumerator() => _inner.GetEnumerator();
-
- public int IndexOf(ShellContent item) => _inner.IndexOf(item);
-
- public void Insert(int index, ShellContent item) => _inner.Insert(index, item);
-
- public bool Remove(ShellContent item) => _inner.Remove(item);
-
- public void RemoveAt(int index) => _inner.RemoveAt(index);
-
- IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_inner).GetEnumerator();
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shell/ShellElementCollection.cs b/Xamarin.Forms.Core/Shell/ShellElementCollection.cs
new file mode 100644
index 00000000000..c32be7033ca
--- /dev/null
+++ b/Xamarin.Forms.Core/Shell/ShellElementCollection.cs
@@ -0,0 +1,316 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+
+ internal abstract class ShellElementCollection :
+ IList,
+ INotifyCollectionChanged
+ {
+ public event NotifyCollectionChangedEventHandler VisibleItemsChangedInternal;
+ readonly List _notifyCollectionChangedEventArgs;
+ bool _pauseCollectionChanged;
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+ public event NotifyCollectionChangedEventHandler VisibleItemsChanged;
+ public int Count => Inner.Count;
+ public bool IsReadOnly => Inner.IsReadOnly;
+ IList _inner;
+ IList _visibleItems;
+
+ protected ShellElementCollection()
+ {
+ _notifyCollectionChangedEventArgs = new List();
+ }
+
+ internal IList Inner
+ {
+ get => _inner;
+ private protected set
+ {
+ if (_inner != null)
+ throw new ArgumentException("Inner can only be set once");
+
+ _inner = value;
+ ((INotifyCollectionChanged)_inner).CollectionChanged += InnerCollectionChanged;
+ }
+ }
+
+ protected void OnVisibleItemsChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {
+ if (args?.NewItems?.Count > 0 && _pauseCollectionChanged)
+ {
+ _notifyCollectionChangedEventArgs.Add(args);
+ ResumeCollectionChanged();
+ return;
+ }
+
+ if (_pauseCollectionChanged)
+ {
+ _notifyCollectionChangedEventArgs.Add(args);
+ return;
+ }
+
+ VisibleItemsChangedInternal?.Invoke(VisibleItemsReadOnly, args);
+ VisibleItemsChanged?.Invoke(VisibleItemsReadOnly, args);
+ }
+
+ protected IList VisibleItems
+ {
+ get => _visibleItems;
+ private protected set
+ {
+ _visibleItems = value;
+ ((INotifyCollectionChanged)_visibleItems).CollectionChanged += OnVisibleItemsChanged;
+ }
+ }
+
+ public IReadOnlyCollection VisibleItemsReadOnly
+ {
+ get;
+ private protected set;
+ }
+
+ // Pause Collection Changed events when the list has zero items
+ // we don't want to propagate out a visible collection changed event until the next visible item
+ // is realized
+ void PauseCollectionChanged() => _pauseCollectionChanged = true;
+
+ void ResumeCollectionChanged()
+ {
+ _pauseCollectionChanged = false;
+
+ // process the added items first and then remove
+ var pendingEvents = _notifyCollectionChangedEventArgs.OrderBy(x => x.NewItems != null ? 0 : 1).ToList();
+ _notifyCollectionChangedEventArgs.Clear();
+
+ foreach (var args in pendingEvents)
+ VisibleItemsChangedInternal?.Invoke(VisibleItemsReadOnly, args);
+
+ foreach (var args in pendingEvents)
+ VisibleItemsChanged?.Invoke(VisibleItemsReadOnly, args);
+ }
+
+ #region IList
+
+ public BaseShellItem this[int index]
+ {
+ get => (BaseShellItem)Inner[index];
+ set => Inner[index] = value;
+ }
+
+ public void Clear()
+ {
+ var list = Inner.Cast().ToList();
+ try
+ {
+ PauseCollectionChanged();
+ Removing(Inner);
+ }
+ finally
+ {
+ ResumeCollectionChanged();
+ }
+
+ Inner.Clear();
+ CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, list));
+ }
+
+ public virtual void Add(BaseShellItem item) => Inner.Add(item);
+
+ public virtual bool Contains(BaseShellItem item) => Inner.Contains(item);
+
+ public virtual void CopyTo(BaseShellItem[] array, int arrayIndex) => Inner.CopyTo(array, arrayIndex);
+
+ public abstract IEnumerator GetEnumerator();
+
+ public virtual int IndexOf(BaseShellItem item) => Inner.IndexOf(item);
+
+ public virtual void Insert(int index, BaseShellItem item) => Inner.Insert(index, item);
+
+ public abstract bool Remove(BaseShellItem item);
+
+ public virtual void RemoveAt(int index) => Inner.RemoveAt(index);
+
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Inner).GetEnumerator();
+
+ #endregion
+
+ void InnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.NewItems != null)
+ {
+ foreach (BaseShellItem element in e.NewItems)
+ {
+ if (element is IElementController controller)
+ OnElementControllerInserting(controller);
+
+ CheckVisibility(element);
+ }
+ }
+
+ if (e.OldItems != null)
+ {
+ Removing(e.OldItems);
+ }
+
+ CollectionChanged?.Invoke(this, e);
+ }
+
+ void Removing(IEnumerable items)
+ {
+ foreach (BaseShellItem element in items)
+ {
+ if (VisibleItems.Contains(element))
+ VisibleItems.Remove(element);
+
+ if (element is IElementController controller)
+ OnElementControllerRemoving(controller);
+ }
+ }
+
+ protected virtual void CheckVisibility(BaseShellItem element)
+ {
+ if (IsShellElementVisible(element))
+ {
+ if (VisibleItems.Contains(element))
+ return;
+
+ int visibleIndex = 0;
+ for (var i = 0; i < Inner.Count; i++)
+ {
+ var item = Inner[i];
+
+ if (!IsShellElementVisible(element))
+ continue;
+
+ if (item == element)
+ {
+ VisibleItems.Insert(visibleIndex, element);
+ break;
+ }
+
+ if (VisibleItems.Contains(item))
+ visibleIndex++;
+ }
+ }
+ else if (VisibleItems.Contains(element))
+ {
+ VisibleItems.Remove(element);
+ }
+ }
+
+ protected virtual bool IsShellElementVisible(BaseShellItem item)
+ {
+ if (item is ShellGroupItem sgi)
+ {
+ return (sgi.ShellElementCollection.VisibleItemsReadOnly.Count > 0) ||
+ item is IMenuItemController;
+ }
+
+ return false;
+ }
+
+
+ protected virtual void OnElementControllerInserting(IElementController controller)
+ {
+ if (controller is ShellGroupItem sgi)
+ {
+ sgi.ShellElementCollection.VisibleItemsChangedInternal += OnShellElementControllerItemsCollectionChanged;
+ }
+ }
+
+ protected virtual void OnElementControllerRemoving(IElementController controller)
+ {
+ if (controller is ShellGroupItem sgi)
+ {
+ sgi.ShellElementCollection.VisibleItemsChangedInternal -= OnShellElementControllerItemsCollectionChanged;
+ }
+ }
+
+ void OnShellElementControllerItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ foreach (BaseShellItem section in (e.NewItems ?? e.OldItems ?? (IList)Inner))
+ {
+ if (section.Parent == null)
+ section.ParentSet += OnParentSet;
+ else
+ CheckVisibility(section.Parent as BaseShellItem);
+ }
+
+ void OnParentSet(object s, System.EventArgs __)
+ {
+ var shellSection = (BaseShellItem)s;
+ shellSection.ParentSet -= OnParentSet;
+ CheckVisibility(shellSection.Parent as BaseShellItem);
+ }
+ }
+
+ }
+
+ internal abstract class ShellElementCollection :
+ ShellElementCollection,
+ IList
+ where TBaseShellItem : BaseShellItem
+ {
+
+ public ShellElementCollection()
+ {
+ var items = new ObservableCollection();
+ VisibleItems = items;
+ VisibleItemsReadOnly = new ReadOnlyCollection(items);
+ }
+
+ public new ReadOnlyCollection VisibleItemsReadOnly
+ {
+ get => (ReadOnlyCollection)base.VisibleItemsReadOnly;
+ private protected set => base.VisibleItemsReadOnly = value;
+ }
+
+ internal new IList Inner
+ {
+ get => (IList)base.Inner;
+ set => base.Inner = (IList)value;
+ }
+
+
+ TBaseShellItem IList.this[int index]
+ {
+ get => (TBaseShellItem)Inner[index];
+ set => Inner[index] = value;
+ }
+
+ public virtual void Add(TBaseShellItem item) => Inner.Add(item);
+
+ public virtual bool Contains(TBaseShellItem item) => Inner.Contains(item);
+
+ public virtual void CopyTo(TBaseShellItem[] array, int arrayIndex) => Inner.CopyTo(array, arrayIndex);
+
+ public virtual int IndexOf(TBaseShellItem item) => Inner.IndexOf(item);
+
+ public virtual void Insert(int index, TBaseShellItem item) => Inner.Insert(index, item);
+
+ public virtual bool Remove(TBaseShellItem item) => Inner.Remove(item);
+
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Inner).GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return Inner.GetEnumerator();
+ }
+
+ public override IEnumerator GetEnumerator()
+ {
+ return Inner.Cast().GetEnumerator();
+ }
+
+ public override bool Remove(BaseShellItem item)
+ {
+ return Remove((TBaseShellItem)item);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core/Shell/ShellGroupItem.cs b/Xamarin.Forms.Core/Shell/ShellGroupItem.cs
index d672d03e888..7f9a51c732e 100644
--- a/Xamarin.Forms.Core/Shell/ShellGroupItem.cs
+++ b/Xamarin.Forms.Core/Shell/ShellGroupItem.cs
@@ -10,5 +10,7 @@ public FlyoutDisplayOptions FlyoutDisplayOptions
get { return (FlyoutDisplayOptions)GetValue(FlyoutDisplayOptionsProperty); }
set { SetValue(FlyoutDisplayOptionsProperty, value); }
}
+
+ internal virtual ShellElementCollection ShellElementCollection { get; }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shell/ShellItem.cs b/Xamarin.Forms.Core/Shell/ShellItem.cs
index 65e66363d18..0a521d74a2d 100644
--- a/Xamarin.Forms.Core/Shell/ShellItem.cs
+++ b/Xamarin.Forms.Core/Shell/ShellItem.cs
@@ -85,7 +85,7 @@ bool IShellItemController.ProposeSection(ShellSection shellSection, bool setValu
// we want the list returned from here to remain point in time accurate
ReadOnlyCollection IShellItemController.GetItems() =>
- new ReadOnlyCollection(((ShellSectionCollection)Items).VisibleItems.ToList());
+ new ReadOnlyCollection(((ShellSectionCollection)Items).VisibleItemsReadOnly.ToList());
event NotifyCollectionChangedEventHandler IShellItemController.ItemsCollectionChanged
{
@@ -93,6 +93,23 @@ event NotifyCollectionChangedEventHandler IShellItemController.ItemsCollectionCh
remove { ((ShellSectionCollection)Items).VisibleItemsChanged -= value; }
}
+ bool IShellItemController.ShowTabs
+ {
+ get
+ {
+ var displayedPage = CurrentItem?.DisplayedPage;
+ if (displayedPage == null)
+ return true;
+
+ Shell shell = Parent as Shell;
+ if (shell == null)
+ return true;
+
+ bool defaultShow = ShellItemController.GetItems().Count > 1;
+ return shell.GetEffectiveValue(Shell.TabBarIsVisibleProperty, () => defaultShow, null, displayedPage);
+ }
+ }
+
#endregion IShellItemController
#region IPropertyPropagationController
@@ -115,9 +132,8 @@ void IPropertyPropagationController.PropagatePropertyChanged(string propertyName
public ShellItem()
{
- ShellItemController.ItemsCollectionChanged += (_, args) =>
+ ((ShellElementCollection)Items).VisibleItemsChangedInternal += (_, args) =>
{
-
if (args.OldItems != null)
{
foreach (Element item in args.OldItems)
@@ -149,6 +165,7 @@ public ShellSection CurrentItem
}
public IList Items => (IList)GetValue(ItemsProperty);
+ internal override ShellElementCollection ShellElementCollection => (ShellElementCollection)Items;
internal bool IsVisibleItem => Parent is Shell shell && shell?.CurrentItem == this;
diff --git a/Xamarin.Forms.Core/Shell/ShellItemCollection.cs b/Xamarin.Forms.Core/Shell/ShellItemCollection.cs
index dfd1cfccc50..3b5b3fe7940 100644
--- a/Xamarin.Forms.Core/Shell/ShellItemCollection.cs
+++ b/Xamarin.Forms.Core/Shell/ShellItemCollection.cs
@@ -1,142 +1,13 @@
using System.Collections;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Collections.Specialized;
-using System.Linq;
namespace Xamarin.Forms
{
- internal sealed class ShellItemCollection : IList, INotifyCollectionChanged
+ internal sealed class ShellItemCollection : ShellElementCollection
{
- public event NotifyCollectionChangedEventHandler VisibleItemsChanged;
-
- IList _inner;
- ObservableCollection _visibleContents = new ObservableCollection();
- public ReadOnlyCollection VisibleItems { get; }
-
- public ShellItemCollection()
- {
- VisibleItems = new ReadOnlyCollection(_visibleContents);
- _visibleContents.CollectionChanged += (_, args) =>
- {
- VisibleItemsChanged?.Invoke(VisibleItems, args);
- };
- }
-
- public event NotifyCollectionChangedEventHandler CollectionChanged;
-
- public int Count => Inner.Count;
- public bool IsReadOnly => ((IList)Inner).IsReadOnly;
- internal IList Inner
- {
- get
- {
- return _inner;
- }
- set
- {
- _inner = value;
- ((INotifyCollectionChanged)_inner).CollectionChanged += InnerCollectionChanged;
- }
- }
-
- void InnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- if (e.NewItems != null)
- {
- foreach (ShellItem element in e.NewItems)
- {
- if (element is IShellItemController controller)
- controller.ItemsCollectionChanged += OnShellItemControllerItemsCollectionChanged;
-
- CheckVisibility(element);
- }
- }
-
- if (e.OldItems != null)
- {
- Removing(e.OldItems);
- }
-
- CollectionChanged?.Invoke(this, e);
- }
-
-
- void Removing(IEnumerable items)
- {
- foreach (ShellItem element in items)
- {
- if (_visibleContents.Contains(element))
- _visibleContents.Remove(element);
-
- if (element is IShellSectionController controller)
- controller.ItemsCollectionChanged -= OnShellItemControllerItemsCollectionChanged;
- }
- }
+ public ShellItemCollection() : base() { }
- void OnShellItemControllerItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- foreach (ShellSection section in (e.NewItems ?? e.OldItems ?? (IList)_inner))
- {
- if (section.Parent == null)
- section.ParentSet += OnParentSet;
- else
- CheckVisibility(section.Parent as ShellItem);
- }
-
- void OnParentSet(object s, System.EventArgs __)
- {
- var shellSection = (ShellSection)s;
- shellSection.ParentSet -= OnParentSet;
- CheckVisibility(shellSection.Parent as ShellItem);
- }
- }
-
- void CheckVisibility(ShellItem shellItem)
- {
- if (IsShellItemVisible(shellItem))
- {
- if (_visibleContents.Contains(shellItem))
- return;
-
- int visibleIndex = 0;
- for (var i = 0; i < _inner.Count; i++)
- {
- var item = _inner[i];
-
- if (!IsShellItemVisible(item))
- continue;
-
- if (item == shellItem)
- {
- _visibleContents.Insert(visibleIndex, shellItem);
- break;
- }
-
- visibleIndex++;
- }
- }
- else if (_visibleContents.Contains(shellItem))
- {
- _visibleContents.Remove(shellItem);
- }
-
- bool IsShellItemVisible(ShellItem item)
- {
- return (item is IShellItemController itemController && itemController.GetItems().Count > 0) ||
- item is IMenuItemController;
- }
- }
-
-
-
- public ShellItem this[int index]
- {
- get => Inner[index];
- set => Inner[index] = value;
- }
-
- public void Add(ShellItem item)
+ public override void Add(ShellItem item)
{
/*
* This is purely for the case where a user is only specifying Tabs at the highest level
@@ -152,36 +23,12 @@ item is TabBar
int i = Count - 1;
if (i >= 0 && this[i] is TabBar && Routing.IsImplicit(this[i]))
{
- this[i].Items.Add(item.Items[0]);
+ (this[i] as ShellItem).Items.Add(item.Items[0]);
return;
}
}
Inner.Add(item);
}
-
- public void Clear()
- {
- var list = Inner.ToList();
- Removing(Inner);
- Inner.Clear();
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, list));
- }
-
- public bool Contains(ShellItem item) => Inner.Contains(item);
-
- public void CopyTo(ShellItem[] array, int arrayIndex) => Inner.CopyTo(array, arrayIndex);
-
- public IEnumerator GetEnumerator() => Inner.GetEnumerator();
-
- public int IndexOf(ShellItem item) => Inner.IndexOf(item);
-
- public void Insert(int index, ShellItem item) => Inner.Insert(index, item);
-
- public bool Remove(ShellItem item) => Inner.Remove(item);
-
- public void RemoveAt(int index) => Inner.RemoveAt(index);
-
- IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Inner).GetEnumerator();
}
}
diff --git a/Xamarin.Forms.Core/Shell/ShellSection.cs b/Xamarin.Forms.Core/Shell/ShellSection.cs
index d44fc6d98dc..a4935c66796 100644
--- a/Xamarin.Forms.Core/Shell/ShellSection.cs
+++ b/Xamarin.Forms.Core/Shell/ShellSection.cs
@@ -181,7 +181,7 @@ void IShellSectionController.SendPopped()
// we want the list returned from here to remain point in time accurate
ReadOnlyCollection IShellSectionController.GetItems()
- => new ReadOnlyCollection(((ShellContentCollection)Items).VisibleItems.ToList());
+ => new ReadOnlyCollection(((ShellContentCollection)Items).VisibleItemsReadOnly.ToList());
[Obsolete]
[EditorBrowsable(EditorBrowsableState.Never)]
@@ -238,9 +238,7 @@ void IPropertyPropagationController.PropagatePropertyChanged(string propertyName
public ShellSection()
{
- (Items as INotifyCollectionChanged).CollectionChanged += ItemsCollectionChanged;
-
- ((ShellContentCollection)Items).VisibleItemsChangedInternal += (_, args) =>
+ ((ShellElementCollection)Items).VisibleItemsChangedInternal += (_, args) =>
{
if (args.OldItems != null)
{
@@ -257,8 +255,12 @@ public ShellSection()
OnVisibleChildAdded(item);
}
}
+
+ SendStructureChanged();
};
+ (Items as INotifyCollectionChanged).CollectionChanged += ItemsCollectionChanged;
+
Navigation = new NavigationImpl(this);
}
@@ -269,12 +271,13 @@ public ShellContent CurrentItem
}
public IList Items => (IList)GetValue(ItemsProperty);
+ internal override ShellElementCollection ShellElementCollection => (ShellElementCollection)Items;
public IReadOnlyList Stack => _navStack;
internal override ReadOnlyCollection LogicalChildrenInternal => _logicalChildrenReadOnly ?? (_logicalChildrenReadOnly = new ReadOnlyCollection(_logicalChildren));
- Page DisplayedPage
+ internal Page DisplayedPage
{
get { return _displayedPage; }
set
@@ -526,10 +529,23 @@ protected override void OnChildAdded(Element child)
base.OnChildAdded(child);
OnVisibleChildAdded(child);
}
-
+
protected override void OnChildRemoved(Element child)
{
- base.OnChildRemoved(child);
+ if(child is IShellContentController sc && sc.Page.IsPlatformEnabled)
+ {
+ sc.Page.PlatformEnabledChanged += WaitForRendererToGetRemoved;
+ void WaitForRendererToGetRemoved(object s, EventArgs p)
+ {
+ sc.Page.PlatformEnabledChanged -= WaitForRendererToGetRemoved;
+ base.OnChildRemoved(child);
+ };
+ }
+ else
+ {
+ base.OnChildRemoved(child);
+ }
+
OnVisibleChildRemoved(child);
}
@@ -865,8 +881,6 @@ void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
foreach (Element element in e.OldItems)
OnChildRemoved(element);
}
-
- SendStructureChanged();
}
void RemovePage(Page page)
diff --git a/Xamarin.Forms.Core/Shell/ShellSectionCollection.cs b/Xamarin.Forms.Core/Shell/ShellSectionCollection.cs
index f2d7c872120..a414b6df9e2 100644
--- a/Xamarin.Forms.Core/Shell/ShellSectionCollection.cs
+++ b/Xamarin.Forms.Core/Shell/ShellSectionCollection.cs
@@ -1,150 +1,10 @@
using System.Collections;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Collections.Specialized;
-using System.Linq;
namespace Xamarin.Forms
{
- internal sealed class ShellSectionCollection : IList, INotifyCollectionChanged
+ internal sealed class ShellSectionCollection : ShellElementCollection
{
- public event NotifyCollectionChangedEventHandler VisibleItemsChanged;
- IList _inner;
- ObservableCollection _visibleContents = new ObservableCollection();
-
- public ShellSectionCollection()
- {
- VisibleItems = new ReadOnlyCollection(_visibleContents);
- _visibleContents.CollectionChanged += (_, args) =>
- {
- VisibleItemsChanged?.Invoke(VisibleItems, args);
- };
- }
-
- public ReadOnlyCollection VisibleItems { get; }
-
- public event NotifyCollectionChangedEventHandler CollectionChanged;
-
- public int Count => Inner.Count;
- public bool IsReadOnly => Inner.IsReadOnly;
- internal IList Inner
- {
- get => _inner;
- set
- {
- _inner = value;
- ((INotifyCollectionChanged)_inner).CollectionChanged += InnerCollectionChanged;
- }
- }
-
- void InnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- if (e.NewItems != null)
- {
- foreach (ShellSection element in e.NewItems)
- {
- if (element is IShellSectionController controller)
- controller.ItemsCollectionChanged += OnShellSectionControllerItemsCollectionChanged;
-
- CheckVisibility(element);
- }
- }
-
- if (e.OldItems != null)
- {
- Removing(e.OldItems);
- }
-
- CollectionChanged?.Invoke(this, e);
- }
-
- void Removing(IEnumerable items)
- {
- foreach (ShellSection element in items)
- {
- if (_visibleContents.Contains(element))
- _visibleContents.Remove(element);
-
- if (element is IShellSectionController controller)
- controller.ItemsCollectionChanged -= OnShellSectionControllerItemsCollectionChanged;
- }
- }
-
- void OnShellSectionControllerItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- foreach (ShellContent content in (e.NewItems ?? e.OldItems ?? (IList)_inner))
- {
- if(content.Parent == null)
- content.ParentSet += OnParentSet;
- else
- CheckVisibility(content.Parent as ShellSection);
- }
-
- void OnParentSet(object s, System.EventArgs __)
- {
- var shellContent = (ShellContent)s;
- shellContent.ParentSet -= OnParentSet;
- CheckVisibility(shellContent.Parent as ShellSection);
- }
- }
-
- void CheckVisibility(ShellSection section)
- {
- if (section is IShellSectionController controller && controller.GetItems().Count > 0)
- {
- if (_visibleContents.Contains(section))
- return;
-
- int visibleIndex = 0;
- for (var i = 0; i < _inner.Count; i++)
- {
- var item = _inner[i];
-
- if (item == section)
- {
- _visibleContents.Insert(visibleIndex, section);
- break;
- }
-
- visibleIndex++;
- }
- }
- else if (_visibleContents.Contains(section))
- {
- _visibleContents.Remove(section);
- }
- }
-
- public ShellSection this[int index]
- {
- get => Inner[index];
- set => Inner[index] = value;
- }
-
- public void Add(ShellSection item) => Inner.Add(item);
-
- public void Clear()
- {
- var list = Inner.ToList();
- Removing(Inner);
- Inner.Clear();
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, list));
- }
-
- public bool Contains(ShellSection item) => Inner.Contains(item);
-
- public void CopyTo(ShellSection[] array, int arrayIndex) => Inner.CopyTo(array, arrayIndex);
-
- public IEnumerator GetEnumerator() => Inner.GetEnumerator();
-
- public int IndexOf(ShellSection item) => Inner.IndexOf(item);
-
- public void Insert(int index, ShellSection item) => Inner.Insert(index, item);
-
- public bool Remove(ShellSection item) => Inner.Remove(item);
-
- public void RemoveAt(int index) => Inner.RemoveAt(index);
-
- IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Inner).GetEnumerator();
+ public ShellSectionCollection() : base() {}
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Shell/ShellUriHandler.cs b/Xamarin.Forms.Core/Shell/ShellUriHandler.cs
index 8008348ad12..78fd439f199 100644
--- a/Xamarin.Forms.Core/Shell/ShellUriHandler.cs
+++ b/Xamarin.Forms.Core/Shell/ShellUriHandler.cs
@@ -2,8 +2,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
-using System.IO;
using System.Linq;
+using IOPath = System.IO.Path;
namespace Xamarin.Forms
{
@@ -17,7 +17,7 @@ internal static Uri FormatUri(Uri path, Shell shell)
{
if (path.OriginalString.StartsWith("..") && shell?.CurrentState != null)
{
- var result = Path.Combine(shell.CurrentState.FullLocation.OriginalString, path.OriginalString);
+ var result = IOPath.Combine(shell.CurrentState.FullLocation.OriginalString, path.OriginalString);
var returnValue = ConvertToStandardFormat("scheme", "host", null, new Uri(result, UriKind.Relative));
return new Uri(FormatUri(returnValue.PathAndQuery), UriKind.Relative);
}
@@ -219,8 +219,13 @@ internal static List GenerateRoutePaths(Shell shell, Uri re
{
// currently relative routes to shell routes isn't supported as we aren't creating navigation stacks
// So right now we will just throw an exception so that once this is implemented
- // GotoAsync doesn't start acting inconsistently and all of a suddent starts creating routes
- if (!enableRelativeShellRoutes && pureGlobalRoutesMatch[0].SegmentsMatched.Count > 0)
+ // GotoAsync doesn't start acting inconsistently and all of a sudden starts creating routes
+
+ int shellElementsMatched =
+ pureGlobalRoutesMatch[0].SegmentsMatched.Count -
+ pureGlobalRoutesMatch[0].GlobalRouteMatches.Count;
+
+ if (!enableRelativeShellRoutes && shellElementsMatched > 0)
{
throw new Exception($"Relative routing to shell elements is currently not supported. Try prefixing your uri with ///: ///{originalRequest}");
}
diff --git a/Xamarin.Forms.Core/StateTriggerBase.cs b/Xamarin.Forms.Core/StateTriggerBase.cs
index c3bfc1bc259..d3c06f71567 100644
--- a/Xamarin.Forms.Core/StateTriggerBase.cs
+++ b/Xamarin.Forms.Core/StateTriggerBase.cs
@@ -9,7 +9,7 @@ public abstract class StateTriggerBase : BindableObject
public StateTriggerBase()
{
- ExperimentalFlags.VerifyFlagEnabled(nameof(StateTriggerBase), ExperimentalFlags.StateTriggersExperimental);
+
}
public bool IsActive
diff --git a/Xamarin.Forms.Core/Stretch.cs b/Xamarin.Forms.Core/Stretch.cs
new file mode 100644
index 00000000000..0f5ba85381f
--- /dev/null
+++ b/Xamarin.Forms.Core/Stretch.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public enum Stretch
+ {
+ None,
+ Fill,
+ Uniform,
+ UniformToFill
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/StyleSheets/StyleSheet.cs b/Xamarin.Forms.Core/StyleSheets/StyleSheet.cs
index 0c79855663a..9fecd37610a 100644
--- a/Xamarin.Forms.Core/StyleSheets/StyleSheet.cs
+++ b/Xamarin.Forms.Core/StyleSheets/StyleSheet.cs
@@ -108,15 +108,8 @@ void IStyle.Apply(BindableObject bindable)
void Apply(Element styleable)
{
- ApplyCore(styleable);
- foreach (var child in styleable.AllChildren)
- ((IStyle)this).Apply(child);
- }
-
- void ApplyCore(Element styleable)
- {
- if (!(styleable is VisualElement visualStylable))
- return;
+ if (!(styleable is VisualElement visualStylable))
+ return;
foreach (var kvp in Styles) {
var selector = kvp.Key;
var style = kvp.Value;
diff --git a/Xamarin.Forms.Core/SweepDirection.cs b/Xamarin.Forms.Core/SweepDirection.cs
new file mode 100644
index 00000000000..a9634524192
--- /dev/null
+++ b/Xamarin.Forms.Core/SweepDirection.cs
@@ -0,0 +1,8 @@
+namespace Xamarin.Forms
+{
+ public enum SweepDirection
+ {
+ CounterClockwise,
+ Clockwise
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/Switch.cs b/Xamarin.Forms.Core/Switch.cs
index 3eee876672d..d4de0a3b1e6 100644
--- a/Xamarin.Forms.Core/Switch.cs
+++ b/Xamarin.Forms.Core/Switch.cs
@@ -6,11 +6,13 @@ namespace Xamarin.Forms
[RenderWith(typeof(_SwitchRenderer))]
public class Switch : View, IElementConfiguration
{
- public static readonly BindableProperty IsToggledProperty = BindableProperty.Create("IsToggled", typeof(bool), typeof(Switch), false, propertyChanged: (bindable, oldValue, newValue) =>
+ public const string SwitchOnVisualState = "On";
+ public const string SwitchOffVisualState = "Off";
+
+ public static readonly BindableProperty IsToggledProperty = BindableProperty.Create(nameof(IsToggled), typeof(bool), typeof(Switch), false, propertyChanged: (bindable, oldValue, newValue) =>
{
- EventHandler eh = ((Switch)bindable).Toggled;
- if (eh != null)
- eh(bindable, new ToggledEventArgs((bool)newValue));
+ ((Switch)bindable).Toggled?.Invoke(bindable, new ToggledEventArgs((bool)newValue));
+ ((Switch)bindable).ChangeVisualState();
}, defaultBindingMode: BindingMode.TwoWay);
public static readonly BindableProperty OnColorProperty = BindableProperty.Create(nameof(OnColor), typeof(Color), typeof(Switch), Color.Default);
@@ -41,6 +43,14 @@ public bool IsToggled
get { return (bool)GetValue(IsToggledProperty); }
set { SetValue(IsToggledProperty, value); }
}
+ protected internal override void ChangeVisualState()
+ {
+ base.ChangeVisualState();
+ if (IsEnabled && IsToggled)
+ VisualStateManager.GoToState(this, SwitchOnVisualState);
+ else if (IsEnabled && !IsToggled)
+ VisualStateManager.GoToState(this, SwitchOffVisualState);
+ }
public event EventHandler Toggled;
diff --git a/Xamarin.Forms.Core/Tweener.cs b/Xamarin.Forms.Core/Tweener.cs
index e889a424bc5..c27395c87e1 100644
--- a/Xamarin.Forms.Core/Tweener.cs
+++ b/Xamarin.Forms.Core/Tweener.cs
@@ -34,11 +34,21 @@ internal class Tweener
long _lastMilliseconds;
int _timer;
+ long _frames;
public Tweener(uint length)
{
Value = 0.0f;
Length = length;
+ Rate = 1;
+ Loop = false;
+ }
+
+ public Tweener(uint length, uint rate)
+ {
+ Value = 0.0f;
+ Length = length;
+ Rate = rate;
Loop = false;
}
@@ -46,6 +56,8 @@ public Tweener(uint length)
public uint Length { get; }
+ public uint Rate { get; }
+
public bool Loop { get; set; }
public double Value { get; private set; }
@@ -66,6 +78,7 @@ public void Start()
Pause();
_lastMilliseconds = 0;
+ _frames = 0;
if (!Ticker.Default.SystemEnabled)
{
@@ -89,7 +102,12 @@ public void Start()
_lastMilliseconds = ms;
}
- ValueUpdated?.Invoke(this, EventArgs.Empty);
+ long wantedFrames = (_lastMilliseconds / Rate) + 1;
+ if(wantedFrames > _frames || Value >= 1.0f)
+ {
+ ValueUpdated?.Invoke(this, EventArgs.Empty);
+ }
+ _frames = wantedFrames;
if (Value >= 1.0f)
{
diff --git a/Xamarin.Forms.Core/TypedBinding.cs b/Xamarin.Forms.Core/TypedBinding.cs
index 61436365f7e..2ca66809c88 100644
--- a/Xamarin.Forms.Core/TypedBinding.cs
+++ b/Xamarin.Forms.Core/TypedBinding.cs
@@ -212,7 +212,7 @@ internal void ApplyCore(object sourceObject, BindableObject target, BindableProp
}
}
if (!BindingExpression.TryConvert(ref value, property, property.ReturnType, true)) {
- Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, property.ReturnType);
+ Log.Warning("Binding", "'{0}' can not be converted to type '{1}'.", value, property.ReturnType);
return;
}
target.SetValueCore(property, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted);
@@ -223,7 +223,7 @@ internal void ApplyCore(object sourceObject, BindableObject target, BindableProp
if (needsSetter && _setter != null && isTSource) {
var value = GetTargetValue(target.GetValue(property), typeof(TProperty));
if (!BindingExpression.TryConvert(ref value, property, typeof(TProperty), false)) {
- Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, typeof(TProperty));
+ Log.Warning("Binding", "'{0}' can not be converted to type '{1}'.", value, typeof(TProperty));
return;
}
_setter((TSource)sourceObject, (TProperty)value);
diff --git a/Xamarin.Forms.Core/UriImageSource.cs b/Xamarin.Forms.Core/UriImageSource.cs
index e0562805299..ae8018b8012 100644
--- a/Xamarin.Forms.Core/UriImageSource.cs
+++ b/Xamarin.Forms.Core/UriImageSource.cs
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
-using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Forms.Internals;
+using IOPath = System.IO.Path;
namespace Xamarin.Forms
{
@@ -101,7 +101,7 @@ public override string ToString()
static string GetCacheKey(Uri uri)
{
- return Device.PlatformServices.GetMD5Hash(uri.AbsoluteUri);
+ return Device.PlatformServices.GetHash(uri.AbsoluteUri);
}
async Task GetHasLocallyCachedCopyAsync(string key, bool checkValidity = true)
@@ -113,7 +113,7 @@ async Task GetHasLocallyCachedCopyAsync(string key, bool checkValidity = t
static async Task GetLastWriteTimeUtcAsync(string key)
{
- string path = Path.Combine(CacheName, key);
+ string path = IOPath.Combine(CacheName, key);
if (!await Store.GetFileExistsAsync(path).ConfigureAwait(false))
return null;
@@ -155,7 +155,7 @@ async Task GetStreamAsyncUnchecked(string key, Uri uri, CancellationToke
int backoff;
try
{
- Stream result = await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Open, FileAccess.Read).ConfigureAwait(false);
+ Stream result = await Store.OpenFileAsync(IOPath.Combine(CacheName, key), FileMode.Open, FileAccess.Read).ConfigureAwait(false);
return result;
}
catch (IOException)
@@ -194,14 +194,14 @@ async Task GetStreamAsyncUnchecked(string key, Uri uri, CancellationToke
try
{
- Stream writeStream = await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Create, FileAccess.Write).ConfigureAwait(false);
+ Stream writeStream = await Store.OpenFileAsync(IOPath.Combine(CacheName, key), FileMode.Create, FileAccess.Write).ConfigureAwait(false);
await stream.CopyToAsync(writeStream, 16384, cancellationToken).ConfigureAwait(false);
if (writeStream != null)
writeStream.Dispose();
stream.Dispose();
- return await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Open, FileAccess.Read).ConfigureAwait(false);
+ return await Store.OpenFileAsync(IOPath.Combine(CacheName, key), FileMode.Open, FileAccess.Read).ConfigureAwait(false);
}
catch (Exception ex)
{
diff --git a/Xamarin.Forms.Core/VisualElement.cs b/Xamarin.Forms.Core/VisualElement.cs
index aa41638bb97..42b6e00017c 100644
--- a/Xamarin.Forms.Core/VisualElement.cs
+++ b/Xamarin.Forms.Core/VisualElement.cs
@@ -3,6 +3,7 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms.Internals;
+using Xamarin.Forms.Shapes;
namespace Xamarin.Forms
{
@@ -57,6 +58,8 @@ public partial class VisualElement : NavigableElement, IAnimatable, IVisualEleme
internal static readonly BindableProperty TransformProperty = BindableProperty.Create("Transform", typeof(string), typeof(VisualElement), null, propertyChanged: OnTransformChanged);
+ public static readonly BindableProperty ClipProperty = BindableProperty.Create(nameof(Clip), typeof(Geometry), typeof(VisualElement), null);
+
public static readonly BindableProperty VisualProperty =
BindableProperty.Create(nameof(Visual), typeof(IVisual), typeof(VisualElement), Forms.VisualMarker.MatchParent,
validateValue: (b, v) => v != null, propertyChanged: OnVisualChanged);
@@ -466,6 +469,13 @@ public double Y
private set { SetValue(YPropertyKey, value); }
}
+ [TypeConverter(typeof(PathGeometryConverter))]
+ public Geometry Clip
+ {
+ get { return (Geometry)GetValue(ClipProperty); }
+ set { SetValue(ClipProperty, value); }
+ }
+
[EditorBrowsable(EditorBrowsableState.Never)]
public bool Batched
{
@@ -531,6 +541,9 @@ public bool IsNativeStateConsistent
}
}
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal event EventHandler PlatformEnabledChanged;
+
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsPlatformEnabled
{
@@ -547,8 +560,9 @@ public bool IsPlatformEnabled
InvalidateStateTriggers(IsPlatformEnabled);
OnIsPlatformEnabledChanged();
+ PlatformEnabledChanged?.Invoke(this, EventArgs.Empty);
}
- }
+ }
internal LayoutConstraint SelfConstraint
{
@@ -882,14 +896,6 @@ internal virtual void OnIsVisibleChanged(bool oldValue, bool newValue)
InvalidateMeasureInternal(InvalidationTrigger.Undefined);
}
- internal override void OnResourcesChanged(object sender, ResourcesChangedEventArgs e)
- {
- if (e == ResourcesChangedEventArgs.StyleSheets)
- ApplyStyleSheets();
- else
- base.OnResourcesChanged(sender, e);
- }
-
internal override void OnParentResourcesChanged(IEnumerable> values)
{
if (values == null)
diff --git a/Xamarin.Forms.Core/VisualElementExtensions.cs b/Xamarin.Forms.Core/VisualElementExtensions.cs
index 1a23f5d0793..3c47b394927 100644
--- a/Xamarin.Forms.Core/VisualElementExtensions.cs
+++ b/Xamarin.Forms.Core/VisualElementExtensions.cs
@@ -8,7 +8,7 @@ public static class VisualElementExtensions
public static void SetOnAppTheme(this VisualElement self, BindableProperty targetProperty, T light, T dark)
{
ExperimentalFlags.VerifyFlagEnabled(nameof(BindableObjectExtensions), ExperimentalFlags.AppThemeExperimental, nameof(BindableObjectExtensions), nameof(SetOnAppTheme));
- self.SetBinding(targetProperty, new OnAppTheme { Light = light, Dark = dark});
+ self.SetBinding(targetProperty, new AppThemeBinding { Light = light, Dark = dark});
}
public static void SetAppThemeColor(this VisualElement self, BindableProperty targetProperty, Color light, Color dark) => SetOnAppTheme(self, targetProperty, light, dark);
diff --git a/Xamarin.Forms.Core/VisualElement_StyleSheet.cs b/Xamarin.Forms.Core/VisualElement_StyleSheet.cs
index 9d54ac7da8b..f22f994b1cb 100644
--- a/Xamarin.Forms.Core/VisualElement_StyleSheet.cs
+++ b/Xamarin.Forms.Core/VisualElement_StyleSheet.cs
@@ -46,11 +46,5 @@ BindableProperty IStylable.GetProperty(string key, bool inheriting)
return (styleAttribute.BindableProperty = bpField.GetValue(null) as BindableProperty);
}
-
- void ApplyStyleSheets()
- {
- foreach (var styleSheet in this.GetStyleSheets())
- ((IStyle)styleSheet).Apply(this);
- }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.DualScreen.UnitTests/MockPlatformServices.cs b/Xamarin.Forms.DualScreen.UnitTests/MockPlatformServices.cs
index 302b2c227f9..172c3c375e1 100644
--- a/Xamarin.Forms.DualScreen.UnitTests/MockPlatformServices.cs
+++ b/Xamarin.Forms.DualScreen.UnitTests/MockPlatformServices.cs
@@ -12,6 +12,7 @@
using FileShare = System.IO.FileShare;
using Stream = System.IO.Stream;
using Xamarin.Forms.DualScreen.UnitTests;
+using Xamarin.Forms.Internals;
[assembly: Dependency(typeof(MockDeserializer))]
[assembly: Dependency(typeof(MockResourcesProvider))]
@@ -40,19 +41,9 @@ public MockPlatformServices(Action invokeOnMainThread = null, Action Internals.Crc64.GetHash(input);
+ string IPlatformServices.GetMD5Hash(string input) => GetHash(input);
- public string GetMD5Hash(string input)
- {
- var bytes = checksum.ComputeHash(Encoding.UTF8.GetBytes(input));
- var ret = new char[32];
- for (int i = 0; i < 16; i++)
- {
- ret[i * 2] = (char)hex(bytes[i] >> 4);
- ret[i * 2 + 1] = (char)hex(bytes[i] & 0xf);
- }
- return new string(ret);
- }
static int hex(int v)
{
if (v < 10)
diff --git a/Xamarin.Forms.DualScreen/DualScreenHelper.uwp.cs b/Xamarin.Forms.DualScreen/DualScreenHelper.uwp.cs
index e73b787e2ea..b5619b77549 100644
--- a/Xamarin.Forms.DualScreen/DualScreenHelper.uwp.cs
+++ b/Xamarin.Forms.DualScreen/DualScreenHelper.uwp.cs
@@ -6,7 +6,7 @@
using Windows.Foundation.Metadata;
using Windows.UI.ViewManagement;
-#if !UWP_14393
+#if UWP_18362
using Windows.UI.WindowManagement;
#endif
@@ -18,7 +18,7 @@ namespace Xamarin.Forms.DualScreen
public static class DualScreenHelper
{
-#if !UWP_14393
+#if UWP_18362
public static bool HasCompactModeSupport()
{
diff --git a/Xamarin.Forms.DualScreen/DualScreenInfo.android.cs b/Xamarin.Forms.DualScreen/DualScreenInfo.android.cs
index 4c166486489..f5fd17bb933 100644
--- a/Xamarin.Forms.DualScreen/DualScreenInfo.android.cs
+++ b/Xamarin.Forms.DualScreen/DualScreenInfo.android.cs
@@ -3,12 +3,38 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Device.Display;
namespace Xamarin.Forms.DualScreen
{
public partial class DualScreenInfo : INotifyPropertyChanged
{
+ static object hingeAngleLock = new object();
public Task GetHingeAngleAsync() => DualScreenService.GetHingeAngleAsync();
+
+ void ProcessHingeAngleSubscriberCount(int newCount)
+ {
+ lock (hingeAngleLock)
+ {
+ if (newCount == 1)
+ {
+ DualScreen.DualScreenService.DualScreenServiceImpl.HingeAngleChanged += OnHingeAngleChanged;
+ }
+ else if (newCount == 0)
+ {
+ DualScreen.DualScreenService.DualScreenServiceImpl.HingeAngleChanged -= OnHingeAngleChanged;
+ }
+ }
+ }
+
+ void OnHingeAngleChanged(object sender, HingeSensor.HingeSensorChangedEventArgs e)
+ {
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ _hingeAngleChanged?.Invoke(this, new HingeAngleChangedEventArgs(e.HingeAngle));
+ });
+ }
}
}
diff --git a/Xamarin.Forms.DualScreen/DualScreenInfo.netstandard.cs b/Xamarin.Forms.DualScreen/DualScreenInfo.netstandard.cs
new file mode 100644
index 00000000000..2c71d0c9609
--- /dev/null
+++ b/Xamarin.Forms.DualScreen/DualScreenInfo.netstandard.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms.DualScreen
+{
+ public partial class DualScreenInfo : INotifyPropertyChanged
+ {
+ public Task GetHingeAngleAsync() => DualScreenService.GetHingeAngleAsync();
+
+ void ProcessHingeAngleSubscriberCount(int newCount) { }
+ }
+}
diff --git a/Xamarin.Forms.DualScreen/DualScreenInfo.shared.cs b/Xamarin.Forms.DualScreen/DualScreenInfo.shared.cs
index a2da2f29944..730fa72181e 100644
--- a/Xamarin.Forms.DualScreen/DualScreenInfo.shared.cs
+++ b/Xamarin.Forms.DualScreen/DualScreenInfo.shared.cs
@@ -3,24 +3,37 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
namespace Xamarin.Forms.DualScreen
{
+ public class HingeAngleChangedEventArgs : EventArgs
+ {
+ public HingeAngleChangedEventArgs(double hingeAngleInDegrees)
+ {
+ HingeAngleInDegrees = hingeAngleInDegrees;
+ }
+
+ public double HingeAngleInDegrees { get; }
+ }
+
public partial class DualScreenInfo : INotifyPropertyChanged
{
- static Lazy _dualScreenInfo { get; } = new Lazy(OnCreate);
- public event PropertyChangedEventHandler PropertyChanged;
Rectangle[] _spanningBounds;
Rectangle _hingeBounds;
bool _isLandscape;
TwoPaneViewMode _spanMode;
TwoPaneViewLayoutGuide _twoPaneViewLayoutGuide;
- IDualScreenService _dualScreenService;
- public static DualScreenInfo Current => _dualScreenInfo.Value;
+ IDualScreenService _dualScreenService;
IDualScreenService DualScreenService =>
_dualScreenService ?? DependencyService.Get() ?? NoDualScreenServiceImpl.Instance;
+ static Lazy _dualScreenInfo { get; } = new Lazy(OnCreate);
+
+ public static DualScreenInfo Current => _dualScreenInfo.Value;
+ public event PropertyChangedEventHandler PropertyChanged;
+
public DualScreenInfo(VisualElement layout) : this(layout, null)
{
}
@@ -35,10 +48,28 @@ internal DualScreenInfo(VisualElement layout, IDualScreenService dualScreenServi
else
{
_twoPaneViewLayoutGuide = new TwoPaneViewLayoutGuide(layout, dualScreenService);
- _twoPaneViewLayoutGuide.PropertyChanged += OnTwoPaneViewLayoutGuideChanged;
+ _twoPaneViewLayoutGuide.PropertyChanged += OnTwoPaneViewLayoutGuideChanged;
}
}
+
+ EventHandler _hingeAngleChanged;
+ int subscriberCount = 0;
+ public event EventHandler HingeAngleChanged
+ {
+ add
+ {
+ ProcessHingeAngleSubscriberCount(Interlocked.Increment(ref subscriberCount));
+ _hingeAngleChanged += value;
+ }
+ remove
+ {
+ ProcessHingeAngleSubscriberCount(Interlocked.Decrement(ref subscriberCount));
+ _hingeAngleChanged -= value;
+ }
+ }
+
+
public Rectangle[] SpanningBounds
{
get => GetSpanningBounds();
diff --git a/Xamarin.Forms.DualScreen/DualScreenInfo.uwp.cs b/Xamarin.Forms.DualScreen/DualScreenInfo.uwp.cs
new file mode 100644
index 00000000000..283aa7890e6
--- /dev/null
+++ b/Xamarin.Forms.DualScreen/DualScreenInfo.uwp.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Devices.Sensors;
+
+namespace Xamarin.Forms.DualScreen
+{
+ public partial class DualScreenInfo : INotifyPropertyChanged
+ {
+ static object hingeAngleLock = new object();
+ public Task GetHingeAngleAsync() => DualScreenService.GetHingeAngleAsync();
+
+#if UWP_18362
+ Windows.Devices.Sensors.HingeAngleSensor _angleSensor;
+#endif
+
+#if UWP_18362
+ async void ProcessHingeAngleSubscriberCount(int newCount)
+ {
+ try
+ {
+ if (_angleSensor == null)
+ _angleSensor = await Windows.Devices.Sensors.HingeAngleSensor.GetDefaultAsync();
+
+ if (_angleSensor == null)
+ return;
+
+ lock (hingeAngleLock)
+ {
+ if (newCount == 1)
+ {
+ _angleSensor.ReadingChanged += OnReadingChanged;
+ }
+ else if(newCount == 0)
+ {
+ _angleSensor.ReadingChanged -= OnReadingChanged;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ Internals.Log.Warning(nameof(DualScreenInfo), $"Failed to retrieve Hinge Angle Sensor {e}");
+ }
+
+ void OnReadingChanged(HingeAngleSensor sender, HingeAngleSensorReadingChangedEventArgs args)
+ {
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ _hingeAngleChanged?.Invoke(this, new HingeAngleChangedEventArgs(args.Reading.AngleInDegrees));
+ });
+ }
+ }
+#else
+ void ProcessHingeAngleSubscriberCount(int newCount)
+ {
+ }
+#endif
+ }
+}
diff --git a/Xamarin.Forms.DualScreen/DualScreenService.android.cs b/Xamarin.Forms.DualScreen/DualScreenService.android.cs
index 2f37850ee23..f4b96fded64 100644
--- a/Xamarin.Forms.DualScreen/DualScreenService.android.cs
+++ b/Xamarin.Forms.DualScreen/DualScreenService.android.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading;
using System.Threading.Tasks;
using Android.App;
using Android.Util;
@@ -14,6 +15,12 @@ namespace Xamarin.Forms.DualScreen
{
public class DualScreenService
{
+ [Internals.Preserve(Conditional = true)]
+ public DualScreenService()
+ {
+
+ }
+
public static void Init(Activity activity)
{
DependencyService.Register();
@@ -22,11 +29,10 @@ public static void Init(Activity activity)
internal class DualScreenServiceImpl : IDualScreenService, Platform.Android.DualScreen.IDualScreenService
{
- public event EventHandler OnScreenChanged;
ScreenHelper _helper;
bool _isDuo = false;
- bool IsDuo => (_helper == null || _HingeService == null || _mainActivity == null || _hingeSensor == null) ? false : _isDuo;
- HingeSensor _hingeSensor;
+ bool IsDuo => (_helper == null || _HingeService == null || _mainActivity == null || _singleUseHingeSensor == null) ? false : _isDuo;
+ HingeSensor _singleUseHingeSensor;
static Activity _mainActivity;
static DualScreenServiceImpl _HingeService;
bool _isLandscape;
@@ -34,6 +40,12 @@ internal class DualScreenServiceImpl : IDualScreenService, Platform.Android.Dual
object _hingeAngleLock = new object();
TaskCompletionSource _gettingHingeAngle;
+ internal static Activity MainActivity => _mainActivity;
+
+ static HingeSensor DefaultHingeSensor;
+ public event EventHandler OnScreenChanged;
+
+ [Internals.Preserve(Conditional = true)]
public DualScreenServiceImpl()
{
_HingeService = this;
@@ -70,51 +82,19 @@ public static void Init(Activity activity)
if (!isDuo)
{
- _HingeService._helper = null;
- _HingeService._hingeSensor = null;
+ _HingeService._helper = null;
+ _HingeService.SetupHingeSensors(null);
return;
}
_HingeService._helper = screenHelper;
- _HingeService._hingeSensor = new HingeSensor(_mainActivity);
+ _HingeService.SetupHingeSensors(_mainActivity);
if (_mainActivity is IDeviceInfoProvider newDeviceInfoProvider)
{
newDeviceInfoProvider.ConfigurationChanged += _HingeService.ConfigurationChanged;
}
}
- void ConfigurationChanged(object sender, EventArgs e)
- {
- if(IsDuo)
- _helper?.Update();
-
- bool screenChanged = false;
- if (_isLandscape != IsLandscape)
- {
- _isLandscape = IsLandscape;
- screenChanged = true;
- }
-
- if (_mainActivity != null)
- {
- using (DisplayMetrics display = _mainActivity.Resources.DisplayMetrics)
- {
- var scalingFactor = display.Density;
- _pixelScreenSize = new Size(display.WidthPixels, display.HeightPixels);
- var newSize = new Size(_pixelScreenSize.Width / scalingFactor, _pixelScreenSize.Height / scalingFactor);
-
- if (newSize != ScaledScreenSize)
- {
- ScaledScreenSize = newSize;
- screenChanged = true;
- }
- }
- }
-
- if(screenChanged)
- OnScreenChanged?.Invoke(this, e);
- }
-
public Size ScaledScreenSize
{
get;
@@ -124,43 +104,6 @@ public Size ScaledScreenSize
public bool IsSpanned
=> IsDuo && (_helper?.IsDualMode ?? false);
- void StartListeningForHingeChanges()
- {
- if (!IsDuo)
- return;
-
- _hingeSensor.OnSensorChanged += OnSensorChanged;
- _hingeSensor.StartListening();
- }
-
- void StopListeningForHingeChanges()
- {
- if (!IsDuo)
- return;
-
- _hingeSensor.OnSensorChanged -= OnSensorChanged;
- _hingeSensor.StopListening();
- }
-
- void OnSensorChanged(object sender, HingeSensor.HingeSensorChangedEventArgs e)
- {
- SetHingeAngle(e.HingeAngle);
- }
-
- void SetHingeAngle(int hingeAngle)
- {
- TaskCompletionSource toSet = null;
- lock (_hingeAngleLock)
- {
- StopListeningForHingeChanges();
- toSet = _gettingHingeAngle;
- _gettingHingeAngle = null;
- }
-
- if (toSet != null)
- toSet.SetResult(hingeAngle);
- }
-
public Task GetHingeAngleAsync()
{
if (!IsDuo)
@@ -286,6 +229,145 @@ public void StopWatchingForChangesOnLayout(VisualElement visualElement, object h
}
}
}
+
+
+ static EventHandler _hingeAngleChanged;
+ static int subscriberCount;
+ static object hingeAngleLock = new object();
+
+ public static event EventHandler HingeAngleChanged
+ {
+ add
+ {
+ if (DefaultHingeSensor == null)
+ return;
+
+ ProcessHingeAngleSubscriberCount(Interlocked.Increment(ref subscriberCount));
+ _hingeAngleChanged += value;
+ }
+ remove
+ {
+ if (DefaultHingeSensor == null)
+ return;
+
+ ProcessHingeAngleSubscriberCount(Interlocked.Decrement(ref subscriberCount));
+ _hingeAngleChanged -= value;
+ }
+ }
+
+ static void ProcessHingeAngleSubscriberCount(int subscriberCount)
+ {
+ var sensor = DefaultHingeSensor;
+ if (sensor == null)
+ return;
+
+ lock(hingeAngleLock)
+ {
+ if (subscriberCount == 1)
+ {
+ sensor.StartListening();
+ }
+ else if (subscriberCount == 0)
+ {
+ sensor.StopListening();
+ }
+ }
+ }
+
+ void DefaultHingeSensorOnSensorChanged(object sender, HingeSensor.HingeSensorChangedEventArgs e)
+ {
+ _hingeAngleChanged?.Invoke(this, e);
+ }
+
+ void SetupHingeSensors(global::Android.Content.Context context)
+ {
+ if (context == null)
+ {
+ if (DefaultHingeSensor != null)
+ DefaultHingeSensor.OnSensorChanged -= DefaultHingeSensorOnSensorChanged;
+
+ _singleUseHingeSensor = null;
+ DefaultHingeSensor = null;
+
+
+ }
+ else
+ {
+ _singleUseHingeSensor = new HingeSensor(context);
+ DefaultHingeSensor = new HingeSensor(context);
+ DefaultHingeSensor.OnSensorChanged += DefaultHingeSensorOnSensorChanged;
+ }
+ }
+
+ void ConfigurationChanged(object sender, EventArgs e)
+ {
+ if (IsDuo)
+ _helper?.Update();
+
+ bool screenChanged = false;
+ if (_isLandscape != IsLandscape)
+ {
+ _isLandscape = IsLandscape;
+ screenChanged = true;
+ }
+
+ if (_mainActivity != null)
+ {
+ using (DisplayMetrics display = _mainActivity.Resources.DisplayMetrics)
+ {
+ var scalingFactor = display.Density;
+ _pixelScreenSize = new Size(display.WidthPixels, display.HeightPixels);
+ var newSize = new Size(_pixelScreenSize.Width / scalingFactor, _pixelScreenSize.Height / scalingFactor);
+
+ if (newSize != ScaledScreenSize)
+ {
+ ScaledScreenSize = newSize;
+ screenChanged = true;
+ }
+ }
+ }
+
+ if (screenChanged)
+ OnScreenChanged?.Invoke(this, e);
+ }
+
+
+ void StartListeningForHingeChanges()
+ {
+ if (!IsDuo)
+ return;
+
+ _singleUseHingeSensor.OnSensorChanged += OnSensorChanged;
+ _singleUseHingeSensor.StartListening();
+ }
+
+ void StopListeningForHingeChanges()
+ {
+ if (!IsDuo)
+ return;
+
+ _singleUseHingeSensor.OnSensorChanged -= OnSensorChanged;
+ _singleUseHingeSensor.StopListening();
+ }
+
+ void OnSensorChanged(object sender, HingeSensor.HingeSensorChangedEventArgs e)
+ {
+ SetHingeAngle(e.HingeAngle);
+ }
+
+ void SetHingeAngle(int hingeAngle)
+ {
+ TaskCompletionSource toSet = null;
+ lock (_hingeAngleLock)
+ {
+ StopListeningForHingeChanges();
+ toSet = _gettingHingeAngle;
+ _gettingHingeAngle = null;
+ }
+
+ if (toSet != null)
+ toSet.SetResult(hingeAngle);
+ }
}
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.DualScreen/DualScreenService.uwp.cs b/Xamarin.Forms.DualScreen/DualScreenService.uwp.cs
index 76f4a0fab7b..944af422660 100644
--- a/Xamarin.Forms.DualScreen/DualScreenService.uwp.cs
+++ b/Xamarin.Forms.DualScreen/DualScreenService.uwp.cs
@@ -1,5 +1,7 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
+using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation.Metadata;
using Windows.Graphics.Display;
@@ -25,32 +27,50 @@ internal partial class DualScreenService : IDualScreenService, Platform.UWP.Dual
public DualScreenService()
{
- if(Window.Current != null)
+ if (Window.Current != null)
+ {
Window.Current.SizeChanged += OnCurrentSizeChanged;
+ }
}
- public Task GetHingeAngleAsync() => Task.FromResult(0);
+ public async Task GetHingeAngleAsync()
+ {
+ if (!ApiInformation.IsMethodPresent("Windows.Devices.Sensors.HingeAngleSensor", "GetDefaultAsync"))
+ {
+ return await NoDualScreenServiceImpl.Instance.GetHingeAngleAsync();
+ }
+
+#if UWP_18362
+ var sensor = await Windows.Devices.Sensors.HingeAngleSensor.GetDefaultAsync();
+
+ if (sensor == null)
+ return await NoDualScreenServiceImpl.Instance.GetHingeAngleAsync();
+
+ var currentReading = await sensor.GetCurrentReadingAsync();
+ return (int)currentReading.AngleInDegrees;
+#else
+ return await NoDualScreenServiceImpl.Instance.GetHingeAngleAsync();
+#endif
+ }
void OnCurrentSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
OnScreenChanged?.Invoke(this, EventArgs.Empty);
}
- bool IsDualScreenDevice => ApiInformation.IsMethodPresent("Windows.UI.ViewManagement.ApplicationView", "GetSpanningRects");
-
public bool IsSpanned
{
get
{
- if (!IsDualScreenDevice)
- return false;
-
- var visibleBounds = Window.Current.Bounds;
-
- if (visibleBounds.Height > 1200 || visibleBounds.Width > 1200)
- return true;
-
- return false;
+ var viewMode = (int)ApplicationView.GetForCurrentView().ViewMode;
+
+ switch (viewMode)
+ {
+ case 2:
+ return true;
+ default:
+ return false;
+ }
}
}
@@ -59,13 +79,33 @@ public bool IsSpanned
public bool IsLandscape
{
get
- {
- if (IsSpanned)
- return ApplicationView.GetForCurrentView().Orientation == ApplicationViewOrientation.Portrait;
- else
- return ApplicationView.GetForCurrentView().Orientation == ApplicationViewOrientation.Landscape;
- }
- }
+ {
+ if (!IsSpanned)
+ return ApplicationView.GetForCurrentView().Orientation == ApplicationViewOrientation.Landscape;
+
+#if UWP_18362
+ var displayRegions = ApplicationView.GetForCurrentView().GetDisplayRegions();
+ if (displayRegions.Count == 2)
+ {
+ // We are split in two panes. Layout accordingly
+ if (displayRegions[0].WorkAreaOffset.X != displayRegions[1].WorkAreaOffset.X)
+ {
+ return false;
+ }
+ else if (displayRegions[0].WorkAreaOffset.Y != displayRegions[1].WorkAreaOffset.Y)
+ {
+ return true;
+ }
+ else
+ {
+ return ApplicationView.GetForCurrentView().Orientation == ApplicationViewOrientation.Landscape;
+
+ }
+ }
+#endif
+ return ApplicationView.GetForCurrentView().Orientation == ApplicationViewOrientation.Landscape;
+ }
+ }
public Size ScaledScreenSize
{
@@ -78,21 +118,56 @@ public Size ScaledScreenSize
public Rectangle GetHinge()
{
- if (!IsDualScreenDevice)
+ if (!ApiInformation.IsMethodPresent("Windows.UI.ViewManagement.ApplicationView", "GetSpanningRects"))
return Rectangle.Zero;
- var screen = DisplayInformation.GetForCurrentView();
+ if (!IsSpanned)
+ return Rectangle.Zero;
- if (IsLandscape)
- {
- if (IsSpanned)
- return new Rectangle(0, 664 + 24, ScaledPixels(screen.ScreenWidthInRawPixels), 0);
- else
- return new Rectangle(0, 664, ScaledPixels(screen.ScreenWidthInRawPixels), 0);
- }
- else
- return new Rectangle(720, 0, 0, ScaledPixels(screen.ScreenHeightInRawPixels));
- }
+ var screen = DisplayInformation.GetForCurrentView();
+
+
+#if UWP_18362
+ var applicationView = ApplicationView.GetForCurrentView();
+ List spanningRects = null;
+
+#if UWP_19000
+ spanningRects = applicationView.GetSpanningRects().ToList();
+#endif
+
+ if (spanningRects?.Count == 2)
+ {
+ if(!IsLandscape)
+ {
+ var x = spanningRects[0].Width;
+ var hingeWidth = spanningRects[1].X - x;
+ return new Rectangle(x, 0, hingeWidth, ScaledPixels(screen.ScreenHeightInRawPixels));
+ }
+ else
+ {
+ var y = spanningRects[0].Height;
+ var hingeHeight = spanningRects[1].Y - y;
+ return new Rectangle(0, y, ScaledPixels(screen.ScreenWidthInRawPixels), hingeHeight);
+ }
+ }
+#endif
+
+ // fall back to hard coded
+ Rectangle returnValue = Rectangle.Zero;
+
+ if (IsLandscape)
+ {
+ if (IsSpanned)
+ returnValue = new Rectangle(0, 664 + 24, ScaledPixels(screen.ScreenWidthInRawPixels), 0);
+ else
+ returnValue = new Rectangle(0, 664, ScaledPixels(screen.ScreenWidthInRawPixels), 0);
+ }
+ else
+ returnValue = new Rectangle(720, 0, 0, ScaledPixels(screen.ScreenHeightInRawPixels));
+
+ return returnValue;
+
+ }
double ScaledPixels(double n)
=> n / DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
diff --git a/Xamarin.Forms.DualScreen/Xamarin.Forms.DualScreen.csproj b/Xamarin.Forms.DualScreen/Xamarin.Forms.DualScreen.csproj
index 07ca1f655c1..16d7c7e4a74 100644
--- a/Xamarin.Forms.DualScreen/Xamarin.Forms.DualScreen.csproj
+++ b/Xamarin.Forms.DualScreen/Xamarin.Forms.DualScreen.csproj
@@ -13,15 +13,6 @@
true
false
-
- $(DefineConstants);UWP_14393
-
-
- $(DefineConstants);UWP_18362
-
-
- $(DefineConstants);UWP
-
$(DefineConstants);ANDROID
diff --git a/Xamarin.Forms.Maps.Android/MapRenderer.cs b/Xamarin.Forms.Maps.Android/MapRenderer.cs
index a3b83182097..307b3ecb7d1 100644
--- a/Xamarin.Forms.Maps.Android/MapRenderer.cs
+++ b/Xamarin.Forms.Maps.Android/MapRenderer.cs
@@ -103,6 +103,7 @@ protected override void Dispose(bool disposing)
if (NativeMap != null)
{
NativeMap.MyLocationEnabled = false;
+ NativeMap.TrafficEnabled = false;
NativeMap.SetOnCameraMoveListener(null);
NativeMap.MarkerClick -= OnMarkerClick;
NativeMap.InfoWindowClick -= OnInfoWindowClick;
@@ -195,6 +196,10 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE
gmap.UiSettings.ZoomControlsEnabled = Map.HasZoomEnabled;
gmap.UiSettings.ZoomGesturesEnabled = Map.HasZoomEnabled;
}
+ else if (e.PropertyName == Map.TrafficEnabledProperty.PropertyName)
+ {
+ gmap.TrafficEnabled = Map.TrafficEnabled;
+ }
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
@@ -236,6 +241,7 @@ protected virtual void OnMapReady(GoogleMap map)
map.InfoWindowClick += OnInfoWindowClick;
map.MapClick += OnMapClick;
+ map.TrafficEnabled = Map.TrafficEnabled;
map.UiSettings.ZoomControlsEnabled = Map.HasZoomEnabled;
map.UiSettings.ZoomGesturesEnabled = Map.HasZoomEnabled;
map.UiSettings.ScrollGesturesEnabled = Map.HasScrollEnabled;
diff --git a/Xamarin.Forms.Maps.Android/Xamarin.Forms.Maps.Android.csproj b/Xamarin.Forms.Maps.Android/Xamarin.Forms.Maps.Android.csproj
index 24301f222d3..2627ef855b1 100644
--- a/Xamarin.Forms.Maps.Android/Xamarin.Forms.Maps.Android.csproj
+++ b/Xamarin.Forms.Maps.Android/Xamarin.Forms.Maps.Android.csproj
@@ -37,4 +37,4 @@
-
\ No newline at end of file
+
diff --git a/Xamarin.Forms.Maps.Tizen/MapRenderer.cs b/Xamarin.Forms.Maps.Tizen/MapRenderer.cs
index 9b08c84d431..bb0a474ac90 100644
--- a/Xamarin.Forms.Maps.Tizen/MapRenderer.cs
+++ b/Xamarin.Forms.Maps.Tizen/MapRenderer.cs
@@ -79,6 +79,7 @@ protected override void OnElementChanged(ElementChangedEventArgs