diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj
index 485487d1465..21f81ef529f 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj
+++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj
@@ -322,6 +322,7 @@
+
@@ -507,6 +508,7 @@
+
@@ -603,6 +605,9 @@
LayoutTransformControlPage.xaml
+
+ InfiniteCanvasPage.xaml
+
ListViewExtensionsPage.xaml
@@ -970,6 +975,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvas.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvas.bind
new file mode 100644
index 00000000000..e0ea4443bc9
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvas.bind
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvas.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvas.png
new file mode 100644
index 00000000000..c64c131f717
Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvas.png differ
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvasPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvasPage.xaml
new file mode 100644
index 00000000000..bfed8e51c68
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvasPage.xaml
@@ -0,0 +1,8 @@
+
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvasPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvasPage.xaml.cs
new file mode 100644
index 00000000000..8d413406714
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas/InfiniteCanvasPage.xaml.cs
@@ -0,0 +1,97 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Collections.Generic;
+using Windows.Storage;
+using Windows.UI.Popups;
+using Microsoft.Toolkit.Uwp.Helpers;
+using Microsoft.Toolkit.Uwp.UI.Controls;
+using Microsoft.Toolkit.Uwp.UI.Extensions;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
+{
+ ///
+ /// InfinteCanvas sample page.
+ ///
+ public sealed partial class InfiniteCanvasPage : Page, IXamlRenderListener
+ {
+ private InfiniteCanvas _infiniteCanvas;
+
+ public InfiniteCanvasPage()
+ {
+ InitializeComponent();
+ }
+
+ public void OnXamlRendered(FrameworkElement control)
+ {
+ _infiniteCanvas = control.FindChildByName("canvas") as InfiniteCanvas;
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ Shell.Current.RegisterNewCommand("Export & Save", async (sender, args) =>
+ {
+ if (_infiniteCanvas != null)
+ {
+ var savePicker = new Windows.Storage.Pickers.FileSavePicker
+ {
+ SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary
+ };
+ savePicker.FileTypeChoices.Add("application/json", new List { ".json" });
+ savePicker.SuggestedFileName = "Infinite Canvas Export";
+
+ StorageFile file = await savePicker.PickSaveFileAsync();
+ if (file != null)
+ {
+ var json = _infiniteCanvas.ExportAsJson();
+ CachedFileManager.DeferUpdates(file);
+ await FileIO.WriteTextAsync(file, json);
+ }
+ }
+ });
+
+ Shell.Current.RegisterNewCommand("Import and Load", async (sender, args) =>
+ {
+ if (_infiniteCanvas != null)
+ {
+ var picker = new Windows.Storage.Pickers.FileOpenPicker
+ {
+ ViewMode = Windows.Storage.Pickers.PickerViewMode.Thumbnail,
+ SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary
+ };
+ picker.FileTypeFilter.Add(".json");
+ var file = await picker.PickSingleFileAsync();
+
+ if (file != null)
+ {
+ try
+ {
+ var json = await FileIO.ReadTextAsync(file);
+ _infiniteCanvas.ImportFromJson(json);
+ }
+ catch
+ {
+ var dialog = new MessageDialog("Invalid File");
+ await dialog.ShowAsync();
+ }
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml
index efc0b5bc1d2..e832614adbe 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml
@@ -2,14 +2,14 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
+ Format="MarkDown" />
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs
index 6ab6431563b..7731a82d0ac 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs
@@ -153,6 +153,8 @@ private void AddCustomButton()
else
{
_toolbar.Formatter.Selected.Text = $"This was filled by {demoText} button ";
+
+ _toolbar.Formatter.Selected.CharacterFormat.Size = 40;
}
}
};
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json
index e553193ee33..2ad315b21f0 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json
@@ -293,6 +293,15 @@
"XamlCodeFile": "LayoutTransformControlXaml.bind",
"Icon": "/SamplePages/LayoutTransformControl/LayoutTransformControl.png",
"DocumentationUrl": "https://raw.githubusercontent.com/Microsoft/UWPCommunityToolkit/master/docs/controls/LayoutTransformControl.md"
+ },
+ {
+ "Name": "InfiniteCanvas",
+ "Type": "InfiniteCanvasPage",
+ "About": "InfiniteCanvas is a canvas that supports Infinite Scrolling, Ink, Text, Format Text, Zoom in/out, Redo, Undo, Export canvas data, Import canvas data.",
+ "CodeUrl": "https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas",
+ "XamlCodeFile": "InfiniteCanvas.bind",
+ "Icon": "/SamplePages/InfiniteCanvas/InfiniteCanvas.png",
+ "DocumentationUrl": "https://raw.githubusercontent.com/Microsoft/UWPCommunityToolkit/master/docs/controls/InfiniteCanvas.md"
}
]
},
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/IInfiniteCanvasCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/IInfiniteCanvasCommand.cs
new file mode 100644
index 00000000000..d02cf4cb0a9
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/IInfiniteCanvasCommand.cs
@@ -0,0 +1,21 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal interface IInfiniteCanvasCommand
+ {
+ void Execute();
+
+ void Undo();
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasClearAllCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasClearAllCommand.cs
new file mode 100644
index 00000000000..da70e81dfe4
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasClearAllCommand.cs
@@ -0,0 +1,46 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System.Collections.Generic;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasClearAllCommand : IInfiniteCanvasCommand
+ {
+ private readonly List _drawableList;
+ private IDrawable[] _storeList;
+
+ public InfiniteCanvasClearAllCommand(List drawableList)
+ {
+ _drawableList = drawableList;
+ }
+
+ public void Execute()
+ {
+ _storeList = new IDrawable[_drawableList.Count];
+ for (int i = 0; i < _drawableList.Count; i++)
+ {
+ _storeList[i] = _drawableList[i];
+ }
+
+ _drawableList.Clear();
+ }
+
+ public void Undo()
+ {
+ foreach (var drawable in _storeList)
+ {
+ _drawableList.Add(drawable);
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasCreateInkCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasCreateInkCommand.cs
new file mode 100644
index 00000000000..8b063de7a27
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasCreateInkCommand.cs
@@ -0,0 +1,39 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System.Collections.Generic;
+using Windows.UI.Input.Inking;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasCreateInkCommand : IInfiniteCanvasCommand
+ {
+ private readonly List _drawableList;
+ private readonly InkDrawable _drawable;
+
+ public InfiniteCanvasCreateInkCommand(List drawableList, IReadOnlyList strokes)
+ {
+ _drawable = new InkDrawable(strokes);
+ _drawableList = drawableList;
+ }
+
+ public void Execute()
+ {
+ _drawableList.Add(_drawable);
+ }
+
+ public void Undo()
+ {
+ _drawableList.Remove(_drawable);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasCreateTextBoxCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasCreateTextBoxCommand.cs
new file mode 100644
index 00000000000..7a247e7b13a
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasCreateTextBoxCommand.cs
@@ -0,0 +1,48 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System.Collections.Generic;
+using Windows.UI;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasCreateTextBoxCommand : IInfiniteCanvasCommand
+ {
+ private readonly List _drawableList;
+ private readonly TextDrawable _drawable;
+
+ public InfiniteCanvasCreateTextBoxCommand(List drawableList, double x, double y, double width, double height, int textFontSize, string text, Color color, bool isBold, bool isItalic)
+ {
+ _drawable = new TextDrawable(
+ x,
+ y,
+ width,
+ height,
+ textFontSize,
+ text,
+ color,
+ isBold,
+ isItalic);
+ _drawableList = drawableList;
+ }
+
+ public void Execute()
+ {
+ _drawableList.Add(_drawable);
+ }
+
+ public void Undo()
+ {
+ _drawableList.Remove(_drawable);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasEraseInkCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasEraseInkCommand.cs
new file mode 100644
index 00000000000..99612cf50db
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasEraseInkCommand.cs
@@ -0,0 +1,38 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System.Collections.Generic;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasEraseInkCommand : IInfiniteCanvasCommand
+ {
+ private readonly List _drawableList;
+ private readonly IDrawable _drawable;
+
+ public InfiniteCanvasEraseInkCommand(List drawableList, IDrawable drawable)
+ {
+ _drawable = drawable;
+ _drawableList = drawableList;
+ }
+
+ public void Execute()
+ {
+ _drawableList.Remove(_drawable);
+ }
+
+ public void Undo()
+ {
+ _drawableList.Add(_drawable);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasRemoveTextBoxCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasRemoveTextBoxCommand.cs
new file mode 100644
index 00000000000..cdf0791fac0
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasRemoveTextBoxCommand.cs
@@ -0,0 +1,38 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System.Collections.Generic;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasRemoveTextBoxCommand : IInfiniteCanvasCommand
+ {
+ private readonly List _drawableList;
+ private readonly TextDrawable _drawable;
+
+ public InfiniteCanvasRemoveTextBoxCommand(List drawableList, TextDrawable drawable)
+ {
+ _drawable = drawable;
+ _drawableList = drawableList;
+ }
+
+ public void Execute()
+ {
+ _drawableList.Remove(_drawable);
+ }
+
+ public void Undo()
+ {
+ _drawableList.Add(_drawable);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextColorCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextColorCommand.cs
new file mode 100644
index 00000000000..d2ba09106f7
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextColorCommand.cs
@@ -0,0 +1,40 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using Windows.UI;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasUpdateTextColorCommand : IInfiniteCanvasCommand
+ {
+ private readonly Color _oldColor;
+ private readonly Color _newColor;
+ private readonly TextDrawable _drawable;
+
+ public InfiniteCanvasUpdateTextColorCommand(TextDrawable drawable, Color oldText, Color newText)
+ {
+ _oldColor = oldText;
+ _newColor = newText;
+ _drawable = drawable;
+ }
+
+ public void Execute()
+ {
+ _drawable.TextColor = _newColor;
+ }
+
+ public void Undo()
+ {
+ _drawable.TextColor = _oldColor;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextCommand.cs
new file mode 100644
index 00000000000..fb20ec0e064
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextCommand.cs
@@ -0,0 +1,38 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasUpdateTextCommand : IInfiniteCanvasCommand
+ {
+ private readonly string _oldText;
+ private readonly string _newText;
+ private readonly TextDrawable _drawable;
+
+ public InfiniteCanvasUpdateTextCommand(TextDrawable drawable, string oldText, string newText)
+ {
+ _oldText = oldText;
+ _newText = newText;
+ _drawable = drawable;
+ }
+
+ public void Execute()
+ {
+ _drawable.Text = _newText;
+ }
+
+ public void Undo()
+ {
+ _drawable.Text = _oldText;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextFontSizeCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextFontSizeCommand.cs
new file mode 100644
index 00000000000..15d39e043c1
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextFontSizeCommand.cs
@@ -0,0 +1,38 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasUpdateTextFontSizeCommand : IInfiniteCanvasCommand
+ {
+ private readonly float _oldValue;
+ private readonly float _newValue;
+ private readonly TextDrawable _drawable;
+
+ public InfiniteCanvasUpdateTextFontSizeCommand(TextDrawable drawable, float oldValue, float newValue)
+ {
+ _oldValue = oldValue;
+ _newValue = newValue;
+ _drawable = drawable;
+ }
+
+ public void Execute()
+ {
+ _drawable.FontSize = _newValue;
+ }
+
+ public void Undo()
+ {
+ _drawable.FontSize = _oldValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextStyleCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextStyleCommand.cs
new file mode 100644
index 00000000000..5553ba4c57b
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextStyleCommand.cs
@@ -0,0 +1,38 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasUpdateTextStyleCommand : IInfiniteCanvasCommand
+ {
+ private readonly bool _oldValue;
+ private readonly bool _newValue;
+ private readonly TextDrawable _drawable;
+
+ public InfiniteCanvasUpdateTextStyleCommand(TextDrawable drawable, bool oldValue, bool newValue)
+ {
+ _oldValue = oldValue;
+ _newValue = newValue;
+ _drawable = drawable;
+ }
+
+ public void Execute()
+ {
+ _drawable.IsItalic = _newValue;
+ }
+
+ public void Undo()
+ {
+ _drawable.IsItalic = _oldValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextWeightCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextWeightCommand.cs
new file mode 100644
index 00000000000..d9e9dea7a34
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Commands/InfiniteCanvasUpdateTextWeightCommand.cs
@@ -0,0 +1,38 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasUpdateTextWeightCommand : IInfiniteCanvasCommand
+ {
+ private readonly bool _oldValue;
+ private readonly bool _newValue;
+ private readonly TextDrawable _drawable;
+
+ public InfiniteCanvasUpdateTextWeightCommand(TextDrawable drawable, bool oldValue, bool newValue)
+ {
+ _oldValue = oldValue;
+ _newValue = newValue;
+ _drawable = drawable;
+ }
+
+ public void Execute()
+ {
+ _drawable.IsBold = _newValue;
+ }
+
+ public void Undo()
+ {
+ _drawable.IsBold = _oldValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasTextBox.cs
new file mode 100644
index 00000000000..a554e207eac
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasTextBox.cs
@@ -0,0 +1,169 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Linq;
+using Windows.UI;
+using Windows.UI.Text;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InfiniteCanvasTextBox : Control
+ {
+ private TextBox _editZone;
+
+ public event EventHandler TextChanged;
+
+ public InfiniteCanvasTextBox()
+ {
+ DefaultStyleKey = typeof(InfiniteCanvasTextBox);
+ }
+
+ protected override void OnApplyTemplate()
+ {
+ _editZone = (TextBox)GetTemplateChild("EditZone");
+ _editZone.TextChanged -= EditZone_TextChanged;
+ _editZone.TextChanged += EditZone_TextChanged;
+ _editZone.FontSize = FontSize;
+ _editZone.SelectionHighlightColorWhenNotFocused.Color = Color.FromArgb(
+ 1,
+ _editZone.SelectionHighlightColor.Color.R,
+ _editZone.SelectionHighlightColor.Color.G,
+ _editZone.SelectionHighlightColor.Color.B);
+ _editZone.SelectionHighlightColorWhenNotFocused.Opacity = .1;
+
+ _editZone.SelectionHighlightColor.Color =
+ Color.FromArgb(
+ 1,
+ _editZone.SelectionHighlightColor.Color.R,
+ _editZone.SelectionHighlightColor.Color.G,
+ _editZone.SelectionHighlightColor.Color.B);
+
+ _editZone.SelectionHighlightColor.Opacity = .1;
+
+ base.OnApplyTemplate();
+ }
+
+ private void EditZone_TextChanged(object sender, RoutedEventArgs e)
+ {
+ TextChanged?.Invoke(this, _editZone.Text);
+ }
+
+ public double GetEditZoneWidth()
+ {
+ return _editZone.ActualWidth;
+ }
+
+ public double GetEditZoneHeight()
+ {
+ return _editZone.ActualHeight;
+ }
+
+ public void Clear()
+ {
+ if (_editZone == null)
+ {
+ return;
+ }
+
+ _editZone.TextChanged -= EditZone_TextChanged;
+ _editZone.Text = string.Empty;
+ _editZone.TextChanged += EditZone_TextChanged;
+ }
+
+ public void SetText(string text)
+ {
+ if (_editZone == null)
+ {
+ if (!ApplyTemplate())
+ {
+ return;
+ }
+ }
+
+ _editZone.Text = text;
+ _editZone.SelectionStart = text.Length;
+ }
+
+ public void UpdateFontSize(float textFontSize)
+ {
+ FontSize = textFontSize;
+
+ if (_editZone != null)
+ {
+ _editZone.FontSize = textFontSize;
+ }
+ }
+
+ public void UpdateFontStyle(bool isItalic)
+ {
+ if (_editZone != null)
+ {
+ _editZone.FontStyle = isItalic ? FontStyle.Italic : FontStyle.Normal;
+ }
+ }
+
+ public void UpdateFontWeight(bool isBold)
+ {
+ if (_editZone != null)
+ {
+ _editZone.FontWeight = isBold ? FontWeights.Bold : FontWeights.Normal;
+ }
+ }
+
+ public bool CannotGoRight()
+ {
+ return (_editZone.SelectionStart + _editZone.SelectionLength) == _editZone.Text.Length;
+ }
+
+ public bool CannotGoLeft()
+ {
+ return _editZone.SelectionStart == 0;
+ }
+
+ public bool CannotGoUp()
+ {
+ var lines = _editZone.Text.Split('\r');
+ if (lines.Count() == 1)
+ {
+ return true;
+ }
+
+ var firstLine = lines.First();
+ if (firstLine.Length >= _editZone.SelectionStart)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool CannotGoDown()
+ {
+ var lines = _editZone.Text.Split('\r');
+ if (lines.Count() == 1)
+ {
+ return true;
+ }
+
+ var lastLine = lines.ElementAt(lines.Length - 1);
+ if ((_editZone.Text.Length - lastLine.Length) <= (_editZone.SelectionStart + _editZone.SelectionLength))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Commands.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Commands.cs
new file mode 100644
index 00000000000..75afbf1fa49
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Commands.cs
@@ -0,0 +1,130 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Collections.Generic;
+using Windows.Foundation;
+using Windows.UI;
+using Windows.UI.Input.Inking;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ ///
+ /// The virtual Drawing surface renderer used to render the ink and text.
+ ///
+ internal partial class InfiniteCanvasVirtualDrawingSurface
+ {
+ private readonly Stack _undoCommands = new Stack();
+ private readonly Stack _redoCommands = new Stack();
+
+ public event EventHandler CommandExecuted;
+
+ public void Undo(Rect viewPort)
+ {
+ if (_undoCommands.Count != 0)
+ {
+ IInfiniteCanvasCommand command = _undoCommands.Pop();
+ command.Undo();
+ _redoCommands.Push(command);
+
+ ReDraw(viewPort);
+ }
+ }
+
+ public void Redo(Rect viewPort)
+ {
+ if (_redoCommands.Count != 0)
+ {
+ IInfiniteCanvasCommand command = _redoCommands.Pop();
+ command.Execute();
+ _undoCommands.Push(command);
+
+ ReDraw(viewPort);
+ }
+ }
+
+ public void ExecuteUpdateTextBoxText(string newText)
+ {
+ var drawable = GetSelectedTextDrawable();
+ var command = new InfiniteCanvasUpdateTextCommand(drawable, drawable.Text, newText);
+ ExecuteCommand(command);
+ }
+
+ public void ExecuteUpdateTextBoxColor(Color newColor)
+ {
+ var drawable = GetSelectedTextDrawable();
+ var command = new InfiniteCanvasUpdateTextColorCommand(drawable, drawable.TextColor, newColor);
+ ExecuteCommand(command);
+ }
+
+ public void ExecuteUpdateTextBoxStyle(bool newValue)
+ {
+ var drawable = GetSelectedTextDrawable();
+ var command = new InfiniteCanvasUpdateTextStyleCommand(drawable, drawable.IsItalic, newValue);
+ ExecuteCommand(command);
+ }
+
+ public void ExecuteUpdateTextBoxWeight(bool newValue)
+ {
+ var drawable = GetSelectedTextDrawable();
+ var command = new InfiniteCanvasUpdateTextWeightCommand(drawable, drawable.IsBold, newValue);
+ ExecuteCommand(command);
+ }
+
+ public void ExecuteUpdateTextBoxFontSize(float newValue)
+ {
+ var drawable = GetSelectedTextDrawable();
+ var command = new InfiniteCanvasUpdateTextFontSizeCommand(drawable, drawable.FontSize, newValue);
+ ExecuteCommand(command);
+ }
+
+ public void ExecuteCreateTextBox(double x, double y, double width, double height, int textFontSize, string text, Color color, bool isBold, bool isItalic)
+ {
+ var command = new InfiniteCanvasCreateTextBoxCommand(_drawableList, x, y, width, height, textFontSize, text, color, isBold, isItalic);
+ ExecuteCommand(command);
+ }
+
+ public void ExecuteRemoveTextBox()
+ {
+ var drawable = GetSelectedTextDrawable();
+ var command = new InfiniteCanvasRemoveTextBoxCommand(_drawableList, drawable);
+ ExecuteCommand(command);
+ }
+
+ public void ExecuteCreateInk(IReadOnlyList beginDry)
+ {
+ var command = new InfiniteCanvasCreateInkCommand(_drawableList, beginDry);
+ ExecuteCommand(command);
+ }
+
+ internal void ExecuteEraseInk(IDrawable drawable)
+ {
+ var command = new InfiniteCanvasEraseInkCommand(_drawableList, drawable);
+ ExecuteCommand(command);
+ }
+
+ internal void ExecuteClearAll()
+ {
+ var command = new InfiniteCanvasClearAllCommand(_drawableList);
+ ExecuteCommand(command);
+ }
+
+ private void ExecuteCommand(IInfiniteCanvasCommand command)
+ {
+ _undoCommands.Push(command);
+ _redoCommands.Clear();
+ command.Execute();
+
+ CommandExecuted?.Invoke(this, EventArgs.Empty);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Ink.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Ink.cs
new file mode 100644
index 00000000000..426a7aeaea4
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Ink.cs
@@ -0,0 +1,59 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Linq;
+using Windows.Foundation;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ ///
+ /// The virtual Drawing surface renderer used to render the ink and text.
+ ///
+ internal partial class InfiniteCanvasVirtualDrawingSurface
+ {
+ public void Erase(Point point, Rect viewPort, float zoomFactor)
+ {
+ const int tolerance = 5;
+ float toleranceWithZoom = tolerance;
+ if (zoomFactor > 1)
+ {
+ toleranceWithZoom /= zoomFactor;
+ }
+
+ for (var i = _visibleList.Count - 1; i >= 0; i--)
+ {
+ var drawable = _visibleList[i];
+ if (drawable is InkDrawable inkDrawable && drawable.Bounds.Contains(point))
+ {
+ foreach (var stroke in inkDrawable.Strokes)
+ {
+ if (stroke.BoundingRect.Contains(point))
+ {
+ foreach (var inkPoint in stroke.GetInkPoints())
+ {
+ if (Math.Abs(point.X - inkPoint.Position.X) < toleranceWithZoom && Math.Abs(point.Y - inkPoint.Position.Y) < toleranceWithZoom)
+ {
+ var toRemove = _visibleList.ElementAt(i);
+ ExecuteEraseInk(toRemove);
+ ReDraw(viewPort);
+
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Render.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Render.cs
new file mode 100644
index 00000000000..48af528cef1
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Render.cs
@@ -0,0 +1,107 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Graphics.Canvas;
+using Microsoft.Graphics.Canvas.UI.Composition;
+using Newtonsoft.Json;
+using Windows.Foundation;
+using Windows.Graphics;
+using Windows.UI;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ ///
+ /// The virtual Drawing surface renderer used to render the ink and text.
+ ///
+ internal partial class InfiniteCanvasVirtualDrawingSurface
+ {
+ private readonly List _visibleList = new List();
+ private readonly List _drawableList = new List();
+
+ public void ReDraw(Rect viewPort)
+ {
+ _visibleList.Clear();
+ double top = double.MaxValue,
+ bottom = double.MinValue,
+ left = double.MaxValue,
+ right = double.MinValue;
+
+ foreach (var drawable in _drawableList)
+ {
+ if (drawable.IsVisible(viewPort))
+ {
+ _visibleList.Add(drawable);
+
+ bottom = Math.Max(drawable.Bounds.Bottom, bottom);
+ right = Math.Max(drawable.Bounds.Right, right);
+ top = Math.Min(drawable.Bounds.Top, top);
+ left = Math.Min(drawable.Bounds.Left, left);
+ }
+ }
+
+ Rect toDraw;
+ if (_visibleList.Any())
+ {
+ toDraw = new Rect(Math.Max(left, 0), Math.Max(top, 0), Math.Max(right - left, 0), Math.Max(bottom - top, 0));
+
+ toDraw.Union(viewPort);
+ }
+ else
+ {
+ toDraw = viewPort;
+ }
+
+ using (CanvasDrawingSession drawingSession = CanvasComposition.CreateDrawingSession(_drawingSurface, toDraw))
+ {
+ drawingSession.Clear(Colors.White);
+ foreach (var drawable in _visibleList)
+ {
+ drawable.Draw(drawingSession, toDraw);
+ }
+ }
+ }
+
+ public void ClearAll(Rect viewPort)
+ {
+ _visibleList.Clear();
+ ExecuteClearAll();
+ _drawingSurface.Trim(new RectInt32[0]);
+ }
+
+ public string GetSerializedList()
+ {
+ return JsonConvert.SerializeObject(_drawableList, Formatting.Indented, new JsonSerializerSettings
+ {
+ TypeNameHandling = TypeNameHandling.Auto
+ });
+ }
+
+ public void RenderFromJsonAndDraw(Rect viewPort, string json)
+ {
+ _visibleList.Clear();
+ _drawableList.Clear();
+ _undoCommands.Clear();
+ _redoCommands.Clear();
+
+ var newList = JsonConvert.DeserializeObject>(json, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
+ foreach (var drawable in newList)
+ {
+ _drawableList.Add(drawable);
+ }
+
+ ReDraw(viewPort);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.TextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.TextBox.cs
new file mode 100644
index 00000000000..2e0cc8c2cb9
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.TextBox.cs
@@ -0,0 +1,62 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System.Linq;
+using Windows.Foundation;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ ///
+ /// The virtual Drawing surface renderer used to render the ink and text.
+ ///
+ internal partial class InfiniteCanvasVirtualDrawingSurface
+ {
+ private const int DrawableNullIndex = -1;
+
+ private int _selectedTextDrawableIndex = DrawableNullIndex;
+
+ internal void UpdateSelectedTextDrawableIfSelected(Point point, Rect viewPort)
+ {
+ for (var i = _drawableList.Count - 1; i >= 0; i--)
+ {
+ var drawable = _drawableList[i];
+ if (drawable is TextDrawable && drawable.IsActive && drawable.Bounds.Contains(point))
+ {
+ _selectedTextDrawableIndex = i;
+ return;
+ }
+ }
+
+ _selectedTextDrawableIndex = DrawableNullIndex;
+ }
+
+ internal TextDrawable GetSelectedTextDrawable()
+ {
+ if (_selectedTextDrawableIndex == DrawableNullIndex)
+ {
+ return null;
+ }
+
+ return (TextDrawable)_drawableList.ElementAt(_selectedTextDrawableIndex);
+ }
+
+ internal void ResetSelectedTextDrawable()
+ {
+ _selectedTextDrawableIndex = DrawableNullIndex;
+ }
+
+ internal void UpdateSelectedTextDrawable()
+ {
+ _selectedTextDrawableIndex = _drawableList.Count - 1;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.cs
new file mode 100644
index 00000000000..2ebe30987a6
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.cs
@@ -0,0 +1,80 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System.Numerics;
+using Microsoft.Graphics.Canvas;
+using Microsoft.Graphics.Canvas.UI.Composition;
+using Windows.Graphics;
+using Windows.Graphics.DirectX;
+using Windows.UI.Composition;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Hosting;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ ///
+ /// The virtual Drawing surface renderer used to render the ink and text.
+ ///
+ internal partial class InfiniteCanvasVirtualDrawingSurface : Panel
+ {
+ private Compositor _compositor;
+ private CanvasDevice _win2DDevice;
+ private CompositionGraphicsDevice _comositionGraphicsDevice;
+ private SpriteVisual _myDrawingVisual;
+ private CompositionVirtualDrawingSurface _drawingSurface;
+ private CompositionSurfaceBrush _surfaceBrush;
+
+ public InfiniteCanvasVirtualDrawingSurface()
+ {
+ InitializeComposition();
+ SizeChanged += TheSurface_SizeChanged;
+ }
+
+ private void TheSurface_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ _myDrawingVisual.Size = new Vector2((float)ActualWidth, (float)ActualHeight);
+ }
+
+ public void InitializeComposition()
+ {
+ _compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
+ _win2DDevice = CanvasDevice.GetSharedDevice();
+ _comositionGraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(_compositor, _win2DDevice);
+ _myDrawingVisual = _compositor.CreateSpriteVisual();
+ ElementCompositionPreview.SetElementChildVisual(this, _myDrawingVisual);
+ }
+
+ public void ConfigureSpriteVisual(double width, double height)
+ {
+ var size = new SizeInt32
+ {
+ Height = (int)width,
+ Width = (int)height
+ };
+
+ _drawingSurface = _comositionGraphicsDevice.CreateVirtualDrawingSurface(
+ size,
+ DirectXPixelFormat.B8G8R8A8UIntNormalized,
+ DirectXAlphaMode.Premultiplied);
+
+ _surfaceBrush = _compositor.CreateSurfaceBrush(_drawingSurface);
+ _surfaceBrush.Stretch = CompositionStretch.None;
+ _surfaceBrush.HorizontalAlignmentRatio = 0;
+ _surfaceBrush.VerticalAlignmentRatio = 0;
+ _surfaceBrush.TransformMatrix = Matrix3x2.CreateTranslation(0, 0);
+
+ _myDrawingVisual.Brush = _surfaceBrush;
+ _surfaceBrush.Offset = new Vector2(0, 0);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Drawables/IDrawable.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Drawables/IDrawable.cs
new file mode 100644
index 00000000000..843a23e90ec
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Drawables/IDrawable.cs
@@ -0,0 +1,28 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using Microsoft.Graphics.Canvas;
+using Windows.Foundation;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal interface IDrawable
+ {
+ void Draw(CanvasDrawingSession drawingSession, Rect sessionBounds);
+
+ bool IsVisible(Rect viewPort);
+
+ bool IsActive { get; set; }
+
+ Rect Bounds { get; set; }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Drawables/InkDrawable.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Drawables/InkDrawable.cs
new file mode 100644
index 00000000000..4f47935ceb4
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Drawables/InkDrawable.cs
@@ -0,0 +1,138 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using Microsoft.Graphics.Canvas;
+using Newtonsoft.Json;
+using Windows.Foundation;
+using Windows.UI.Input.Inking;
+using Windows.UI.Xaml;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class InkDrawable : IDrawable
+ {
+ [JsonIgnore]
+ public IReadOnlyList Strokes { get; set; }
+
+ public List SerializableStrokeList { get; set; }
+
+ public Rect Bounds { get; set; }
+
+ public bool IsActive { get; set; }
+
+ internal static readonly InkStrokeBuilder StrokeBuilder = new InkStrokeBuilder();
+
+ public InkDrawable(IReadOnlyList strokes)
+ {
+ if (strokes == null || !strokes.Any())
+ {
+ return;
+ }
+
+ Strokes = strokes;
+
+ var first = strokes.First();
+ double top = first.BoundingRect.Top, bottom = first.BoundingRect.Bottom, left = first.BoundingRect.Left, right = first.BoundingRect.Right;
+
+ for (var index = 1; index < strokes.Count; index++)
+ {
+ var stroke = strokes[index];
+ bottom = Math.Max(stroke.BoundingRect.Bottom, bottom);
+ right = Math.Max(stroke.BoundingRect.Right, right);
+ top = Math.Min(stroke.BoundingRect.Top, top);
+ left = Math.Min(stroke.BoundingRect.Left, left);
+ }
+
+ Bounds = new Rect(left, top, right - left, bottom - top);
+ }
+
+ public bool IsVisible(Rect viewPort)
+ {
+ IsActive = RectHelper.Intersect(viewPort, Bounds) != Rect.Empty;
+ return IsActive;
+ }
+
+ public void Draw(CanvasDrawingSession drawingSession, Rect sessionBounds)
+ {
+ var finalStrokeList = new List(Strokes.Count);
+
+ foreach (var stroke in Strokes)
+ {
+ var points = stroke.GetInkPoints();
+ var finalPointList = new List(points.Count);
+ foreach (var point in points)
+ {
+ finalPointList.Add(MapPointToToSessionBounds(point, sessionBounds));
+ }
+
+ StrokeBuilder.SetDefaultDrawingAttributes(stroke.DrawingAttributes);
+ var newStroke = StrokeBuilder.CreateStrokeFromInkPoints(finalPointList, stroke.PointTransform);
+ finalStrokeList.Add(newStroke);
+ }
+
+ drawingSession.DrawInk(finalStrokeList);
+ }
+
+ private static InkPoint MapPointToToSessionBounds(InkPoint point, Rect sessionBounds)
+ {
+ return new InkPoint(new Point(point.Position.X - sessionBounds.X, point.Position.Y - sessionBounds.Y), point.Pressure, point.TiltX, point.TiltY, point.Timestamp);
+ }
+
+ [OnSerializing]
+ internal void OnSerializingMethod(StreamingContext context)
+ {
+ SerializableStrokeList = new List(Strokes.Count);
+ foreach (var stroke in Strokes)
+ {
+ var serializableStroke = new SerializableStroke();
+ var points = stroke.GetInkPoints();
+ var finalPointList = new List(points.Count);
+ foreach (var point in points)
+ {
+ finalPointList.Add(point);
+ }
+
+ serializableStroke.FinalPointList = finalPointList;
+
+ serializableStroke.DrawingAttributes = stroke.DrawingAttributes;
+ serializableStroke.PointTransform = stroke.PointTransform;
+ SerializableStrokeList.Add(serializableStroke);
+ }
+ }
+
+ [OnDeserialized]
+ internal void OnDeserializedMethod(StreamingContext context)
+ {
+ var finalStrokeList = new List(SerializableStrokeList.Count);
+
+ foreach (var stroke in SerializableStrokeList)
+ {
+ StrokeBuilder.SetDefaultDrawingAttributes(stroke.DrawingAttributes);
+ var newStroke = StrokeBuilder.CreateStrokeFromInkPoints(stroke.FinalPointList, stroke.PointTransform);
+ finalStrokeList.Add(newStroke);
+ }
+
+ Strokes = finalStrokeList;
+ SerializableStrokeList = null;
+ }
+
+ [OnSerialized]
+ internal void OnSerializedMethod(StreamingContext context)
+ {
+ SerializableStrokeList = null;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Drawables/TextDrawable.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Drawables/TextDrawable.cs
new file mode 100644
index 00000000000..9c2f840edfa
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Drawables/TextDrawable.cs
@@ -0,0 +1,88 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using Microsoft.Graphics.Canvas;
+using Microsoft.Graphics.Canvas.Text;
+using Windows.Foundation;
+using Windows.UI;
+using Windows.UI.Text;
+using Windows.UI.Xaml;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class TextDrawable : IDrawable
+ {
+ public string Text { get; set; }
+
+ public Rect Bounds { get; set; }
+
+ public bool IsActive { get; set; }
+
+ public float FontSize { get; set; }
+
+ public Color TextColor { get; set; }
+
+ public bool IsBold { get; set; }
+
+ public bool IsItalic { get; set; }
+
+ public TextDrawable(double left, double top, double width, double height, float fontSize, string text, Color textColor, bool isBold, bool isItalic)
+ {
+ Bounds = new Rect(left, top, width, height);
+ Text = text;
+ FontSize = fontSize;
+ TextColor = textColor;
+ IsBold = isBold;
+ IsItalic = isItalic;
+ }
+
+ public bool IsVisible(Rect viewPort)
+ {
+ IsActive = RectHelper.Intersect(viewPort, Bounds) != Rect.Empty;
+ return IsActive;
+ }
+
+ public void Draw(CanvasDrawingSession drawingSession, Rect sessionBounds)
+ {
+ const int verticalMargin = 3;
+ CanvasTextFormat format = new CanvasTextFormat
+ {
+ FontSize = FontSize,
+ WordWrapping = CanvasWordWrapping.NoWrap,
+ FontWeight = IsBold ? FontWeights.Bold : FontWeights.Normal,
+ FontStyle = IsItalic ? FontStyle.Italic : FontStyle.Normal
+ };
+
+ CanvasTextLayout textLayout = new CanvasTextLayout(drawingSession, Text, format, 0.0f, 0.0f);
+
+ drawingSession.DrawTextLayout(textLayout, (float)(Bounds.X - sessionBounds.X + HorizontalMarginBasedOnFont), (float)(Bounds.Y - sessionBounds.Y + verticalMargin), TextColor);
+ }
+
+ public void UpdateBounds(double actualWidth, double actualHeight)
+ {
+ Bounds = new Rect(Bounds.X, Bounds.Y, actualWidth, actualHeight);
+ }
+
+ public float HorizontalMarginBasedOnFont
+ {
+ get
+ {
+ if (FontSize > 100)
+ {
+ return 5;
+ }
+
+ return ((100 - FontSize) / 10) + 5;
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.Events.cs
new file mode 100644
index 00000000000..f7112f9378c
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.Events.cs
@@ -0,0 +1,158 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using System.Threading.Tasks;
+using Windows.System;
+using Windows.UI.Core;
+using Windows.UI.Input.Inking;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ ///
+ /// InfiniteCanvas is a canvas that supports Ink, Text, Format Text, Zoom in/out, Redo, Undo, Export canvas data, Import canvas data.
+ ///
+ public partial class InfiniteCanvas
+ {
+ private static void CanvasWidthHeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var infiniteCanvas = (InfiniteCanvas)d;
+ infiniteCanvas.SetCanvasWidthHeight();
+ }
+
+ private static void IsToolbarVisiblePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var infiniteCanvas = (InfiniteCanvas)d;
+ if (infiniteCanvas._canvasToolbarContainer != null)
+ {
+ infiniteCanvas._canvasToolbarContainer.Visibility = infiniteCanvas.IsToolbarVisible ? Visibility.Visible : Visibility.Collapsed;
+ }
+ }
+
+ private static void MinMaxZoomChangedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var infiniteCanvas = (InfiniteCanvas)d;
+ infiniteCanvas.SetZoomFactor();
+ }
+
+ ///
+ protected override void OnKeyDown(KeyRoutedEventArgs e)
+ {
+ var isCtrlDown = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
+
+ if (!isCtrlDown)
+ {
+ return;
+ }
+
+ if (e.Key == VirtualKey.Z)
+ {
+ Undo();
+ }
+
+ if (e.Key == VirtualKey.Y)
+ {
+ Redo();
+ }
+
+ base.OnKeyDown(e);
+ }
+
+ private void InfiniteCanvas_Unloaded(object sender, RoutedEventArgs e)
+ {
+ Application.Current.LeavingBackground -= Current_LeavingBackground;
+ }
+
+ private void RedoButton_Click(object sender, RoutedEventArgs e)
+ {
+ Redo();
+ }
+
+ private void UndoButton_Click(object sender, RoutedEventArgs e)
+ {
+ Undo();
+ }
+
+ private void EnableTouchInkingButton_Unchecked(object sender, RoutedEventArgs e)
+ {
+ _inkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Pen;
+ }
+
+ private void EnableTouchInkingButton_Checked(object sender, RoutedEventArgs e)
+ {
+ _inkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Touch;
+ }
+
+ private void EnableTextButton_Unchecked(object sender, RoutedEventArgs e)
+ {
+ _canvasTextBox.Visibility = Visibility.Collapsed;
+ _inkCanvas.Visibility = Visibility.Visible;
+ _canvasTextBoxTools.Visibility = Visibility.Collapsed;
+ }
+
+ private void EnableTextButton_Checked(object sender, RoutedEventArgs e)
+ {
+ _inkCanvas.Visibility = Visibility.Collapsed;
+ _canvasTextBoxTools.Visibility = Visibility.Visible;
+ }
+
+ private void EraseAllButton_Click(object sender, RoutedEventArgs e)
+ {
+ _canvasTextBox.Visibility = Visibility.Collapsed;
+ ClearTextBoxValue();
+ _drawingSurfaceRenderer.ClearAll(ViewPort);
+ }
+
+ private async void Current_LeavingBackground(object sender, Windows.ApplicationModel.LeavingBackgroundEventArgs e)
+ {
+ // work around to virtual drawing surface bug.
+ await Task.Delay(1000);
+ _drawingSurfaceRenderer.ReDraw(ViewPort);
+ }
+
+ private void InkScrollViewer_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ ReDrawCanvas();
+ }
+
+ private void OnStrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args)
+ {
+ _drawingSurfaceRenderer.ExecuteCreateInk(_inkSync.BeginDry());
+ _inkSync.EndDry();
+ ReDrawCanvas();
+ }
+
+ private void UnprocessedInput_PointerMoved(InkUnprocessedInput sender, PointerEventArgs args)
+ {
+ if (_inkCanvasToolBar.ActiveTool == _inkCanvasToolBar.GetToolButton(InkToolbarTool.Eraser))
+ {
+ _drawingSurfaceRenderer.Erase(args.CurrentPoint.Position, ViewPort, _infiniteCanvasScrollViewer.ZoomFactor);
+ }
+ }
+
+ private void InkScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
+ {
+ if (!e.IsIntermediate)
+ {
+ ReDrawCanvas();
+ }
+ }
+
+ private void DrawingSurfaceRenderer_CommandExecuted(object sender, EventArgs e)
+ {
+ ReRenderCompleted?.Invoke(this, EventArgs.Empty);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.TextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.TextBox.cs
new file mode 100644
index 00000000000..66f4cf4b650
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.TextBox.cs
@@ -0,0 +1,238 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System.Linq;
+using System.Text.RegularExpressions;
+using Windows.Foundation;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ ///
+ /// InfiniteCanvas is a canvas that supports Ink, Text, Format Text, Zoom in/out, Redo, Undo, Export canvas data, Import canvas data.
+ ///
+ public partial class InfiniteCanvas
+ {
+ private const int DefaultFontValue = 22;
+ private readonly string[] _allowedCommands =
+ {
+ "Shift",
+ "Escape",
+ "Delete",
+ "Back",
+ "Right",
+ "Up",
+ "Left",
+ "Down"
+ };
+
+ private Point _lastInputPoint;
+
+ private TextDrawable SelectedTextDrawable => _drawingSurfaceRenderer.GetSelectedTextDrawable();
+
+ private int _lastValidTextFontSizeValue = DefaultFontValue;
+
+ private int TextFontSize
+ {
+ get
+ {
+ if (!string.IsNullOrWhiteSpace(_canvasTextBoxFontSizeTextBox.Text) &&
+ Regex.IsMatch(_canvasTextBoxFontSizeTextBox.Text, "^[0-9]*$"))
+ {
+ var fontSize = int.Parse(_canvasTextBoxFontSizeTextBox.Text);
+ _lastValidTextFontSizeValue = fontSize;
+ }
+
+ return _lastValidTextFontSizeValue;
+ }
+ }
+
+ private void InkScrollViewer_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
+ {
+ // fixing scroll viewer issue with text box when you hit UP/DOWN/Right/LEFT
+ if (_canvasTextBox.Visibility != Visibility.Visible)
+ {
+ return;
+ }
+
+ if (((e.Key == VirtualKey.PageUp || e.Key == VirtualKey.Up) && _canvasTextBox.CannotGoUp()) || ((e.Key == VirtualKey.PageDown || e.Key == VirtualKey.Down) && _canvasTextBox.CannotGoDown()))
+ {
+ e.Handled = true;
+ return;
+ }
+
+ if (((e.Key == VirtualKey.Right || e.Key == VirtualKey.End) && _canvasTextBox.CannotGoRight())
+ || ((e.Key == VirtualKey.Left || e.Key == VirtualKey.Home) && _canvasTextBox.CannotGoLeft()))
+ {
+ e.Handled = true;
+ }
+ }
+
+ private void CanvasTextBoxBoldButton_Clicked(object sender, RoutedEventArgs e)
+ {
+ if (SelectedTextDrawable != null)
+ {
+ _drawingSurfaceRenderer.ExecuteUpdateTextBoxWeight(_canvasTextBoxBoldButton.IsChecked ?? false);
+ _canvasTextBox.UpdateFontWeight(SelectedTextDrawable.IsBold);
+ ReDrawCanvas();
+ }
+ }
+
+ private void CanvasTextBoxItlaicButton_Clicked(object sender, RoutedEventArgs e)
+ {
+ if (SelectedTextDrawable != null)
+ {
+ _drawingSurfaceRenderer.ExecuteUpdateTextBoxStyle(_canvasTextBoxItlaicButton.IsChecked ?? false);
+ _canvasTextBox.UpdateFontStyle(SelectedTextDrawable.IsItalic);
+ ReDrawCanvas();
+ }
+ }
+
+ private void CanvasTextBoxFontSizeTextBox_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ _canvasTextBox.UpdateFontSize(TextFontSize);
+ if (SelectedTextDrawable != null)
+ {
+ _drawingSurfaceRenderer.ExecuteUpdateTextBoxFontSize(TextFontSize);
+ ReDrawCanvas();
+ }
+ }
+
+ private void CanvasTextBox_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ SelectedTextDrawable?.UpdateBounds(_canvasTextBox.ActualWidth, _canvasTextBox.ActualHeight);
+ }
+
+ private void CanvasTextBoxColorPicker_ColorChanged(ColorPicker sender, ColorChangedEventArgs args)
+ {
+ if (SelectedTextDrawable != null)
+ {
+ _drawingSurfaceRenderer.ExecuteUpdateTextBoxColor(_canvasTextBoxColorPicker.Color);
+ ReDrawCanvas();
+ }
+
+ _fontColorIcon.Foreground = new SolidColorBrush(_canvasTextBoxColorPicker.Color);
+ }
+
+ private void CanvasTextBox_TextChanged(object sender, string text)
+ {
+ if (string.IsNullOrEmpty(text) && SelectedTextDrawable == null)
+ {
+ return;
+ }
+
+ if (SelectedTextDrawable != null)
+ {
+ if (string.IsNullOrEmpty(text))
+ {
+ _drawingSurfaceRenderer.ExecuteRemoveTextBox();
+ _drawingSurfaceRenderer.ResetSelectedTextDrawable();
+ }
+ else
+ {
+ if (SelectedTextDrawable.Text != text)
+ {
+ _drawingSurfaceRenderer.ExecuteUpdateTextBoxText(text);
+ }
+ }
+
+ ReDrawCanvas();
+ return;
+ }
+
+ _drawingSurfaceRenderer.ExecuteCreateTextBox(
+ _lastInputPoint.X,
+ _lastInputPoint.Y,
+ _canvasTextBox.GetEditZoneWidth(),
+ _canvasTextBox.GetEditZoneHeight(),
+ TextFontSize,
+ text,
+ _canvasTextBoxColorPicker.Color,
+ _canvasTextBoxBoldButton.IsChecked ?? false,
+ _canvasTextBoxItlaicButton.IsChecked ?? false);
+
+ ReDrawCanvas();
+ _drawingSurfaceRenderer.UpdateSelectedTextDrawable();
+ }
+
+ private void InkScrollViewer_PointerPressed(object sender, PointerRoutedEventArgs e)
+ {
+ if (_enableTextButton.IsChecked ?? false)
+ {
+ var point = e.GetCurrentPoint(_infiniteCanvasScrollViewer);
+ _lastInputPoint = new Point((point.Position.X + _infiniteCanvasScrollViewer.HorizontalOffset) / _infiniteCanvasScrollViewer.ZoomFactor, (point.Position.Y + _infiniteCanvasScrollViewer.VerticalOffset) / _infiniteCanvasScrollViewer.ZoomFactor);
+
+ _drawingSurfaceRenderer.UpdateSelectedTextDrawableIfSelected(_lastInputPoint, ViewPort);
+
+ if (SelectedTextDrawable != null)
+ {
+ _canvasTextBox.Visibility = Visibility.Visible;
+ _canvasTextBox.SetText(SelectedTextDrawable.Text);
+
+ Canvas.SetLeft(_canvasTextBox, SelectedTextDrawable.Bounds.X);
+ Canvas.SetTop(_canvasTextBox, SelectedTextDrawable.Bounds.Y);
+ _canvasTextBox.UpdateFontSize(SelectedTextDrawable.FontSize);
+ _canvasTextBox.UpdateFontStyle(SelectedTextDrawable.IsItalic);
+ _canvasTextBox.UpdateFontWeight(SelectedTextDrawable.IsBold);
+
+ // Updating toolbar
+ _canvasTextBoxColorPicker.Color = SelectedTextDrawable.TextColor;
+ _canvasTextBoxFontSizeTextBox.Text = SelectedTextDrawable.FontSize.ToString();
+ _canvasTextBoxBoldButton.IsChecked = SelectedTextDrawable.IsBold;
+ _canvasTextBoxItlaicButton.IsChecked = SelectedTextDrawable.IsBold;
+
+ return;
+ }
+
+ _canvasTextBox.UpdateFontSize(TextFontSize);
+ _canvasTextBox.UpdateFontStyle(_canvasTextBoxItlaicButton.IsChecked ?? false);
+ _canvasTextBox.UpdateFontWeight(_canvasTextBoxBoldButton.IsChecked ?? false);
+
+ _inkCanvas.Visibility = Visibility.Collapsed;
+ ClearTextBoxValue();
+ _canvasTextBox.Visibility = Visibility.Visible;
+ Canvas.SetLeft(_canvasTextBox, _lastInputPoint.X);
+ Canvas.SetTop(_canvasTextBox, _lastInputPoint.Y);
+ }
+ }
+
+ private void ClearTextBoxValue()
+ {
+ _drawingSurfaceRenderer.ResetSelectedTextDrawable();
+ _canvasTextBox.Clear();
+ }
+
+ private void CanvasTextBoxFontSizeTextBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
+ {
+ if (_allowedCommands.Contains(e.Key.ToString()))
+ {
+ e.Handled = false;
+ return;
+ }
+
+ for (int i = 0; i < 10; i++)
+ {
+ if (e.Key.ToString() == string.Format("Number{0}", i))
+ {
+ e.Handled = false;
+ return;
+ }
+ }
+
+ e.Handled = true;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs
new file mode 100644
index 00000000000..7f612570a36
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs
@@ -0,0 +1,330 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System;
+using Windows.Foundation;
+using Windows.UI.Core;
+using Windows.UI.Input.Inking;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ ///
+ /// InfiniteCanvas is a canvas that supports Ink, Text, Format Text, Zoom in/out, Redo, Undo, Export canvas data, Import canvas data.
+ ///
+ public partial class InfiniteCanvas : Control
+ {
+ private const double DefaultMaxZoomFactor = 4.0;
+ private const double DefaultMinZoomFactor = .25;
+ private const double LargeCanvasWidthHeight = 1 << 21;
+
+ private InkCanvas _inkCanvas;
+ private InfiniteCanvasVirtualDrawingSurface _drawingSurfaceRenderer;
+ private InkSynchronizer _inkSync;
+ private InkToolbarCustomToolButton _enableTextButton;
+ private InkToolbarCustomToggleButton _enableTouchInkingButton;
+ private InfiniteCanvasTextBox _canvasTextBox;
+ private StackPanel _canvasTextBoxTools;
+ private ColorPicker _canvasTextBoxColorPicker;
+
+ private TextBox _canvasTextBoxFontSizeTextBox;
+ private ToggleButton _canvasTextBoxItlaicButton;
+ private ToggleButton _canvasTextBoxBoldButton;
+ private Button _undoButton;
+ private Button _redoButton;
+ private Button _eraseAllButton;
+
+ private InkToolbar _inkCanvasToolBar;
+ private Canvas _mainContainer;
+ private ScrollViewer _infiniteCanvasScrollViewer;
+ private StackPanel _canvasToolbarContainer;
+ private FontIcon _fontColorIcon;
+
+ ///
+ /// Gets or sets the width of the canvas, default value is the max value 2097152
+ ///
+ public double CanvasWidth
+ {
+ get { return (double)GetValue(CanvasWidthProperty); }
+ set { SetValue(CanvasWidthProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty CanvasWidthProperty =
+ DependencyProperty.Register(
+ nameof(CanvasWidth),
+ typeof(double),
+ typeof(InfiniteCanvas),
+ new PropertyMetadata(LargeCanvasWidthHeight, CanvasWidthHeightPropertyChanged));
+
+ ///
+ /// Gets or sets the height of the canvas, default value is the max value 2097152
+ ///
+ public double CanvasHeight
+ {
+ get { return (double)GetValue(CanvasHeightProperty); }
+ set { SetValue(CanvasHeightProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty CanvasHeightProperty =
+ DependencyProperty.Register(
+ nameof(CanvasHeight),
+ typeof(double),
+ typeof(InfiniteCanvas),
+ new PropertyMetadata(LargeCanvasWidthHeight, CanvasWidthHeightPropertyChanged));
+
+ ///
+ /// Gets or sets a value indicating whether the toolbar is visible or not.
+ ///
+ public bool IsToolbarVisible
+ {
+ get { return (bool)GetValue(IsToolbarVisibleProperty); }
+ set { SetValue(IsToolbarVisibleProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty IsToolbarVisibleProperty =
+ DependencyProperty.Register(
+ nameof(IsToolbarVisible),
+ typeof(bool),
+ typeof(InfiniteCanvas),
+ new PropertyMetadata(true, IsToolbarVisiblePropertyChanged));
+
+ ///
+ /// Gets or sets the MaxZoomFactor for the canvas, range between 1 to 10 and the default value is 4
+ ///
+ public double MaxZoomFactor
+ {
+ get { return (double)GetValue(MaxZoomFactorProperty); }
+ set { SetValue(MaxZoomFactorProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty MaxZoomFactorProperty =
+ DependencyProperty.Register(
+ nameof(MaxZoomFactor),
+ typeof(double),
+ typeof(InfiniteCanvas),
+ new PropertyMetadata(DefaultMaxZoomFactor, MinMaxZoomChangedPropertyChanged));
+
+ ///
+ /// Gets or sets the MinZoomFactor for the canvas, range between .1 to 1 the default value is .25
+ ///
+ public double MinZoomFactor
+ {
+ get { return (double)GetValue(MinZoomFactorProperty); }
+ set { SetValue(MinZoomFactorProperty, value); }
+ }
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty MinZoomFactorProperty =
+ DependencyProperty.Register(
+ nameof(MinZoomFactor),
+ typeof(double),
+ typeof(InfiniteCanvas),
+ new PropertyMetadata(DefaultMinZoomFactor, MinMaxZoomChangedPropertyChanged));
+
+ private Rect ViewPort => new Rect(_infiniteCanvasScrollViewer.HorizontalOffset / _infiniteCanvasScrollViewer.ZoomFactor, _infiniteCanvasScrollViewer.VerticalOffset / _infiniteCanvasScrollViewer.ZoomFactor, _infiniteCanvasScrollViewer.ViewportWidth / _infiniteCanvasScrollViewer.ZoomFactor, _infiniteCanvasScrollViewer.ViewportHeight / _infiniteCanvasScrollViewer.ZoomFactor);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public InfiniteCanvas()
+ {
+ DefaultStyleKey = typeof(InfiniteCanvas);
+ }
+
+ ///
+ protected override void OnApplyTemplate()
+ {
+ _canvasTextBoxTools = (StackPanel)GetTemplateChild("CanvasTextBoxTools");
+ _canvasTextBoxColorPicker = (ColorPicker)GetTemplateChild("CanvasTextBoxColorPicker");
+ _canvasTextBoxFontSizeTextBox = (TextBox)GetTemplateChild("CanvasTextBoxFontSizeTextBox");
+ _canvasTextBoxItlaicButton = (ToggleButton)GetTemplateChild("CanvasTextBoxItlaicButton");
+ _canvasTextBoxBoldButton = (ToggleButton)GetTemplateChild("CanvasTextBoxBoldButton");
+ _drawingSurfaceRenderer = (InfiniteCanvasVirtualDrawingSurface)GetTemplateChild("DrawingSurfaceRenderer");
+ _mainContainer = (Canvas)GetTemplateChild("MainContainer");
+ _infiniteCanvasScrollViewer = (ScrollViewer)GetTemplateChild("InfiniteCanvasScrollViewer");
+ _eraseAllButton = (Button)GetTemplateChild("EraseAllButton");
+ _canvasTextBox = (InfiniteCanvasTextBox)GetTemplateChild("CanvasTextBox");
+ _enableTextButton = (InkToolbarCustomToolButton)GetTemplateChild("EnableTextButton");
+ _enableTouchInkingButton = (InkToolbarCustomToggleButton)GetTemplateChild("EnableTouchInkingButton");
+ _inkCanvasToolBar = (InkToolbar)GetTemplateChild("InkCanvasToolBar");
+ _canvasToolbarContainer = (StackPanel)GetTemplateChild("CanvasToolbarContainer");
+
+ _inkCanvas = (InkCanvas)GetTemplateChild("DrawingInkCanvas");
+ _undoButton = (Button)GetTemplateChild("UndoButton");
+ _redoButton = (Button)GetTemplateChild("RedoButton");
+ _fontColorIcon = (FontIcon)GetTemplateChild("FontColorIcon");
+
+ UnRegisterEvents();
+ RegisterEvents();
+
+ ConfigureControls();
+ base.OnApplyTemplate();
+ }
+
+ private void UnRegisterEvents()
+ {
+ _canvasTextBoxFontSizeTextBox.TextChanged -= CanvasTextBoxFontSizeTextBox_TextChanged;
+ _canvasTextBoxItlaicButton.Click -= CanvasTextBoxItlaicButton_Clicked;
+ _canvasTextBoxBoldButton.Click -= CanvasTextBoxBoldButton_Clicked;
+ _canvasTextBoxColorPicker.ColorChanged -= CanvasTextBoxColorPicker_ColorChanged;
+ _enableTouchInkingButton.Checked -= EnableTouchInkingButton_Checked;
+ _enableTouchInkingButton.Unchecked -= EnableTouchInkingButton_Unchecked;
+ _enableTextButton.Checked -= EnableTextButton_Checked;
+ _enableTextButton.Unchecked -= EnableTextButton_Unchecked;
+ _eraseAllButton.Click -= EraseAllButton_Click;
+ _infiniteCanvasScrollViewer.PointerPressed -= InkScrollViewer_PointerPressed;
+ _infiniteCanvasScrollViewer.PreviewKeyDown -= InkScrollViewer_PreviewKeyDown;
+ _canvasTextBox.TextChanged -= CanvasTextBox_TextChanged;
+ _canvasTextBox.SizeChanged -= CanvasTextBox_SizeChanged;
+ _undoButton.Click -= UndoButton_Click;
+ _redoButton.Click -= RedoButton_Click;
+ Unloaded -= InfiniteCanvas_Unloaded;
+ Application.Current.LeavingBackground -= Current_LeavingBackground;
+ _drawingSurfaceRenderer.CommandExecuted -= DrawingSurfaceRenderer_CommandExecuted;
+ _canvasTextBoxFontSizeTextBox.PreviewKeyDown -= CanvasTextBoxFontSizeTextBox_PreviewKeyDown;
+ }
+
+ private void RegisterEvents()
+ {
+ _canvasTextBoxFontSizeTextBox.TextChanged += CanvasTextBoxFontSizeTextBox_TextChanged;
+ _canvasTextBoxItlaicButton.Click += CanvasTextBoxItlaicButton_Clicked;
+ _canvasTextBoxBoldButton.Click += CanvasTextBoxBoldButton_Clicked;
+ _canvasTextBoxColorPicker.ColorChanged += CanvasTextBoxColorPicker_ColorChanged;
+ _enableTouchInkingButton.Checked += EnableTouchInkingButton_Checked;
+ _enableTouchInkingButton.Unchecked += EnableTouchInkingButton_Unchecked;
+ _enableTextButton.Checked += EnableTextButton_Checked;
+ _enableTextButton.Unchecked += EnableTextButton_Unchecked;
+ _eraseAllButton.Click += EraseAllButton_Click;
+ _infiniteCanvasScrollViewer.PointerPressed += InkScrollViewer_PointerPressed;
+ _infiniteCanvasScrollViewer.PreviewKeyDown += InkScrollViewer_PreviewKeyDown;
+ _canvasTextBox.TextChanged += CanvasTextBox_TextChanged;
+ _canvasTextBox.SizeChanged += CanvasTextBox_SizeChanged;
+ _undoButton.Click += UndoButton_Click;
+ _redoButton.Click += RedoButton_Click;
+ Unloaded += InfiniteCanvas_Unloaded;
+ Application.Current.LeavingBackground += Current_LeavingBackground;
+ _drawingSurfaceRenderer.CommandExecuted += DrawingSurfaceRenderer_CommandExecuted;
+ _canvasTextBoxFontSizeTextBox.PreviewKeyDown += CanvasTextBoxFontSizeTextBox_PreviewKeyDown;
+ }
+
+ private void ConfigureControls()
+ {
+ _inkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Pen;
+ _inkSync = _inkCanvas.InkPresenter.ActivateCustomDrying();
+ _inkCanvas.InkPresenter.StrokesCollected += OnStrokesCollected;
+
+ _inkCanvas.InkPresenter.UnprocessedInput.PointerMoved -= UnprocessedInput_PointerMoved;
+ _inkCanvas.InkPresenter.UnprocessedInput.PointerMoved += UnprocessedInput_PointerMoved;
+
+ SetZoomFactor();
+
+ _infiniteCanvasScrollViewer.ViewChanged -= InkScrollViewer_ViewChanged;
+ _infiniteCanvasScrollViewer.SizeChanged -= InkScrollViewer_SizeChanged;
+ _infiniteCanvasScrollViewer.ViewChanged += InkScrollViewer_ViewChanged;
+ _infiniteCanvasScrollViewer.SizeChanged += InkScrollViewer_SizeChanged;
+
+ SetCanvasWidthHeight();
+
+ _canvasTextBox.UpdateFontSize(TextFontSize);
+ }
+
+ private void SetZoomFactor()
+ {
+ var maxZoomFactor = DefaultMaxZoomFactor;
+ var minZoomFactor = DefaultMinZoomFactor;
+
+ if (MaxZoomFactor >= 1 && MaxZoomFactor <= 10)
+ {
+ maxZoomFactor = MaxZoomFactor;
+ }
+
+ if (MinZoomFactor >= .1 && MinZoomFactor <= 1)
+ {
+ minZoomFactor = MinZoomFactor;
+ }
+
+ _infiniteCanvasScrollViewer.MaxZoomFactor = (float)maxZoomFactor;
+ _infiniteCanvasScrollViewer.MinZoomFactor = (float)minZoomFactor;
+ }
+
+ private void SetCanvasWidthHeight()
+ {
+ _mainContainer.Width = CanvasWidth;
+ _mainContainer.Height = CanvasHeight;
+ _inkCanvas.Width = CanvasWidth;
+ _inkCanvas.Height = CanvasHeight;
+ _drawingSurfaceRenderer.Width = CanvasWidth;
+ _drawingSurfaceRenderer.Height = CanvasHeight;
+ _drawingSurfaceRenderer.ConfigureSpriteVisual(CanvasWidth, CanvasHeight);
+ }
+
+ private void ReDrawCanvas()
+ {
+ _drawingSurfaceRenderer.ReDraw(ViewPort);
+ }
+
+ ///
+ /// Redo the last action.
+ ///
+ public void Redo()
+ {
+ _drawingSurfaceRenderer.Redo(ViewPort);
+ }
+
+ ///
+ /// Undo the last action.
+ ///
+ public void Undo()
+ {
+ _drawingSurfaceRenderer.Undo(ViewPort);
+ }
+
+ ///
+ /// Export the InfinitCanvas as json string.
+ ///
+ /// json string
+ public string ExportAsJson()
+ {
+ return _drawingSurfaceRenderer.GetSerializedList();
+ }
+
+ ///
+ /// Import InfiniteCanvas from json string and render the new canvas, this function will empty the Redo/Undo queue.
+ ///
+ /// InfiniteCanvas json representation
+ public void ImportFromJson(string json)
+ {
+ _drawingSurfaceRenderer.RenderFromJsonAndDraw(ViewPort, json);
+ }
+
+ ///
+ /// This event triggered after each render happended because of any change in the canvas elements.
+ ///
+ public event EventHandler ReRenderCompleted;
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.xaml
new file mode 100644
index 00000000000..3140660c8cc
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.xaml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/JsonConverters/SerializablePoint.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/JsonConverters/SerializablePoint.cs
new file mode 100644
index 00000000000..774b6f1f278
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/JsonConverters/SerializablePoint.cs
@@ -0,0 +1,29 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using Windows.Foundation;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class SerializablePoint
+ {
+ public Point Position { get; set; }
+
+ public float Pressure { get; set; }
+
+ public float TiltX { get; set; }
+
+ public float TiltY { get; set; }
+
+ public ulong Timestamp { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/JsonConverters/SerializableStroke.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/JsonConverters/SerializableStroke.cs
new file mode 100644
index 00000000000..34b5027d52a
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/JsonConverters/SerializableStroke.cs
@@ -0,0 +1,101 @@
+// ******************************************************************
+// Copyright (c) Microsoft. All rights reserved.
+// This code is licensed under the MIT License (MIT).
+// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
+// ******************************************************************
+
+using System.Collections.Generic;
+using System.Numerics;
+using System.Runtime.Serialization;
+using Newtonsoft.Json;
+using Windows.UI.Input.Inking;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls
+{
+ internal class SerializableStroke
+ {
+ [JsonIgnore]
+ public List FinalPointList { get; set; }
+
+ public InkDrawingAttributes DrawingAttributes { get; set; }
+
+ public List SerializableFinalPointList { get; set; }
+
+ public short? SerializableDrawingAttributesKind { get; set; }
+
+ public double? SerializableDrawingAttributesPencilProperties { get; set; }
+
+ public Matrix3x2 PointTransform { get; set; }
+
+ [OnSerializing]
+ internal void OnSerializingMethod(StreamingContext context)
+ {
+ SerializableFinalPointList = new List(FinalPointList.Count);
+ foreach (var point in FinalPointList)
+ {
+ var serializablePoint = new SerializablePoint();
+ serializablePoint.Position = point.Position;
+ serializablePoint.Pressure = point.Pressure;
+ serializablePoint.TiltX = point.TiltX;
+ serializablePoint.TiltY = point.TiltY;
+ serializablePoint.Pressure = point.Pressure;
+ SerializableFinalPointList.Add(serializablePoint);
+ }
+
+ if (DrawingAttributes != null)
+ {
+ SerializableDrawingAttributesKind = (short)DrawingAttributes.Kind;
+ SerializableDrawingAttributesPencilProperties = DrawingAttributes.PencilProperties?.Opacity;
+ }
+ }
+
+ [OnDeserialized]
+ internal void OnDeserializedMethod(StreamingContext context)
+ {
+ var finalPointList = new List(SerializableFinalPointList.Count);
+
+ foreach (var point in SerializableFinalPointList)
+ {
+ finalPointList.Add(new InkPoint(point.Position, point.Pressure, point.TiltX, point.TiltY, point.Timestamp));
+ }
+
+ FinalPointList = finalPointList;
+
+ if (DrawingAttributes != null && SerializableDrawingAttributesKind.HasValue && SerializableDrawingAttributesKind == (short)InkDrawingAttributesKind.Pencil)
+ {
+ var pencilAttributes = InkDrawingAttributes.CreateForPencil();
+ pencilAttributes.Color = DrawingAttributes.Color;
+
+ // work around argument null exception.
+ pencilAttributes.FitToCurve = DrawingAttributes.FitToCurve;
+ pencilAttributes.IgnorePressure = DrawingAttributes.IgnorePressure;
+ pencilAttributes.IgnoreTilt = DrawingAttributes.IgnoreTilt;
+ pencilAttributes.Size = DrawingAttributes.Size;
+
+ if (SerializableDrawingAttributesPencilProperties.HasValue)
+ {
+ pencilAttributes.PencilProperties.Opacity = SerializableDrawingAttributesPencilProperties.Value;
+ }
+
+ DrawingAttributes = pencilAttributes;
+ }
+
+ // Empty unused values
+ SerializableDrawingAttributesPencilProperties = null;
+ SerializableFinalPointList = null;
+ SerializableDrawingAttributesKind = null;
+ }
+
+ [OnSerialized]
+ internal void OnSerializedMethod(StreamingContext context)
+ {
+ SerializableFinalPointList = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj
index 6c88f0ceacb..453aad117aa 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj
@@ -8,7 +8,6 @@
-
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml
index 47a6ad008d8..1d65e19f1cc 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml
+++ b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml
@@ -28,5 +28,6 @@
+
\ No newline at end of file
diff --git a/docs/controls/InfiniteCanvas.md b/docs/controls/InfiniteCanvas.md
new file mode 100644
index 00000000000..bcdca1f9864
--- /dev/null
+++ b/docs/controls/InfiniteCanvas.md
@@ -0,0 +1,82 @@
+---
+title: InfiniteCanvas XAML Control
+author: IbraheemOsama
+description: InfiniteCanvas is a canvas that supports Infinite Scrolling, Ink, Text, Format Text, Zoom in/out, Redo, Undo, Export canvas data, Import canvas data.
+keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, InfiniteCanvas, XAML Control, xaml
+---
+
+# InfiniteCanvas XAML Control
+
+The [InfiniteCanvas Control](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.uwp.ui.controls.infinitecanvas) is a canvas that supports Infinite Scrolling, Ink, Text, Format Text, Zoom in/out, Redo, Undo, Export canvas data, Import canvas data.
+
+## Syntax
+
+```xaml
+
+
+
+```
+
+## Sample Output
+
+![InfiniteCanvas animation](../resources/images/Controls/InfiniteCanvas.gif)
+
+## Properties
+
+| Property | Type | Description |
+| -- | -- | -- |
+| CanvasWidth | double | Gets or sets the width of the canvas, default value is the max value 2097152. |
+| CanvasHeight | double | Gets or sets the height of the canvas, default value is the max value 2097152. |
+| IsToolbarVisible | bool | Gets or sets a value indicating whether the toolbar is visible or not. |
+| MaxZoomFactor | double | Gets or sets the MaxZoomFactor for the canvas, range between 1 to 10 and the default value is 4. |
+| MinZoomFactor | double | Gets or sets the MinZoomFactor for the canvas, range between .1 to 1 the default value is .25. |
+
+## Methods
+
+| Method | Return Type | Description |
+| -- | -- | -- |
+| Redo() | void | Redo the last action. |
+| Undo() | void | Undo the last action. |
+| ExportAsJson() | string | Export the InfinitCanvas as json string. |
+| ImportFromJson(string json) | void | Import InfiniteCanvas from json string and render the new canvas, this function will empty the Redo/Undo queue. |
+
+## Events
+
+### ReRenderCompleted
+
+This event triggered after each render happended because of any change in the canvas elements.
+This event could be used to do the Auto Save functionality.
+
+## Examples
+
+The following sample demonstrates how to add InfiniteCanvas Control
+
+```xaml
+
+
+
+
+
+
+```
+
+## Sample Code
+
+[InfiniteCanvas Sample Page Source](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InfiniteCanvas). You can see this in action in [Windows Community Toolkit Sample App](https://www.microsoft.com/store/apps/9NBLGGH4TLCQ).
+
+## Default Template
+
+[InfiniteCanvas XAML File](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.xaml) is the XAML template used in the toolkit for the default styling.
+
+## Requirements
+
+| Device family | Universal, 10.0.14393.0 or higher |
+| -- | -- |
+| Namespace | Microsoft.Toolkit.Uwp.UI.Controls |
+| NuGet package | [Microsoft.Toolkit.Uwp.UI.Controls](https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.UI.Controls/) |
+
+## API
+
+* [InfiniteCanvas source code](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas)
diff --git a/docs/resources/images/Controls/InfiniteCanvas.gif b/docs/resources/images/Controls/InfiniteCanvas.gif
new file mode 100644
index 00000000000..eb9f1ba5331
Binary files /dev/null and b/docs/resources/images/Controls/InfiniteCanvas.gif differ