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