diff --git a/Ooui.sln b/Ooui.sln
index a5e7898..4cdc2c4 100644
--- a/Ooui.sln
+++ b/Ooui.sln
@@ -1,21 +1,20 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26124.0
+VisualStudioVersion = 15.0.27130.2003
MinimumVisualStudioVersion = 15.0.26124.0
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ooui", "Ooui\Ooui.csproj", "{DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ooui", "Ooui\Ooui.csproj", "{DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{CDF8BB01-40BB-402F-8446-47AA6F1628F3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "Samples\Samples.csproj", "{CDF8BB01-40BB-402F-8446-47AA6F1628F3}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ooui.Forms", "Ooui.Forms\Ooui.Forms.csproj", "{DB819A2F-91E1-40FB-8D48-6544169966B8}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ooui.Forms", "Ooui.Forms\Ooui.Forms.csproj", "{DB819A2F-91E1-40FB-8D48-6544169966B8}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ooui.AspNetCore", "Ooui.AspNetCore\Ooui.AspNetCore.csproj", "{2EDF0328-698B-458A-B10C-AB1B4786A6CA}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ooui.AspNetCore", "Ooui.AspNetCore\Ooui.AspNetCore.csproj", "{2EDF0328-698B-458A-B10C-AB1B4786A6CA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PlatformSamples", "PlatformSamples", "{12ADF328-BBA8-48FC-9AF1-F11B7921D9EA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreMvc", "PlatformSamples\AspNetCoreMvc\AspNetCoreMvc.csproj", "{7C6D477C-3378-4A86-9C31-AAD51204120B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreMvc", "PlatformSamples\AspNetCoreMvc\AspNetCoreMvc.csproj", "{7C6D477C-3378-4A86-9C31-AAD51204120B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -26,46 +25,43 @@ Global
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Debug|x64.ActiveCfg = Debug|x64
- {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Debug|x64.Build.0 = Debug|x64
- {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Debug|x86.ActiveCfg = Debug|x86
- {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Debug|x86.Build.0 = Debug|x86
+ {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Debug|x64.Build.0 = Debug|Any CPU
+ {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Debug|x86.Build.0 = Debug|Any CPU
{DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Release|Any CPU.Build.0 = Release|Any CPU
- {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Release|x64.ActiveCfg = Release|x64
- {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Release|x64.Build.0 = Release|x64
- {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Release|x86.ActiveCfg = Release|x86
- {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Release|x86.Build.0 = Release|x86
+ {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Release|x64.ActiveCfg = Release|Any CPU
+ {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Release|x64.Build.0 = Release|Any CPU
+ {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Release|x86.ActiveCfg = Release|Any CPU
+ {DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}.Release|x86.Build.0 = Release|Any CPU
{CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Debug|x64.ActiveCfg = Debug|x64
- {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Debug|x64.Build.0 = Debug|x64
- {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Debug|x86.ActiveCfg = Debug|x86
- {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Debug|x86.Build.0 = Debug|x86
+ {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Debug|x64.Build.0 = Debug|Any CPU
+ {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Debug|x86.Build.0 = Debug|Any CPU
{CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Release|Any CPU.Build.0 = Release|Any CPU
- {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Release|x64.ActiveCfg = Release|x64
- {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Release|x64.Build.0 = Release|x64
- {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Release|x86.ActiveCfg = Release|x86
- {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Release|x86.Build.0 = Release|x86
+ {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Release|x64.ActiveCfg = Release|Any CPU
+ {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Release|x64.Build.0 = Release|Any CPU
+ {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Release|x86.ActiveCfg = Release|Any CPU
+ {CDF8BB01-40BB-402F-8446-47AA6F1628F3}.Release|x86.Build.0 = Release|Any CPU
{78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Debug|x64.ActiveCfg = Debug|x64
- {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Debug|x64.Build.0 = Debug|x64
- {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Debug|x86.ActiveCfg = Debug|x86
- {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Debug|x86.Build.0 = Debug|x86
+ {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Debug|x64.Build.0 = Debug|Any CPU
+ {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Debug|x86.Build.0 = Debug|Any CPU
{78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Release|Any CPU.Build.0 = Release|Any CPU
- {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Release|x64.ActiveCfg = Release|x64
- {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Release|x64.Build.0 = Release|x64
- {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Release|x86.ActiveCfg = Release|x86
- {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Release|x86.Build.0 = Release|x86
+ {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Release|x64.ActiveCfg = Release|Any CPU
+ {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Release|x64.Build.0 = Release|Any CPU
+ {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Release|x86.ActiveCfg = Release|Any CPU
+ {78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}.Release|x86.Build.0 = Release|Any CPU
{DB819A2F-91E1-40FB-8D48-6544169966B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DB819A2F-91E1-40FB-8D48-6544169966B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB819A2F-91E1-40FB-8D48-6544169966B8}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -103,6 +99,15 @@ Global
{7C6D477C-3378-4A86-9C31-AAD51204120B}.Release|x86.ActiveCfg = Release|Any CPU
{7C6D477C-3378-4A86-9C31-AAD51204120B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {7C6D477C-3378-4A86-9C31-AAD51204120B} = {12ADF328-BBA8-48FC-9AF1-F11B7921D9EA}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {9370DD6D-816D-4E0F-8356-835F65DCF3AA}
+ EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
$0.TextStylePolicy = $1
@@ -122,7 +127,4 @@ Global
$2.SpacingAfterMethodDeclarationName = True
$2.SpaceAfterMethodCallName = True
EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {7C6D477C-3378-4A86-9C31-AAD51204120B} = {12ADF328-BBA8-48FC-9AF1-F11B7921D9EA}
- EndGlobalSection
EndGlobal
diff --git a/PlatformSamples/AspNetCoreMvc/Properties/launchSettings.json b/PlatformSamples/AspNetCoreMvc/Properties/launchSettings.json
new file mode 100644
index 0000000..e94d59b
--- /dev/null
+++ b/PlatformSamples/AspNetCoreMvc/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:58785/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "AspNetCoreMvc": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:58786/"
+ }
+ }
+}
\ No newline at end of file
diff --git a/Samples/DotMatrixClock/DotMatrixClockPage.cs b/Samples/DotMatrixClock/DotMatrixClockPage.cs
new file mode 100644
index 0000000..ae08948
--- /dev/null
+++ b/Samples/DotMatrixClock/DotMatrixClockPage.cs
@@ -0,0 +1,170 @@
+using System;
+using Xamarin.Forms;
+
+namespace DotMatrixClock
+{
+ public partial class DotMatrixClockPage : ContentPage
+ {
+ // Total dots horizontally and vertically.
+ const int horzDots = 41;
+ const int vertDots = 7;
+
+ // 5 x 7 dot matrix patterns for 0 through 9.
+ static readonly int[, ,] numberPatterns = new int[10, 7, 5]
+ {
+ {
+ { 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 1, 1}, { 1, 0, 1, 0, 1},
+ { 1, 1, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
+ },
+ {
+ { 0, 0, 1, 0, 0}, { 0, 1, 1, 0, 0}, { 0, 0, 1, 0, 0}, { 0, 0, 1, 0, 0},
+ { 0, 0, 1, 0, 0}, { 0, 0, 1, 0, 0}, { 0, 1, 1, 1, 0}
+ },
+ {
+ { 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0},
+ { 0, 0, 1, 0, 0}, { 0, 1, 0, 0, 0}, { 1, 1, 1, 1, 1}
+ },
+ {
+ { 1, 1, 1, 1, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 1, 0, 0}, { 0, 0, 0, 1, 0},
+ { 0, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
+ },
+ {
+ { 0, 0, 0, 1, 0}, { 0, 0, 1, 1, 0}, { 0, 1, 0, 1, 0}, { 1, 0, 0, 1, 0},
+ { 1, 1, 1, 1, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 0, 1, 0}
+ },
+ {
+ { 1, 1, 1, 1, 1}, { 1, 0, 0, 0, 0}, { 1, 1, 1, 1, 0}, { 0, 0, 0, 0, 1},
+ { 0, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
+ },
+ {
+ { 0, 0, 1, 1, 0}, { 0, 1, 0, 0, 0}, { 1, 0, 0, 0, 0}, { 1, 1, 1, 1, 0},
+ { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
+ },
+ {
+ { 1, 1, 1, 1, 1}, { 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 1, 0, 0},
+ { 0, 1, 0, 0, 0}, { 0, 1, 0, 0, 0}, { 0, 1, 0, 0, 0}
+ },
+ {
+ { 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0},
+ { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
+ },
+ {
+ { 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 1},
+ { 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0}, { 0, 1, 1, 0, 0}
+ },
+ };
+
+ // Dot matrix pattern for a colon.
+ static readonly int[,] colonPattern = new int[7, 2]
+ {
+ { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 }
+ };
+
+ // BoxView colors for on and off.
+ static readonly Color colorOn = Color.Red;
+ static readonly Color colorOff = new Color(0.5, 0.5, 0.5, 0.25);
+
+ // Box views for 6 digits, 7 rows, 5 columns.
+ BoxView[, ,] digitBoxViews = new BoxView[6, 7, 5];
+
+ public DotMatrixClockPage()
+ {
+ InitializeComponent();
+
+ // BoxView dot dimensions.
+ double height = 0.85 / vertDots;
+ double width = 0.85 / horzDots;
+
+ // Create and assemble the BoxViews.
+ double xIncrement = 1.0 / (horzDots - 1);
+ double yIncrement = 1.0 / (vertDots - 1);
+ double x = 0;
+
+ for (int digit = 0; digit < 6; digit++)
+ {
+ for (int col = 0; col < 5; col++)
+ {
+ double y = 0;
+
+ for (int row = 0; row < 7; row++)
+ {
+ // Create the digit BoxView and add to layout.
+ BoxView boxView = new BoxView();
+ digitBoxViews[digit, row, col] = boxView;
+ absoluteLayout.Children.Add(boxView,
+ new Rectangle(x, y, width, height),
+ AbsoluteLayoutFlags.All);
+ y += yIncrement;
+ }
+ x += xIncrement;
+ }
+ x += xIncrement;
+
+ // Colons between the hours, minutes, and seconds.
+ if (digit == 1 || digit == 3)
+ {
+ int colon = digit / 2;
+
+ for (int col = 0; col < 2; col++)
+ {
+ double y = 0;
+
+ for (int row = 0; row < 7; row++)
+ {
+ // Create the BoxView and set the color.
+ BoxView boxView = new BoxView
+ {
+ Color = colonPattern[row, col] == 1 ?
+ colorOn : colorOff
+ };
+ absoluteLayout.Children.Add(boxView,
+ new Rectangle(x, y, width, height),
+ AbsoluteLayoutFlags.All);
+ y += yIncrement;
+ }
+ x += xIncrement;
+ }
+ x += xIncrement;
+ }
+ }
+
+ // Set the timer and initialize with a manual call.
+ Device.StartTimer(TimeSpan.FromSeconds(1), OnTimer);
+ OnTimer();
+ }
+
+ void OnPageSizeChanged(object sender, EventArgs args)
+ {
+ // No chance a display will have an aspect ratio > 41:7
+ absoluteLayout.HeightRequest = vertDots * Width / horzDots;
+ }
+
+ bool OnTimer()
+ {
+ DateTime dateTime = DateTime.Now;
+
+ // Convert 24-hour clock to 12-hour clock.
+ int hour = (dateTime.Hour + 11) % 12 + 1;
+
+ // Set the dot colors for each digit separately.
+ SetDotMatrix(0, hour / 10);
+ SetDotMatrix(1, hour % 10);
+ SetDotMatrix(2, dateTime.Minute / 10);
+ SetDotMatrix(3, dateTime.Minute % 10);
+ SetDotMatrix(4, dateTime.Second / 10);
+ SetDotMatrix(5, dateTime.Second % 10);
+ return true;
+ }
+
+ void SetDotMatrix(int index, int digit)
+ {
+ for (int row = 0; row < 7; row++)
+ for (int col = 0; col < 5; col++)
+ {
+ bool isOn = numberPatterns[digit, row, col] == 1;
+ Color color = isOn ? colorOn : colorOff;
+ digitBoxViews[index, row, col].Color = color;
+ }
+ }
+ }
+}
diff --git a/Samples/DotMatrixClock/DotMatrixClockPage.xaml b/Samples/DotMatrixClock/DotMatrixClockPage.xaml
new file mode 100644
index 0000000..41ec329
--- /dev/null
+++ b/Samples/DotMatrixClock/DotMatrixClockPage.xaml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/Samples/DotMatrixClockSample.cs b/Samples/DotMatrixClockSample.cs
new file mode 100644
index 0000000..b901e86
--- /dev/null
+++ b/Samples/DotMatrixClockSample.cs
@@ -0,0 +1,21 @@
+using Ooui;
+using Xamarin.Forms;
+
+namespace Samples
+{
+ public class DotMatrixClockSample : ISample
+ {
+ public string Title => "Xamarin.Forms DoMatrixClock";
+
+ public Ooui.Element CreateElement()
+ {
+ var page = new DotMatrixClock.DotMatrixClockPage();
+ return page.GetOouiElement();
+ }
+
+ public void Publish()
+ {
+ UI.Publish("/dotmatrixclock", CreateElement);
+ }
+ }
+}
diff --git a/Samples/Program.cs b/Samples/Program.cs
index 7a3f706..6e1152a 100644
--- a/Samples/Program.cs
+++ b/Samples/Program.cs
@@ -30,7 +30,11 @@ namespace Samples
new DrawSample ().Publish ();
new FilesSample ().Publish ();
new DisplayAlertSample ().Publish ();
+ new DotMatrixClockSample().Publish();
new EditorSample().Publish();
+ new TipCalcSample().Publish();
+ new WeatherAppSample().Publish();
+ new XuzzleSample().Publish();
UI.Present ("/display-alert");
diff --git a/Samples/Samples.csproj b/Samples/Samples.csproj
index 72ede52..bdbc539 100644
--- a/Samples/Samples.csproj
+++ b/Samples/Samples.csproj
@@ -38,6 +38,15 @@
MSBuild:UpdateDesignTimeXaml
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
MSBuild:UpdateDesignTimeXaml
@@ -46,6 +55,9 @@
+
+
+
Exe
diff --git a/Samples/TipCalc/DoubleRoundingConverter.cs b/Samples/TipCalc/DoubleRoundingConverter.cs
new file mode 100644
index 0000000..ce6494c
--- /dev/null
+++ b/Samples/TipCalc/DoubleRoundingConverter.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+
+namespace TipCalc
+{
+ public class DoubleRoundingConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType,
+ object parameter, CultureInfo culture)
+ {
+ return Round((double)value, parameter);
+ }
+
+ public object ConvertBack(object value, Type targetType,
+ object parameter, CultureInfo culture)
+ {
+ return Round((double)value, parameter);
+ }
+
+ double Round(double number, object parameter)
+ {
+ double precision = 1;
+
+ // Assume parameter is string encoding precision.
+ if (parameter != null)
+ {
+ precision = Double.Parse((string)parameter);
+ }
+ return precision * Math.Round(number / precision);
+ }
+ }
+}
diff --git a/Samples/TipCalc/DoubleToStringConverter.cs b/Samples/TipCalc/DoubleToStringConverter.cs
new file mode 100644
index 0000000..447d488
--- /dev/null
+++ b/Samples/TipCalc/DoubleToStringConverter.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Globalization;
+using Xamarin.Forms;
+
+namespace TipCalc
+{
+ public class DoubleToStringConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType,
+ object parameter, CultureInfo culture)
+ {
+ // Assumes value is double.
+ double number = (double)value;
+
+ // Return empty string for a zero (good for Entry views).
+ if (number == 0)
+ {
+ return "";
+ }
+
+ return number.ToString();
+ }
+
+ public object ConvertBack(object value, Type targetType,
+ object parameter, CultureInfo culture)
+ {
+ double number = 0;
+ Double.TryParse((string)value, out number);
+ return number;
+ }
+ }
+}
diff --git a/Samples/TipCalc/TipCalcModel.cs b/Samples/TipCalc/TipCalcModel.cs
new file mode 100644
index 0000000..f3164f7
--- /dev/null
+++ b/Samples/TipCalc/TipCalcModel.cs
@@ -0,0 +1,111 @@
+using System;
+using System.ComponentModel;
+
+namespace TipCalc
+{
+ class TipCalcModel : INotifyPropertyChanged
+ {
+ double subTotal, postTaxTotal, tipPercent, tipAmount, total;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public double SubTotal
+ {
+ set
+ {
+ if (subTotal != value)
+ {
+ subTotal = value;
+ OnPropertyChanged("SubTotal");
+ Recalculate();
+ }
+ }
+ get
+ {
+ return subTotal;
+ }
+ }
+
+ public double PostTaxTotal
+ {
+ set
+ {
+ if (postTaxTotal != value)
+ {
+ postTaxTotal = value;
+ OnPropertyChanged("PostTaxTotal");
+ Recalculate();
+ }
+ }
+ get
+ {
+ return postTaxTotal;
+ }
+ }
+
+ public double TipPercent
+ {
+ set
+ {
+ if (tipPercent != value)
+ {
+ tipPercent = value;
+ OnPropertyChanged("TipPercent");
+ Recalculate();
+ }
+ }
+ get
+ {
+ return tipPercent;
+ }
+ }
+
+ public double TipAmount
+ {
+ set
+ {
+ if (tipAmount != value)
+ {
+ tipAmount = value;
+ OnPropertyChanged("TipAmount");
+ }
+ }
+ get
+ {
+ return tipAmount;
+ }
+ }
+
+ public double Total
+ {
+ set
+ {
+ if (total != value)
+ {
+ total = value;
+ OnPropertyChanged("Total");
+ }
+ }
+ get
+ {
+ return total;
+ }
+ }
+
+ void Recalculate()
+ {
+ this.TipAmount = Math.Round(this.TipPercent * this.SubTotal / 100, 2);
+
+ // Round total to nearest quarter.
+ this.Total = Math.Round(4 * (this.PostTaxTotal + this.TipAmount)) / 4;
+ }
+
+ protected void OnPropertyChanged(string propertyName)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+ }
+}
diff --git a/Samples/TipCalc/TipCalcPage.xaml b/Samples/TipCalc/TipCalcPage.xaml
new file mode 100644
index 0000000..f8b69cb
--- /dev/null
+++ b/Samples/TipCalc/TipCalcPage.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/TipCalc/TipCalcPage.xaml.cs b/Samples/TipCalc/TipCalcPage.xaml.cs
new file mode 100644
index 0000000..fd9823e
--- /dev/null
+++ b/Samples/TipCalc/TipCalcPage.xaml.cs
@@ -0,0 +1,10 @@
+namespace TipCalc
+{
+ public partial class TipCalcPage
+ {
+ public TipCalcPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Samples/TipCalcSample.cs b/Samples/TipCalcSample.cs
new file mode 100644
index 0000000..a3440d7
--- /dev/null
+++ b/Samples/TipCalcSample.cs
@@ -0,0 +1,21 @@
+using Ooui;
+using Xamarin.Forms;
+
+namespace Samples
+{
+ public class TipCalcSample : ISample
+ {
+ public string Title => "Xamarin.Forms TipCalc";
+
+ public Ooui.Element CreateElement()
+ {
+ var page = new TipCalc.TipCalcPage();
+ return page.GetOouiElement();
+ }
+
+ public void Publish()
+ {
+ UI.Publish("/tipcalc", CreateElement);
+ }
+ }
+}
diff --git a/Samples/WeatherApp/Core.cs b/Samples/WeatherApp/Core.cs
new file mode 100644
index 0000000..31ee5e9
--- /dev/null
+++ b/Samples/WeatherApp/Core.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Threading.Tasks;
+
+namespace WeatherApp
+{
+ public class Core
+ {
+ public static async Task GetWeather(string zipCode)
+ {
+ //Sign up for a free API key at http://openweathermap.org/appid
+ string key = "fc9f6c524fc093759cd28d41fda89a1b";
+ string queryString = "http://api.openweathermap.org/data/2.5/weather?zip="
+ + zipCode + "&appid=" + key;
+
+ var results = await DataService.getDataFromService(queryString).ConfigureAwait(false);
+
+ if (results["weather"] != null)
+ {
+ Weather weather = new Weather
+ {
+ Title = (string)results["name"],
+ Temperature = (string)results["main"]["temp"] + " F",
+ Wind = (string)results["wind"]["speed"] + " mph",
+ Humidity = (string)results["main"]["humidity"] + " %",
+ Visibility = (string)results["weather"][0]["main"]
+ };
+
+ DateTime time = new DateTime(1970, 1, 1, 0, 0, 0, 0);
+ DateTime sunrise = time.AddSeconds((double)results["sys"]["sunrise"]);
+ DateTime sunset = time.AddSeconds((double)results["sys"]["sunset"]);
+ weather.Sunrise = sunrise.ToString() + " UTC";
+ weather.Sunset = sunset.ToString() + " UTC";
+ return weather;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/Samples/WeatherApp/DataService.cs b/Samples/WeatherApp/DataService.cs
new file mode 100644
index 0000000..ca1516e
--- /dev/null
+++ b/Samples/WeatherApp/DataService.cs
@@ -0,0 +1,25 @@
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using System.Net.Http;
+using Newtonsoft.Json.Linq;
+
+namespace WeatherApp
+{
+ public class DataService
+ {
+ public static async Task getDataFromService(string queryString)
+ {
+ HttpClient client = new HttpClient();
+ var response = await client.GetAsync(queryString);
+
+ JContainer data = null;
+ if (response != null)
+ {
+ string json = response.Content.ReadAsStringAsync().Result;
+ data = (JContainer)JsonConvert.DeserializeObject(json);
+ }
+
+ return data;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Samples/WeatherApp/Weather.cs b/Samples/WeatherApp/Weather.cs
new file mode 100644
index 0000000..e6e30f4
--- /dev/null
+++ b/Samples/WeatherApp/Weather.cs
@@ -0,0 +1,26 @@
+namespace WeatherApp
+{
+ public class Weather
+ {
+ public string Title { get; set; }
+ public string Temperature { get; set; }
+ public string Wind { get; set; }
+ public string Humidity { get; set; }
+ public string Visibility { get; set; }
+ public string Sunrise { get; set; }
+ public string Sunset { get; set; }
+
+ public Weather()
+ {
+ //Because labels bind to these values, set them to an empty string to
+ //ensure that the label appears on all platforms by default.
+ this.Title = " ";
+ this.Temperature = " ";
+ this.Wind = " ";
+ this.Humidity = " ";
+ this.Visibility = " ";
+ this.Sunrise = " ";
+ this.Sunset = " ";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Samples/WeatherApp/WeatherPage.xaml b/Samples/WeatherApp/WeatherPage.xaml
new file mode 100644
index 0000000..29bc1e0
--- /dev/null
+++ b/Samples/WeatherApp/WeatherPage.xaml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/WeatherApp/WeatherPage.xaml.cs b/Samples/WeatherApp/WeatherPage.xaml.cs
new file mode 100644
index 0000000..b1f3c07
--- /dev/null
+++ b/Samples/WeatherApp/WeatherPage.xaml.cs
@@ -0,0 +1,31 @@
+using System;
+using Xamarin.Forms;
+
+namespace WeatherApp
+{
+ public partial class WeatherPage : ContentPage
+ {
+ public WeatherPage()
+ {
+ InitializeComponent();
+ this.Title = "Sample Weather App";
+ getWeatherBtn.Clicked += GetWeatherBtn_Clicked;
+
+ //Set the default binding to a default object for now
+ this.BindingContext = new Weather();
+ }
+
+ private async void GetWeatherBtn_Clicked(object sender, EventArgs e)
+ {
+ if (!String.IsNullOrEmpty(zipCodeEntry.Text))
+ {
+ Weather weather = await Core.GetWeather(zipCodeEntry.Text);
+ if (weather != null)
+ {
+ this.BindingContext = weather;
+ getWeatherBtn.Text = "Search Again";
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Samples/WeatherAppSample.cs b/Samples/WeatherAppSample.cs
new file mode 100644
index 0000000..3119138
--- /dev/null
+++ b/Samples/WeatherAppSample.cs
@@ -0,0 +1,21 @@
+using Ooui;
+using Xamarin.Forms;
+
+namespace Samples
+{
+ public class WeatherAppSample : ISample
+ {
+ public string Title => "Xamarin.Forms WeatherApp";
+
+ public Ooui.Element CreateElement()
+ {
+ var page = new WeatherApp.WeatherPage();
+ return page.GetOouiElement();
+ }
+
+ public void Publish()
+ {
+ UI.Publish("/weatherapp", CreateElement);
+ }
+ }
+}
diff --git a/Samples/Xuzzle/XuzzlePage.cs b/Samples/Xuzzle/XuzzlePage.cs
new file mode 100644
index 0000000..6acf714
--- /dev/null
+++ b/Samples/Xuzzle/XuzzlePage.cs
@@ -0,0 +1,280 @@
+using System;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+
+namespace Xuzzle
+{
+ public class XuzzlePage : ContentPage
+ {
+ // Number of squares horizontally and vertically,
+ // but if you change it, some code will break.
+ static readonly int NUM = 4;
+
+ // Array of XuzzleSquare views, and empty row & column.
+ XuzzleSquare[,] squares = new XuzzleSquare[NUM, NUM];
+ int emptyRow = NUM - 1;
+ int emptyCol = NUM - 1;
+
+ StackLayout stackLayout;
+ AbsoluteLayout absoluteLayout;
+ Button randomizeButton;
+ Label timeLabel;
+ double squareSize;
+ bool isBusy;
+ bool isPlaying;
+
+ public XuzzlePage()
+ {
+ // AbsoluteLayout to host the squares.
+ absoluteLayout = new AbsoluteLayout()
+ {
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.Center
+ };
+
+ // Create XuzzleSquare's for all the rows and columns.
+ string text = "{XAMARIN.FORMS}";
+ string winText = "CONGRATULATIONS";
+ int index = 0;
+
+ for (int row = 0; row < NUM; row++)
+ {
+ for (int col = 0; col < NUM; col++)
+ {
+ // But skip the last one!
+ if (row == NUM - 1 && col == NUM - 1)
+ break;
+
+ // Instantiate XuzzleSquare.
+ XuzzleSquare square = new XuzzleSquare(text[index], winText[index], index)
+ {
+ Row = row,
+ Col = col
+ };
+
+ // Add tap recognition
+ TapGestureRecognizer tapGestureRecognizer = new TapGestureRecognizer
+ {
+ Command = new Command(OnSquareTapped),
+ CommandParameter = square
+ };
+ square.GestureRecognizers.Add(tapGestureRecognizer);
+
+ // Add it to the array and the AbsoluteLayout.
+ squares[row, col] = square;
+ absoluteLayout.Children.Add(square);
+ index++;
+ }
+ }
+
+ // This is the "Randomize" button.
+ randomizeButton = new Button
+ {
+ Text = "Randomize",
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.CenterAndExpand
+ };
+ randomizeButton.Clicked += OnRandomizeButtonClicked;
+
+ // Label to display elapsed time.
+ timeLabel = new Label
+ {
+ FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
+ FontAttributes = FontAttributes.Bold,
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.CenterAndExpand
+ };
+
+ // Put everything in a StackLayout.
+ stackLayout = new StackLayout
+ {
+ Children = {
+ new StackLayout {
+ VerticalOptions = LayoutOptions.FillAndExpand,
+ HorizontalOptions = LayoutOptions.FillAndExpand,
+ Children = {
+ randomizeButton,
+ timeLabel
+ }
+ },
+ absoluteLayout
+ }
+ };
+ stackLayout.SizeChanged += OnStackSizeChanged;
+
+ // And set that to the content of the page.
+ this.Padding = new Thickness(0, Device.RuntimePlatform == Device.iOS ? 20 : 0, 0, 0);
+ this.Content = stackLayout;
+ }
+
+ void OnStackSizeChanged(object sender, EventArgs args)
+ {
+ double width = stackLayout.Width;
+ double height = stackLayout.Height;
+
+ if (width <= 0 || height <= 0)
+ return;
+
+ // Orient StackLayout based on portrait/landscape mode.
+ stackLayout.Orientation = (width < height) ? StackOrientation.Vertical :
+ StackOrientation.Horizontal;
+
+ // Calculate square size and position based on stack size.
+ squareSize = Math.Min(width, height) / NUM;
+ absoluteLayout.WidthRequest = NUM * squareSize;
+ absoluteLayout.HeightRequest = NUM * squareSize;
+
+ foreach (View view in absoluteLayout.Children)
+ {
+ XuzzleSquare square = (XuzzleSquare)view;
+ square.SetLabelFont(0.4 * squareSize, FontAttributes.Bold);
+
+ AbsoluteLayout.SetLayoutBounds(square,
+ new Rectangle(square.Col * squareSize,
+ square.Row * squareSize,
+ squareSize,
+ squareSize));
+ }
+ }
+
+ async void OnSquareTapped(object parameter)
+ {
+ if (isBusy)
+ return;
+
+ isBusy = true;
+ XuzzleSquare tappedSquare = (XuzzleSquare)parameter;
+ await ShiftIntoEmpty(tappedSquare.Row, tappedSquare.Col);
+ isBusy = false;
+
+ // Check for a "win".
+ if (isPlaying)
+ {
+ int index;
+
+ for (index = 0; index < NUM * NUM - 1; index++)
+ {
+ int row = index / NUM;
+ int col = index % NUM;
+ XuzzleSquare square = squares[row, col];
+ if (square == null || square.Index != index)
+ break;
+ }
+
+ // We have a winner!
+ if (index == NUM * NUM - 1)
+ {
+ isPlaying = false;
+ await DoWinAnimation();
+ }
+ }
+ }
+
+ async Task ShiftIntoEmpty(int tappedRow, int tappedCol, uint length = 100)
+ {
+ // Shift columns.
+ if (tappedRow == emptyRow && tappedCol != emptyCol)
+ {
+ int inc = Math.Sign(tappedCol - emptyCol);
+ int begCol = emptyCol + inc;
+ int endCol = tappedCol + inc;
+
+ for (int col = begCol; col != endCol; col += inc)
+ {
+ await AnimateSquare(emptyRow, col, emptyRow, emptyCol, length);
+ }
+ }
+ // Shift rows.
+ else if (tappedCol == emptyCol && tappedRow != emptyRow)
+ {
+ int inc = Math.Sign(tappedRow - emptyRow);
+ int begRow = emptyRow + inc;
+ int endRow = tappedRow + inc;
+
+ for (int row = begRow; row != endRow; row += inc)
+ {
+ await AnimateSquare(row, emptyCol, emptyRow, emptyCol, length);
+ }
+ }
+ }
+
+ async Task AnimateSquare(int row, int col, int newRow, int newCol, uint length)
+ {
+ // The Square to be animated.
+ XuzzleSquare animaSquare = squares[row, col];
+
+ // The destination rectangle.
+ Rectangle rect = new Rectangle(squareSize * emptyCol,
+ squareSize * emptyRow,
+ squareSize,
+ squareSize);
+
+ // This is the actual animation call.
+ await animaSquare.LayoutTo(rect, length);
+
+ // Set several variables and properties for new layout.
+ squares[newRow, newCol] = animaSquare;
+ animaSquare.Row = newRow;
+ animaSquare.Col = newCol;
+ squares[row, col] = null;
+ emptyRow = row;
+ emptyCol = col;
+ }
+
+ async void OnRandomizeButtonClicked(object sender, EventArgs args)
+ {
+ Button button = (Button)sender;
+ button.IsEnabled = false;
+ Random rand = new Random();
+
+ isBusy = true;
+
+ // Simulate some fast crazy taps.
+ for (int i = 0; i < 100; i++)
+ {
+ await ShiftIntoEmpty(rand.Next(NUM), emptyCol, 25);
+ await ShiftIntoEmpty(emptyRow, rand.Next(NUM), 25);
+ }
+ button.IsEnabled = true;
+
+ isBusy = false;
+
+ // Prepare for playing.
+ DateTime startTime = DateTime.Now;
+
+ Device.StartTimer(TimeSpan.FromSeconds(1), () => {
+ // Round duration and get rid of milliseconds.
+ TimeSpan timeSpan = (DateTime.Now - startTime) +
+ TimeSpan.FromSeconds(0.5);
+ timeSpan = new TimeSpan(timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
+
+ // Display the duration.
+ if (isPlaying)
+ timeLabel.Text = timeSpan.ToString("t");
+ return isPlaying;
+ });
+ this.isPlaying = true;
+ }
+
+ async Task DoWinAnimation()
+ {
+ // Inhibit all input.
+ randomizeButton.IsEnabled = false;
+ isBusy = true;
+
+ for (int cycle = 0; cycle < 2; cycle++)
+ {
+ foreach (XuzzleSquare square in squares)
+ if (square != null)
+ await square.AnimateWinAsync(cycle == 1);
+
+ if (cycle == 0)
+ await Task.Delay(1500);
+ }
+
+ // All input.
+ randomizeButton.IsEnabled = true;
+ isBusy = false;
+ }
+ }
+}
diff --git a/Samples/Xuzzle/XuzzleSquare.cs b/Samples/Xuzzle/XuzzleSquare.cs
new file mode 100644
index 0000000..203db53
--- /dev/null
+++ b/Samples/Xuzzle/XuzzleSquare.cs
@@ -0,0 +1,73 @@
+using System.Threading.Tasks;
+using Xamarin.Forms;
+
+namespace Xuzzle
+{
+ public class XuzzleSquare : ContentView
+ {
+ Label label;
+ string normText, winText;
+
+ public XuzzleSquare(char normChar, char winChar, int index)
+ {
+ this.Index = index;
+ this.normText = normChar.ToString();
+ this.winText = winChar.ToString();
+
+ // A Frame surrounding two Labels.
+ label = new Label
+ {
+ Text = this.normText,
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.CenterAndExpand
+ };
+
+ Label tinyLabel = new Label
+ {
+ Text = (index + 1).ToString(),
+ FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(Label)),
+ HorizontalOptions = LayoutOptions.End
+ };
+
+ this.Padding = new Thickness(3);
+ this.Content = new Frame
+ {
+ OutlineColor = Color.Accent,
+ Padding = new Thickness(5, 10, 5, 0),
+ Content = new StackLayout
+ {
+ Spacing = 0,
+ Children = {
+ label,
+ tinyLabel,
+ }
+ }
+ };
+
+ // Don't let touch pass us by.
+ this.BackgroundColor = Color.Transparent;
+ }
+
+ // Retain current Row and Col position.
+ public int Index { private set; get; }
+
+ public int Row { set; get; }
+
+ public int Col { set; get; }
+
+ public async Task AnimateWinAsync(bool isReverse)
+ {
+ uint length = 150;
+ await Task.WhenAll(this.ScaleTo(3, length), this.RotateTo(180, length));
+ label.Text = isReverse ? normText : winText;
+ await Task.WhenAll(this.ScaleTo(1, length), this.RotateTo(360, length));
+ this.Rotation = 0;
+ }
+
+ public void SetLabelFont(double fontSize, FontAttributes attributes)
+ {
+ label.FontSize = fontSize;
+ label.FontAttributes = attributes;
+ }
+ }
+}
diff --git a/Samples/XuzzleSample.cs b/Samples/XuzzleSample.cs
new file mode 100644
index 0000000..10f1d2f
--- /dev/null
+++ b/Samples/XuzzleSample.cs
@@ -0,0 +1,21 @@
+using Ooui;
+using Xamarin.Forms;
+
+namespace Samples
+{
+ public class XuzzleSample : ISample
+ {
+ public string Title => "Xamarin.Forms Xuzzle";
+
+ public Ooui.Element CreateElement()
+ {
+ var page = new Xuzzle.XuzzlePage();
+ return page.GetOouiElement();
+ }
+
+ public void Publish()
+ {
+ UI.Publish("/xuzzle", CreateElement);
+ }
+ }
+}