diff --git a/README.md b/README.md
index 473f42ec7..2feabcf01 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,42 @@ Of course, `AvatarView` also supports `Source` for loading images plus a few oth

+## DrawingView
+
+### Using DrawingView on Xamarin.Forms
+
+```xml
+
+
+
+```
+
+### Get Image from points
+
+```
+var stream = DrawingView.GetImageStream(
+ points,
+ new Size(GestureImage.Width, GestureImage.Height),
+ 10, Color.White, Color.Black);
+GestureImage.Source = ImageSource.FromStream(() => stream);
+```
+
+or
+
+```
+var stream = DrawingViewControl.GetImageStream(GestureImage.Width, GestureImage.Height);
+GestureImage.Source = ImageSource.FromStream(() => stream);
+```
+
## Contributions welcome!
If you have one or more of these common pieces of code that you are always replicating across apps, don't hesitate to contribute! We aim to be the first NuGet package you install when creating a new Xamarin app!
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Android/MainActivity.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Android/MainActivity.cs
index effbc124f..c5295d92d 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Android/MainActivity.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Android/MainActivity.cs
@@ -16,7 +16,6 @@ protected override void OnCreate(Bundle savedInstanceState)
base.OnCreate(savedInstanceState);
- global::Xamarin.Forms.Forms.SetFlags("CollectionView_Experimental");
Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Android/Xamarin.CommunityToolkit.Sample.Android.csproj b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Android/Xamarin.CommunityToolkit.Sample.Android.csproj
index 66a95231f..9d54018f0 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Android/Xamarin.CommunityToolkit.Sample.Android.csproj
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Android/Xamarin.CommunityToolkit.Sample.Android.csproj
@@ -20,6 +20,7 @@
true
true
Xamarin.Android.Net.AndroidClientHandler
+ true
@@ -52,10 +53,10 @@
-
+
- 2.1.0.714
+ 2.3.0.759
@@ -106,11 +107,11 @@
-
+
{81AADE72-D666-4AB0-83D9-8FE366E0755E}
Xamarin.CommunityToolkit.Sample
-
+
{b0dcdf81-953d-47da-a7d4-0565339bf07c}
Xamarin.CommunityToolkit
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.GTK/Xamarin.CommunityToolkit.Sample.GTK.csproj b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.GTK/Xamarin.CommunityToolkit.Sample.GTK.csproj
index 451ceb22b..b1c2c58eb 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.GTK/Xamarin.CommunityToolkit.Sample.GTK.csproj
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.GTK/Xamarin.CommunityToolkit.Sample.GTK.csproj
@@ -80,18 +80,21 @@
- 4.8.0.1269
+ 4.8.0.1451
- 4.8.0.1269
+ 4.8.0.1451
+
+
+ 2.3.0.759
-
+
{81aade72-d666-4ab0-83d9-8fe366e0755e}
Xamarin.CommunityToolkit.Sample
-
+
{b0dcdf81-953d-47da-a7d4-0565339bf07c}
Xamarin.CommunityToolkit
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Tizen/Xamarin.CommunityToolkit.Sample.Tizen.csproj b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Tizen/Xamarin.CommunityToolkit.Sample.Tizen.csproj
index 8c60ba8bd..763474686 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Tizen/Xamarin.CommunityToolkit.Sample.Tizen.csproj
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.Tizen/Xamarin.CommunityToolkit.Sample.Tizen.csproj
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.UWP/App.xaml.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.UWP/App.xaml.cs
index f8d822e46..2d6825189 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.UWP/App.xaml.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.UWP/App.xaml.cs
@@ -1,18 +1,8 @@
using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
-using Windows.Foundation;
-using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
-using Windows.UI.Xaml.Controls.Primitives;
-using Windows.UI.Xaml.Data;
-using Windows.UI.Xaml.Input;
-using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace Xamarin.CommunityToolkit.Sample.UWP
@@ -28,8 +18,8 @@ sealed partial class App : Application
///
public App()
{
- this.InitializeComponent();
- this.Suspending += OnSuspending;
+ InitializeComponent();
+ Suspending += OnSuspending;
}
///
@@ -42,7 +32,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
- this.DebugSettings.EnableFrameRateCounter = true;
+ DebugSettings.EnableFrameRateCounter = true;
}
#endif
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.UWP/Xamarin.CommunityToolkit.Sample.UWP.csproj b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.UWP/Xamarin.CommunityToolkit.Sample.UWP.csproj
index 4c470943a..bb5ff9c39 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.UWP/Xamarin.CommunityToolkit.Sample.UWP.csproj
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.UWP/Xamarin.CommunityToolkit.Sample.UWP.csproj
@@ -146,16 +146,16 @@
-
+
-
+
{81aade72-d666-4ab0-83d9-8fe366e0755e}
Xamarin.CommunityToolkit.Sample
-
+
{b0dcdf81-953d-47da-a7d4-0565339bf07c}
Xamarin.CommunityToolkit
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.WPF/MainWindow.xaml.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.WPF/MainWindow.xaml.cs
index c5595dcb7..8df09cfbc 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.WPF/MainWindow.xaml.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.WPF/MainWindow.xaml.cs
@@ -1,18 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-using Xamarin.Forms.PancakeView.Platforms.WPF;
+using Xamarin.Forms.PancakeView.Platforms.WPF;
using Xamarin.Forms.Platform.WPF;
namespace Xamarin.CommunityToolkit.Sample.WPF
@@ -25,7 +11,7 @@ public partial class MainWindow : FormsApplicationPage
public MainWindow()
{
InitializeComponent();
- Xamarin.Forms.Forms.Init();
+ Forms.Forms.Init();
PancakeViewRenderer.Init();
LoadApplication(new Xamarin.CommunityToolkit.Sample.App());
}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.WPF/Xamarin.CommunityToolkit.Sample.WPF.csproj b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.WPF/Xamarin.CommunityToolkit.Sample.WPF.csproj
index 58a6dd6b7..a4b7b5164 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.WPF/Xamarin.CommunityToolkit.Sample.WPF.csproj
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.WPF/Xamarin.CommunityToolkit.Sample.WPF.csproj
@@ -1,125 +1,125 @@
-
-
- Debug
- AnyCPU
- {C4D6CD2D-8DF4-4D46-936C-1AB31C87B5EA}
- WinExe
- Xamarin.CommunityToolkit.Sample.WPF
- Xamarin.CommunityToolkit.Sample.WPF
- v4.7.2
- 512
- {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- 4
- true
- true
-
-
-
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
-
-
-
-
-
- 4.0
-
-
-
-
-
-
-
- MSBuild:Compile
- Designer
-
-
- MSBuild:Compile
- Designer
-
-
- App.xaml
- Code
-
-
- MainWindow.xaml
- Code
-
-
-
-
- Code
-
-
- True
- True
- Resources.resx
-
-
- True
- Settings.settings
- True
-
-
- ResXFileCodeGenerator
- Resources.Designer.cs
-
-
- SettingsSingleFileGenerator
- Settings.Designer.cs
-
-
-
-
-
-
-
- {81aade72-d666-4ab0-83d9-8fe366e0755e}
- Xamarin.CommunityToolkit.Sample
-
-
- {b0dcdf81-953d-47da-a7d4-0565339bf07c}
- Xamarin.CommunityToolkit
-
-
-
-
- 3.0.1
-
-
- 3.0.1
-
-
- 4.8.0.1269
-
-
- 4.8.0.1269
-
-
-
-
+
+
+ Debug
+ AnyCPU
+ {C4D6CD2D-8DF4-4D46-936C-1AB31C87B5EA}
+ WinExe
+ Xamarin.CommunityToolkit.Sample.WPF
+ Xamarin.CommunityToolkit.Sample.WPF
+ v4.7.2
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+ true
+
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+ MainWindow.xaml
+ Code
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+ {81aade72-d666-4ab0-83d9-8fe366e0755e}
+ Xamarin.CommunityToolkit.Sample
+
+
+ {b0dcdf81-953d-47da-a7d4-0565339bf07c}
+ Xamarin.CommunityToolkit
+
+
+
+
+ 3.0.1
+
+
+ 3.0.1
+
+
+ 4.8.0.1451
+
+
+ 4.8.0.1451
+
+
+
+
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.iOS/AppDelegate.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.iOS/AppDelegate.cs
index 5b3a3540c..bf5ca64ed 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.iOS/AppDelegate.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.iOS/AppDelegate.cs
@@ -6,8 +6,8 @@ namespace Xamarin.CommunityToolkit.Sample.iOS
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
- [Register("AppDelegate")]
- public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
+ [Register(nameof(AppDelegate))]
+ public class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
@@ -16,7 +16,6 @@ public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsAppli
// You have 17 seconds to return from this method, or iOS will terminate your application.
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
- global::Xamarin.Forms.Forms.SetFlags("CollectionView_Experimental");
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.iOS/Xamarin.CommunityToolkit.Sample.iOS.csproj b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.iOS/Xamarin.CommunityToolkit.Sample.iOS.csproj
index 8600735c0..840b128f0 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.iOS/Xamarin.CommunityToolkit.Sample.iOS.csproj
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample.iOS/Xamarin.CommunityToolkit.Sample.iOS.csproj
@@ -139,10 +139,10 @@
-
+
- 2.1.0.714
+ 2.3.0.759
@@ -165,11 +165,11 @@
-
+
{81AADE72-D666-4AB0-83D9-8FE366E0755E}
Xamarin.CommunityToolkit.Sample
-
+
{b0dcdf81-953d-47da-a7d4-0565339bf07c}
Xamarin.CommunityToolkit
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Pages/Views/DrawingViewPage.xaml b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Pages/Views/DrawingViewPage.xaml
new file mode 100644
index 000000000..12ee78764
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Pages/Views/DrawingViewPage.xaml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Pages/Views/DrawingViewPage.xaml.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Pages/Views/DrawingViewPage.xaml.cs
new file mode 100644
index 000000000..0f8cc2830
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Pages/Views/DrawingViewPage.xaml.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms;
+using Xamarin.CommunityToolkit.UI.Views;
+using System.Collections.ObjectModel;
+
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views
+{
+ public partial class DrawingViewPage : BasePage
+ {
+ public DrawingViewPage()
+ {
+ InitializeComponent();
+ DrawingViewControl.Points = GeneratePoints(200);
+ DrawingViewControl.DrawingCompletedCommand = new Command>(points =>
+ {
+ Logs.Text += "GestureCompletedCommand executed" + Environment.NewLine;
+ DrawImage(points);
+ });
+ }
+
+ void LoadPointsButtonClicked(object sender, EventArgs e) => DrawingViewControl.Points = GeneratePoints(50);
+
+ void DisplayHiddenLabelButtonClicked(object sender, EventArgs e) => HiddenLabel.IsVisible = !HiddenLabel.IsVisible;
+
+ void GetCurrentDrawingViewImageClicked(object sender, EventArgs e)
+ {
+ var stream = DrawingViewControl.GetImageStream(GestureImage.Width, GestureImage.Height);
+ GestureImage.Source = ImageSource.FromStream(() => stream);
+ }
+
+ void GetImageClicked(object sender, EventArgs e)
+ {
+ var points = GeneratePoints(100);
+ DrawImage(points);
+ }
+
+ static ObservableCollection GeneratePoints(int count)
+ {
+ var points = new ObservableCollection();
+ for (var i = 0; i < count; i++)
+ {
+ points.Add(new Point(i, i));
+ }
+
+ return points;
+ }
+
+ void DrawImage(IEnumerable points)
+ {
+ var stream = DrawingView.GetImageStream(points, new Size(GestureImage.Width, GestureImage.Height), 10,
+ Color.White, Color.Black);
+ GestureImage.Source = ImageSource.FromStream(() => stream);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Resx/AppResources.Designer.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Resx/AppResources.Designer.cs
index 1fd909ed2..bcc54384d 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Resx/AppResources.Designer.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Resx/AppResources.Designer.cs
@@ -10,35 +10,48 @@
namespace Xamarin.CommunityToolkit.Sample.Resx {
using System;
- using System.Reflection;
- [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
- [System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class AppResources {
- private static System.Resources.ResourceManager resourceMan;
+ private static global::System.Resources.ResourceManager resourceMan;
- private static System.Globalization.CultureInfo resourceCulture;
+ private static global::System.Globalization.CultureInfo resourceCulture;
- [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal AppResources() {
}
- [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static System.Resources.ResourceManager ResourceManager {
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
get {
- if (object.Equals(null, resourceMan)) {
- System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Xamarin.CommunityToolkit.Sample.Resx.AppResources", typeof(AppResources).Assembly);
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xamarin.CommunityToolkit.Sample.Resx.AppResources", typeof(AppResources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
- [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static System.Globalization.CultureInfo Culture {
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
@@ -47,27 +60,39 @@ internal static System.Globalization.CultureInfo Culture {
}
}
+ ///
+ /// Looks up a localized string similar to Change language below, hit Save and see the texts in this page change. This will not affect the rest of the application, it just serves as a demo..
+ ///
internal static string ChangeLanguage {
get {
return ResourceManager.GetString("ChangeLanguage", resourceCulture);
}
}
+ ///
+ /// Looks up a localized string similar to English.
+ ///
internal static string English {
get {
return ResourceManager.GetString("English", resourceCulture);
}
}
- internal static string Spanish {
+ ///
+ /// Looks up a localized string similar to Save.
+ ///
+ internal static string Save {
get {
- return ResourceManager.GetString("Spanish", resourceCulture);
+ return ResourceManager.GetString("Save", resourceCulture);
}
}
- internal static string Save {
+ ///
+ /// Looks up a localized string similar to Spanish.
+ ///
+ internal static string Spanish {
get {
- return ResourceManager.GetString("Save", resourceCulture);
+ return ResourceManager.GetString("Spanish", resourceCulture);
}
}
}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/ViewModels/Views/DrawingViewViewModel.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/ViewModels/Views/DrawingViewViewModel.cs
new file mode 100644
index 000000000..00ec31f56
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/ViewModels/Views/DrawingViewViewModel.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+using System.Windows.Input;
+using Xamarin.Forms;
+using System.Linq;
+using Xamarin.CommunityToolkit.Extensions;
+using System.Collections.ObjectModel;
+using Xamarin.CommunityToolkit.ObjectModel;
+
+namespace Xamarin.CommunityToolkit.Sample.ViewModels.Views
+{
+ public class DrawingViewViewModel : BaseViewModel
+ {
+ ObservableCollection macroPoints = new ObservableCollection();
+
+ public DrawingViewViewModel()
+ {
+ SetPointsCommand = new Command(() =>
+ {
+ var points = GeneratePoints(100).ToList();
+ MacroPoints = new ObservableCollection(points);
+
+ MacroPoints2.Clear();
+ foreach (var point in points)
+ {
+ MacroPoints2.Add(point);
+ }
+ OnPropertyChanged(nameof(MacroPoints2));
+ });
+ GetPointsCommand = new AsyncCommand(async () =>
+ {
+ await Application.Current.MainPage.DisplayToastAsync($"PointsCount: {MacroPoints.Count}");
+ await Application.Current.MainPage.DisplayToastAsync($"Points2Count: {MacroPoints2.Count}");
+ });
+ }
+
+ public ICommand SetPointsCommand { get; }
+
+ public ICommand GetPointsCommand { get; }
+
+ public ObservableCollection MacroPoints
+ {
+ get => macroPoints;
+ set => SetProperty( ref macroPoints, value);
+ }
+
+ public ObservableCollection MacroPoints2 { get; } = new ObservableCollection();
+
+ static IEnumerable GeneratePoints(int count)
+ {
+ var points = new List();
+ for (var i = 0; i < count; i++)
+ {
+ points.Add(new Point(i, i));
+ }
+
+ return points;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/ViewModels/Views/ViewsGalleryViewModel.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/ViewModels/Views/ViewsGalleryViewModel.cs
index 9181ff785..32096fa5c 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/ViewModels/Views/ViewsGalleryViewModel.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/ViewModels/Views/ViewsGalleryViewModel.cs
@@ -14,6 +14,9 @@ public class ViewsGalleryViewModel : BaseGalleryViewModel
new SectionModel(typeof(BadgeViewPage), "BadgeView",
"View used to used to notify users notifications, or status of something"),
+ new SectionModel(typeof(DrawingViewPage), "DrawingView",
+ "Draw & GO DrawingView makes capturing and displaying gestures extremely simple on all platforms that Xamarin.Forms supports. (Android, iOS, macOS, UWP, WPF, GTK and Tizen)"),
+
new SectionModel(typeof(CameraViewPage), "CameraView",
"The CameraView allows you to show a live preview from the camera. You can take pictures, record videos and much more!"),
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Xamarin.CommunityToolkit.Sample.csproj b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Xamarin.CommunityToolkit.Sample.csproj
index 79093678f..f2dd3d78d 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Xamarin.CommunityToolkit.Sample.csproj
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.Sample/Xamarin.CommunityToolkit.Sample.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.0
@@ -64,9 +64,6 @@
ResXFileCodeGenerator
AppResources.es.Designer.cs
-
- ImpliedOrderGridBehaviorPage.xaml
-
CharactersValidationBehaviorPage.xaml
@@ -78,10 +75,10 @@
-
+
-
+
@@ -104,6 +101,6 @@
-
+
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/AvatarView/ImageSourceValidator.android.ios.macos.uwp.wpf.gtk.tizen.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/AvatarView/ImageSourceValidator.android.ios.macos.uwp.wpf.gtk.tizen.cs
index ea0113f73..0dddff450 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/AvatarView/ImageSourceValidator.android.ios.macos.uwp.wpf.gtk.tizen.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/AvatarView/ImageSourceValidator.android.ios.macos.uwp.wpf.gtk.tizen.cs
@@ -8,7 +8,7 @@
using Xamarin.Forms.Platform.iOS;
#elif __MACOS__
using Xamarin.Forms.Platform.MacOS;
-#elif UWP
+#elif UAP10_0
using Xamarin.Forms.Platform.UWP;
#elif NET471
using Xamarin.Forms.Platform.GTK.Renderers;
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/DrawingView.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/DrawingView.shared.cs
new file mode 100644
index 000000000..08ed54dd2
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/DrawingView.shared.cs
@@ -0,0 +1,94 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Windows.Input;
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ public class DrawingView : View
+ {
+ const int minValueGranularity = 5;
+
+ public static readonly BindableProperty GranularityProperty =
+ BindableProperty.Create(nameof(Granularity), typeof(int), typeof(DrawingView), minValueGranularity, coerceValue: CoerceValue);
+
+ public static readonly BindableProperty EnableSmoothedPathProperty =
+ BindableProperty.Create(nameof(EnableSmoothedPath), typeof(bool), typeof(DrawingView), default(bool));
+
+ public static readonly BindableProperty LineColorProperty =
+ BindableProperty.Create(nameof(LineColor), typeof(Color), typeof(DrawingView), Color.Default);
+
+ public static readonly BindableProperty LineWidthProperty =
+ BindableProperty.Create(nameof(LineWidth), typeof(float), typeof(DrawingView), 5f);
+
+ public static readonly BindableProperty ClearOnFinishProperty =
+ BindableProperty.Create(nameof(ClearOnFinish), typeof(bool), typeof(DrawingView), default(bool));
+
+ public static readonly BindableProperty PointsProperty = BindableProperty.Create(
+ nameof(Points), typeof(ObservableCollection), typeof(DrawingView), new ObservableCollection(),
+ BindingMode.TwoWay);
+
+ public static readonly BindableProperty DrawingCompletedCommandProperty = BindableProperty.Create(
+ nameof(DrawingCompletedCommand), typeof(ICommand), typeof(DrawingView), default(ICommand));
+
+ public ObservableCollection Points
+ {
+ get => (ObservableCollection)GetValue(PointsProperty);
+ set => SetValue(PointsProperty, value);
+ }
+
+ public ICommand DrawingCompletedCommand
+ {
+ get => (ICommand)GetValue(DrawingCompletedCommandProperty);
+ set => SetValue(DrawingCompletedCommandProperty, value);
+ }
+
+ public int Granularity
+ {
+ get => (int)GetValue(GranularityProperty);
+ set => SetValue(GranularityProperty, value);
+ }
+
+ public bool EnableSmoothedPath
+ {
+ get => (bool)GetValue(EnableSmoothedPathProperty);
+ set => SetValue(EnableSmoothedPathProperty, value);
+ }
+
+ public Color LineColor
+ {
+ get => (Color)GetValue(LineColorProperty);
+ set => SetValue(LineColorProperty, value);
+ }
+
+ public float LineWidth
+ {
+ get => (float)GetValue(LineWidthProperty);
+ set => SetValue(LineWidthProperty, value);
+ }
+
+ public bool ClearOnFinish
+ {
+ get => (bool)GetValue(ClearOnFinishProperty);
+ set => SetValue(ClearOnFinishProperty, value);
+ }
+
+ static object CoerceValue(BindableObject bindable, object value)
+ => ((DrawingView)bindable).CoerceValue((int)value);
+
+ int CoerceValue(int value) => value < minValueGranularity ? minValueGranularity : value;
+
+ public Stream GetImageStream(double imageSizeWidth, double imageSizeHeight) =>
+ DrawingViewService.GetImageStream(Points.ToList(), new Size(imageSizeWidth, imageSizeHeight), LineWidth, LineColor,
+ BackgroundColor);
+
+ public static Stream GetImageStream(IEnumerable points,
+ Size imageSize,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor) =>
+ DrawingViewService.GetImageStream(points.ToList(), imageSize, lineWidth, strokeColor, backgroundColor);
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Extensions/Extensions.ios.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Extensions/Extensions.ios.cs
new file mode 100644
index 000000000..f565f4599
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Extensions/Extensions.ios.cs
@@ -0,0 +1,12 @@
+using CoreGraphics;
+using UIKit;
+
+namespace Xamarin.CommunityToolkit.UI.Views.iOS
+{
+ public static class Extensions
+ {
+ public static void MoveTo(this UIBezierPath path, double x, double y) => path.MoveTo(new CGPoint(x, y));
+
+ public static void LineTo(this UIBezierPath path, double x, double y) => path.AddLineTo(new CGPoint(x, y));
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Extensions/Extensions.macos.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Extensions/Extensions.macos.cs
new file mode 100644
index 000000000..7efa6e3c4
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Extensions/Extensions.macos.cs
@@ -0,0 +1,12 @@
+using AppKit;
+using CoreGraphics;
+
+namespace Xamarin.CommunityToolkit.UI.Views.macOS
+{
+ public static class Extensions
+ {
+ public static void MoveTo(this NSBezierPath path, double x, double y) => path.MoveTo(new CGPoint(x, y));
+
+ public static void LineTo(this NSBezierPath path, double x, double y) => path.LineTo(new CGPoint(x, y));
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.android.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.android.cs
new file mode 100644
index 000000000..0c10efef5
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.android.cs
@@ -0,0 +1,181 @@
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+using System.ComponentModel;
+using System.Linq;
+using Android.Content;
+using Android.Graphics;
+using Android.Views;
+using Xamarin.Forms.Platform.Android;
+using Point = Xamarin.Forms.Point;
+using View = Android.Views.View;
+
+[assembly: ExportRenderer(typeof(DrawingView), typeof(DrawingViewRenderer))]
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ public class DrawingViewRenderer : ViewRenderer
+ {
+ bool disposed;
+
+ readonly Paint canvasPaint;
+ readonly Paint drawPaint;
+ readonly Path drawPath;
+ Bitmap canvasBitmap;
+ Canvas drawCanvas;
+
+ public DrawingViewRenderer(Context context)
+ : base(context)
+ {
+ drawPath = new Path();
+ drawPaint = new Paint
+ {
+ AntiAlias = true
+ };
+
+ drawPaint.SetStyle(Paint.Style.Stroke);
+ drawPaint.StrokeJoin = Paint.Join.Round;
+ drawPaint.StrokeCap = Paint.Cap.Round;
+
+ canvasPaint = new Paint
+ {
+ Dither = true
+ };
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == DrawingView.PointsProperty.PropertyName)
+ {
+ LoadPoints();
+ }
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+ if (Element != null)
+ {
+ SetBackgroundColor(Element.BackgroundColor.ToAndroid());
+ drawPaint.Color = Element.LineColor.ToAndroid();
+ drawPaint.StrokeWidth = Element.LineWidth;
+ Element.Points.CollectionChanged += (sender, args) =>
+ {
+ LoadPoints();
+ };
+ }
+ }
+
+ protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
+ {
+ base.OnSizeChanged(w, h, oldw, oldh);
+
+ canvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
+ drawCanvas = new Canvas(canvasBitmap);
+ LoadPoints();
+ }
+
+ protected override void OnDraw(Canvas canvas)
+ {
+ base.OnDraw(canvas);
+
+ canvas.DrawBitmap(canvasBitmap, 0, 0, canvasPaint);
+ canvas.DrawPath(drawPath, drawPaint);
+ }
+
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ var touchX = e.GetX();
+ var touchY = e.GetY();
+
+ switch (e.Action)
+ {
+ case MotionEventActions.Down:
+ Parent?.RequestDisallowInterceptTouchEvent(true);
+ Element.Points.Clear();
+ drawCanvas.DrawColor(Element.BackgroundColor.ToAndroid(), PorterDuff.Mode.Clear);
+ drawPath.MoveTo(touchX, touchY);
+ break;
+ case MotionEventActions.Move:
+ if (touchX > 0 && touchY > 0)
+ {
+ drawPath.LineTo(touchX, touchY);
+ }
+
+ Element.Points.Add(new Point(touchX, touchY));
+ break;
+ case MotionEventActions.Up:
+ Parent?.RequestDisallowInterceptTouchEvent(false);
+ drawCanvas.DrawPath(drawPath, drawPaint);
+ drawPath.Reset();
+ if (Element.Points.Any())
+ {
+ if (Element.DrawingCompletedCommand != null && Element.DrawingCompletedCommand.CanExecute(null))
+ {
+ Element.DrawingCompletedCommand.Execute(Element.Points);
+ }
+ }
+
+ if (Element.ClearOnFinish)
+ {
+ Element.Points.Clear();
+ }
+ break;
+ default:
+ return false;
+ }
+
+ Invalidate();
+
+ return true;
+ }
+
+ public override bool OnInterceptTouchEvent(MotionEvent ev)
+ {
+ if (!Enabled || Element?.IsEnabled == false)
+ return true;
+
+ return base.OnInterceptTouchEvent(ev);
+ }
+
+ void LoadPoints()
+ {
+ drawCanvas.DrawColor(Element.BackgroundColor.ToAndroid(), PorterDuff.Mode.Clear);
+ drawPath.Reset();
+ if (Element.Points.Any())
+ {
+ drawPath.MoveTo((float)Element.Points[0].X, (float)Element.Points[0].Y);
+ foreach (var (x, y) in Element.Points)
+ {
+ drawPath.LineTo((float)x, (float)y);
+ }
+
+ drawCanvas.DrawPath(drawPath, drawPaint);
+ drawPath.Reset();
+
+ Invalidate();
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ drawCanvas.Dispose();
+ drawPaint.Dispose();
+ drawPath.Dispose();
+ canvasBitmap.Dispose();
+ canvasPaint.Dispose();
+ }
+
+ disposed = true;
+
+ base.Dispose(disposing);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.gtk.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.gtk.cs
new file mode 100644
index 000000000..6f80f0dc8
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.gtk.cs
@@ -0,0 +1,202 @@
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using Cairo;
+using Gdk;
+using global::Gtk;
+using Xamarin.Forms.Platform.GTK;
+using Xamarin.Forms.Platform.GTK.Extensions;
+using Point = Xamarin.Forms.Point;
+
+[assembly: ExportRenderer(typeof(DrawingView), typeof(DrawingViewRenderer))]
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ public class DrawingViewRenderer : ViewRenderer
+ {
+ bool disposed;
+
+ DrawingArea area;
+ bool isDrawing;
+ PointD point;
+ PointD previousPoint;
+ ImageSurface surface;
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == DrawingView.PointsProperty.PropertyName)
+ {
+ surface = new ImageSurface(Format.Argb32, Convert.ToInt32(Element.Width),
+ Convert.ToInt32(Element.Height));
+ LoadPoints(surface);
+ }
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+ if (Control == null && Element != null)
+ {
+ var vBox = new VBox();
+ surface = new ImageSurface(Format.Argb32, 500, 500);
+ area = new DrawingArea();
+
+ area.ModifyBg(StateType.Normal, Element.BackgroundColor.ToGtkColor());
+ area.AddEvents((int)EventMask.PointerMotionMask |
+ (int)EventMask.ButtonPressMask |
+ (int)EventMask.ButtonReleaseMask);
+
+ point = new PointD(500.0, 500.0);
+ isDrawing = false;
+
+ vBox.Add(area);
+ Element.Points.CollectionChanged += (sender, args) =>
+ {
+ LoadPoints(surface);
+ };
+ SetNativeControl(vBox);
+ }
+
+ if (e.OldElement != null)
+ {
+ area.ExposeEvent -= OnDrawingAreaExposed;
+ area.ButtonPressEvent -= OnMousePress;
+ area.ButtonReleaseEvent -= OnMouseRelease;
+ area.MotionNotifyEvent -= OnMouseMotion;
+ }
+
+ if (e.NewElement != null)
+ {
+ area.ExposeEvent += OnDrawingAreaExposed;
+ area.ButtonPressEvent += OnMousePress;
+ area.ButtonReleaseEvent += OnMouseRelease;
+ area.MotionNotifyEvent += OnMouseMotion;
+ }
+ }
+
+ void OnDrawingAreaExposed(object source, ExposeEventArgs args)
+ {
+ Context ctx;
+ using (ctx = CairoHelper.Create(area.GdkWindow))
+ {
+ ctx.SetSource(new SurfacePattern(surface));
+ ctx.Paint();
+ }
+
+ if (isDrawing)
+ {
+ using (ctx = CairoHelper.Create(area.GdkWindow))
+ {
+ DrawPoint(ctx, point);
+ }
+ }
+ }
+
+ void OnMousePress(object source, ButtonPressEventArgs args)
+ {
+ surface = new ImageSurface(Format.Argb32, Convert.ToInt32(Element.Width), Convert.ToInt32(Element.Height));
+
+ point.X = args.Event.X;
+ point.Y = args.Event.Y;
+ previousPoint = point;
+ isDrawing = true;
+ area.QueueDraw();
+ Element.Points.Clear();
+ }
+
+ void OnMouseRelease(object source, ButtonReleaseEventArgs args)
+ {
+ point.X = args.Event.X;
+ point.Y = args.Event.Y;
+
+ isDrawing = false;
+
+ using (var ctx = new Context(surface))
+ {
+ DrawPoint(ctx, point);
+ }
+
+ area.QueueDraw();
+ if (Element.Points.Any())
+ {
+ if (Element.DrawingCompletedCommand != null && Element.DrawingCompletedCommand.CanExecute(null))
+ {
+ Element.DrawingCompletedCommand.Execute(Element.Points);
+ }
+ }
+
+ if (Element.ClearOnFinish)
+ {
+ Element.Points.Clear();
+ }
+ }
+
+ void OnMouseMotion(object source, MotionNotifyEventArgs args)
+ {
+ if (isDrawing)
+ {
+ point.X = args.Event.X;
+ point.Y = args.Event.Y;
+
+ using (var ctx = new Context(surface))
+ {
+ DrawPoint(ctx, point);
+ }
+
+ area.QueueDraw();
+ }
+ }
+
+ void DrawPoint(Context ctx, PointD pointD)
+ {
+ ctx.SetSourceRGBA(Element.LineColor.R, Element.LineColor.G, Element.LineColor.B, Element.LineColor.A);
+ ctx.LineWidth = Element.LineWidth;
+ ctx.MoveTo(previousPoint);
+ previousPoint = pointD;
+ ctx.LineTo(pointD);
+ ctx.Stroke();
+ Element.Points.Add(new Point(pointD.X, pointD.Y));
+ }
+
+ void LoadPoints(ImageSurface imageSurface)
+ {
+ var stylusPoints = Element?.Points?.Select(stylusPoint => new PointD(stylusPoint.X, stylusPoint.Y))
+ .ToList();
+ if (stylusPoints != null && stylusPoints.Any())
+ {
+ previousPoint = stylusPoints[0];
+ using (var ctx = new Context(imageSurface))
+ {
+ foreach (var stylusPoint in stylusPoints)
+ {
+ DrawPoint(ctx, stylusPoint);
+ }
+ }
+
+ area.QueueDraw();
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ area.Dispose();
+ surface.Dispose();
+ }
+
+ disposed = true;
+
+ base.Dispose(disposing);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.ios.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.ios.cs
new file mode 100644
index 000000000..0f5f8aef3
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.ios.cs
@@ -0,0 +1,217 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using CoreGraphics;
+using Foundation;
+using UIKit;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.CommunityToolkit.UI.Views.iOS;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.iOS;
+
+[assembly: ExportRenderer(typeof(DrawingView), typeof(DrawingViewRenderer))]
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ public class DrawingViewRenderer : ViewRenderer
+ {
+ bool disposed;
+ UIBezierPath currentPath;
+ UIColor lineColor;
+ CGPoint previousPoint;
+
+ public DrawingViewRenderer() => currentPath = new UIBezierPath();
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == DrawingView.PointsProperty.PropertyName)
+ {
+ LoadPoints();
+ }
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+ if (Element != null)
+ {
+ BackgroundColor = Element.BackgroundColor.ToUIColor();
+ currentPath.LineWidth = Element.LineWidth;
+ lineColor = Element.LineColor.ToUIColor();
+ Element.Points.CollectionChanged += (sender, args) =>
+ {
+ LoadPoints();
+ };
+ }
+ }
+
+ public override void TouchesBegan(NSSet touches, UIEvent evt)
+ {
+ Element.Points.Clear();
+ currentPath.RemoveAllPoints();
+
+ var touch = (UITouch)touches.AnyObject;
+ previousPoint = touch.PreviousLocationInView(this);
+ currentPath.MoveTo(previousPoint);
+
+ InvokeOnMainThread(SetNeedsDisplay);
+ }
+
+ public override void TouchesMoved(NSSet touches, UIEvent evt)
+ {
+ var touch = (UITouch)touches.AnyObject;
+ var currentPoint = touch.LocationInView(this);
+ AddPointToPath(currentPoint);
+ }
+
+ void AddPointToPath(CGPoint currentPoint)
+ {
+ currentPath.AddLineTo(currentPoint);
+ InvokeOnMainThread(SetNeedsDisplay);
+ Element.Points.Add(currentPoint.ToPoint());
+ }
+
+ public override void TouchesEnded(NSSet touches, UIEvent evt)
+ {
+ UpdatePath();
+ if (Element.Points.Any())
+ {
+ if (Element.DrawingCompletedCommand != null && Element.DrawingCompletedCommand.CanExecute(null))
+ {
+ Element.DrawingCompletedCommand.Execute(Element.Points);
+ }
+ }
+
+ if (Element.ClearOnFinish)
+ {
+ Element.Points.Clear();
+ }
+ }
+
+ public override void TouchesCancelled(NSSet touches, UIEvent evt) => InvokeOnMainThread(SetNeedsDisplay);
+
+ public override void Draw(CGRect rect)
+ {
+ lineColor.SetStroke();
+ currentPath.Stroke();
+ }
+
+ void LoadPoints()
+ {
+ var stylusPoints = Element.Points.Select(point => new CGPoint(point.X, point.Y)).ToList();
+ currentPath.RemoveAllPoints();
+ if (stylusPoints.Any())
+ {
+ previousPoint = stylusPoints[0];
+ currentPath.MoveTo(previousPoint);
+ foreach (var point in stylusPoints)
+ {
+ AddPointToPath(point);
+ }
+
+ UpdatePath();
+ }
+ }
+
+ void UpdatePath()
+ {
+ var smoothedPoints = Element.EnableSmoothedPath
+ ? SmoothedPathWithGranularity(Element.Points, Element.Granularity, ref currentPath)
+ : new ObservableCollection(Element.Points);
+ InvokeOnMainThread(SetNeedsDisplay);
+ Element.Points.Clear();
+ foreach (var point in smoothedPoints)
+ {
+ Element.Points.Add(point);
+ }
+ }
+
+ ObservableCollection SmoothedPathWithGranularity(ObservableCollection currentPoints,
+ int granularity,
+ ref UIBezierPath smoothedPath)
+ {
+ // not enough points to smooth effectively, so return the original path and points.
+ if (currentPoints.Count < granularity + 2)
+ {
+ return new ObservableCollection(currentPoints);
+ }
+
+ // create a new bezier path to hold the smoothed path.
+ smoothedPath.RemoveAllPoints();
+ var smoothedPoints = new ObservableCollection();
+
+ // duplicate the first and last points as control points.
+ currentPoints.Insert(0, currentPoints[0]);
+ currentPoints.Add(currentPoints[^1]);
+
+ // add the first point
+ smoothedPath.MoveTo(currentPoints[0].X, currentPoints[0].Y);
+ smoothedPoints.Add(currentPoints[0]);
+
+ var currentPointsCount = currentPoints.Count;
+ for (var index = 1; index < currentPointsCount - 2; index++)
+ {
+ var p0 = currentPoints[index - 1];
+ var p1 = currentPoints[index];
+ var p2 = currentPoints[index + 1];
+ var p3 = currentPoints[index + 2];
+
+ // add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines
+ for (var i = 1; i < granularity; i++)
+ {
+ var t = i * (1f / granularity);
+ var tt = t * t;
+ var ttt = tt * t;
+
+ // intermediate point
+ var mid = GetIntermediatePoint(p0, p1, p2, p3, t, tt, ttt);
+ smoothedPath.LineTo(mid.X, mid.Y);
+ smoothedPoints.Add(mid);
+ }
+
+ // add p2
+ smoothedPath.LineTo(p2.X, p2.Y);
+ smoothedPoints.Add(p2);
+ }
+
+ // add the last point
+ var last = currentPoints[^1];
+ smoothedPath.LineTo(last.X, last.Y);
+ smoothedPoints.Add(last);
+ return smoothedPoints;
+ }
+
+ Point GetIntermediatePoint(Point p0, Point p1, Point p2, Point p3, in float t, in float tt, in float ttt) =>
+ new Point
+ {
+ X = 0.5f *
+ ((2f * p1.X) +
+ ((p2.X - p0.X) * t) +
+ (((((2f * p0.X) - (5f * p1.X)) + (4f * p2.X)) - p3.X) * tt) +
+ ((((3f * p1.X) - p0.X - (3f * p2.X)) + p3.X) * ttt)),
+ Y = 0.5f *
+ ((2 * p1.Y) +
+ ((p2.Y - p0.Y) * t) +
+ (((((2 * p0.Y) - (5 * p1.Y)) + (4 * p2.Y)) - p3.Y) * tt) +
+ ((((3 * p1.Y) - p0.Y - (3 * p2.Y)) + p3.Y) * ttt))
+ };
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ currentPath.Dispose();
+ }
+
+ disposed = true;
+
+ base.Dispose(disposing);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.macos.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.macos.cs
new file mode 100644
index 000000000..092f3995f
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.macos.cs
@@ -0,0 +1,214 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using AppKit;
+using CoreGraphics;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.CommunityToolkit.UI.Views.macOS;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.MacOS;
+
+[assembly: ExportRenderer(typeof(DrawingView), typeof(DrawingViewRenderer))]
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ public class DrawingViewRenderer : ViewRenderer
+ {
+ bool disposed;
+ NSBezierPath currentPath;
+ NSColor lineColor;
+ CGPoint previousPoint;
+
+ public DrawingViewRenderer() => currentPath = new NSBezierPath();
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == DrawingView.PointsProperty.PropertyName)
+ {
+ LoadPoints();
+ }
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+ if (Element != null)
+ {
+ WantsLayer = true;
+ Layer.BackgroundColor = Element.BackgroundColor.ToCGColor();
+ currentPath.LineWidth = Element.LineWidth;
+ lineColor = Element.LineColor.ToNSColor();
+ Element.Points.CollectionChanged += (sender, args) =>
+ {
+ LoadPoints();
+ };
+ }
+ }
+
+ public override void MouseDown(NSEvent theEvent)
+ {
+ Element.Points.Clear();
+ currentPath.RemoveAllPoints();
+
+ previousPoint = theEvent.LocationInWindow;
+ currentPath.MoveTo(previousPoint);
+
+ InvokeOnMainThread(Layer.SetNeedsDisplay);
+ }
+
+ public override void MouseUp(NSEvent theEvent)
+ {
+ UpdatePath();
+ if (Element.Points.Any())
+ {
+ if (Element.DrawingCompletedCommand != null && Element.DrawingCompletedCommand.CanExecute(null))
+ {
+ Element.DrawingCompletedCommand.Execute(Element.Points);
+ }
+ }
+
+ if (Element.ClearOnFinish)
+ {
+ Element.Points.Clear();
+ }
+ }
+
+ public override void MouseDragged(NSEvent theEvent)
+ {
+ var currentPoint = theEvent.LocationInWindow;
+ AddPointToPath(currentPoint);
+ InvokeOnMainThread(Layer.SetNeedsDisplay);
+ }
+
+ public override void DrawRect(CGRect dirtyRect)
+ {
+ base.DrawRect(dirtyRect);
+ lineColor.SetStroke();
+ currentPath.Stroke();
+ }
+
+ void AddPointToPath(CGPoint currentPoint)
+ {
+ currentPath.LineTo(currentPoint);
+ Element.Points.Add(currentPoint.ToPoint());
+ }
+
+ void LoadPoints()
+ {
+ var stylusPoints = Element.Points.Select(point => new CGPoint(point.X, point.Y)).ToList();
+ currentPath.RemoveAllPoints();
+ if (stylusPoints.Any())
+ {
+ previousPoint = stylusPoints[0];
+ currentPath.MoveTo(previousPoint);
+ foreach (var point in stylusPoints)
+ {
+ AddPointToPath(point);
+ }
+
+ UpdatePath();
+ }
+ }
+
+ void UpdatePath()
+ {
+ var smoothedPoints = Element.EnableSmoothedPath
+ ? SmoothedPathWithGranularity(Element.Points, Element.Granularity, ref currentPath)
+ : new ObservableCollection(Element.Points);
+ InvokeOnMainThread(Layer.SetNeedsDisplay);
+ Element.Points.Clear();
+ foreach (var point in smoothedPoints)
+ {
+ Element.Points.Add(point);
+ }
+ }
+
+ ObservableCollection SmoothedPathWithGranularity(ObservableCollection currentPoints,
+ int granularity,
+ ref NSBezierPath smoothedPath)
+ {
+ // not enough points to smooth effectively, so return the original path and points.
+ if (currentPoints.Count < 4)
+ {
+ return new ObservableCollection(currentPoints);
+ }
+
+ // create a new bezier path to hold the smoothed path.
+ smoothedPath.RemoveAllPoints();
+ var smoothedPoints = new ObservableCollection();
+
+ // duplicate the first and last points as control points.
+ currentPoints.Insert(0, currentPoints[0]);
+ currentPoints.Add(currentPoints[^1]);
+
+ // add the first point
+ smoothedPath.MoveTo(currentPoints[0].X, currentPoints[0].Y);
+ smoothedPoints.Add(currentPoints[0]);
+
+ var currentPointsCount = currentPoints.Count;
+ for (var index = 1; index < currentPointsCount - 2; index++)
+ {
+ var p0 = currentPoints[index - 1];
+ var p1 = currentPoints[index];
+ var p2 = currentPoints[index + 1];
+ var p3 = currentPoints[index + 2];
+
+ // add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines
+ for (var i = 1; i < granularity; i++)
+ {
+ var t = i * (1f / granularity);
+ var tt = t * t;
+ var ttt = tt * t;
+
+ // intermediate point
+ var mid = GetIntermediatePoint(p0, p1, p2, p3, t, tt, ttt);
+ smoothedPath.LineTo(mid.X, mid.Y);
+ smoothedPoints.Add(mid);
+ }
+
+ // add p2
+ smoothedPath.LineTo(p2.X, p2.Y);
+ smoothedPoints.Add(p2);
+ }
+
+ // add the last point
+ var last = currentPoints[^1];
+ smoothedPath.LineTo(last.X, last.Y);
+ smoothedPoints.Add(last);
+ return smoothedPoints;
+ }
+
+ Point GetIntermediatePoint(Point p0, Point p1, Point p2, Point p3, in float t, in float tt, in float ttt) =>
+ new Point
+ {
+ X = 0.5f *
+ ((2f * p1.X) +
+ ((p2.X - p0.X) * t) +
+ (((((2f * p0.X) - (5f * p1.X)) + (4f * p2.X)) - p3.X) * tt) +
+ ((((3f * p1.X) - p0.X - (3f * p2.X)) + p3.X) * ttt)),
+ Y = 0.5f *
+ ((2 * p1.Y) +
+ ((p2.Y - p0.Y) * t) +
+ (((((2 * p0.Y) - (5 * p1.Y)) + (4 * p2.Y)) - p3.Y) * tt) +
+ ((((3 * p1.Y) - p0.Y - (3 * p2.Y)) + p3.Y) * ttt))
+ };
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ currentPath.Dispose();
+ }
+
+ disposed = true;
+
+ base.Dispose(disposing);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.tizen.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.tizen.cs
new file mode 100644
index 000000000..b0aaa0b78
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.tizen.cs
@@ -0,0 +1,152 @@
+using System.ComponentModel;
+using System.Linq;
+using ElmSharp;
+using SkiaSharp;
+using SkiaSharp.Views.Tizen;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+using Point = Xamarin.Forms.Point;
+
+[assembly: ExportRenderer(typeof(DrawingView), typeof(DrawingViewRenderer))]
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ public class DrawingViewRenderer : ViewRenderer
+ {
+ SKCanvasView canvasView;
+ bool isDrawing;
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == DrawingView.PointsProperty.PropertyName)
+ {
+ canvasView.EvasCanvas.DeleteEventAction(EvasObjectCallbackType.MouseUp, MouseUp);
+ LoadPoints();
+ canvasView.EvasCanvas.AddEventAction(EvasObjectCallbackType.MouseUp, MouseUp);
+ }
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+ if (Control == null && Element != null)
+ {
+ canvasView = new SKCanvasView(Forms.Forms.NativeParent)
+ {
+ BackgroundColor = Element.BackgroundColor.ToNative()
+ };
+ canvasView.Show();
+ Element.Points.CollectionChanged += (sender, args) =>
+ {
+ LoadPoints();
+ };
+ SetNativeControl(canvasView);
+ }
+
+ if (e.OldElement != null)
+ {
+ canvasView.EvasCanvas.DeleteEventAction(EvasObjectCallbackType.MouseDown, MouseDown);
+ canvasView.EvasCanvas.DeleteEventAction(EvasObjectCallbackType.MouseUp, MouseUp);
+ canvasView.EvasCanvas.DeleteEventAction(EvasObjectCallbackType.MouseMove, MouseMove);
+ canvasView.PaintSurface -= OnPaintSurface;
+ }
+
+ if (e.NewElement != null)
+ {
+ canvasView.PaintSurface += OnPaintSurface;
+ canvasView.EvasCanvas.AddEventAction(EvasObjectCallbackType.MouseDown, MouseDown);
+ canvasView.EvasCanvas.AddEventAction(EvasObjectCallbackType.MouseUp, MouseUp);
+ canvasView.EvasCanvas.AddEventAction(EvasObjectCallbackType.MouseMove, MouseMove);
+ }
+ }
+
+ void MouseMove()
+ {
+ if (isDrawing)
+ {
+ var point = canvasView.EvasCanvas.Pointer;
+ Element.Points.Add(new Point(point.X, point.Y));
+ canvasView.Invalidate();
+ }
+ }
+
+ void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) => DrawPath(e.Surface.Canvas);
+
+ void MouseDown()
+ {
+ if (Element == null)
+ {
+ return;
+ }
+
+ Element.Points.Clear();
+ canvasView?.Invalidate();
+ isDrawing = true;
+ }
+
+ void MouseUp()
+ {
+ if (Element == null)
+ {
+ return;
+ }
+
+ isDrawing = false;
+
+ if (Element.Points.Any())
+ {
+ if (Element.DrawingCompletedCommand != null && Element.DrawingCompletedCommand.CanExecute(null))
+ {
+ Element.DrawingCompletedCommand.Execute(Element.Points);
+ }
+ }
+
+ if (Element.ClearOnFinish)
+ {
+ Element.Points.Clear();
+ }
+ }
+
+ void LoadPoints()
+ {
+ if (Element == null)
+ {
+ return;
+ }
+
+ canvasView.Invalidate();
+ }
+
+ void DrawPath(SKCanvas canvas)
+ {
+ canvas.Clear(SKColor.Empty);
+ if (Element.Points.Count == 0)
+ {
+ return;
+ }
+
+ var strokePaint = new SKPaint
+ {
+ Style = SKPaintStyle.Stroke,
+ Color = Element.LineColor.ToNative().ToSKColor(),
+ StrokeWidth = Element.LineWidth,
+ IsAntialias = true
+ };
+
+ var skPoints = Element.Points.Select(p => new SKPoint((float)p.X, (float)p.Y)).ToArray();
+ var path = new SKPath();
+ path.MoveTo(skPoints[0]);
+
+ foreach (var point in skPoints)
+ {
+ path.LineTo(point);
+ }
+
+ canvas.DrawPath(path, strokePaint);
+ path.Dispose();
+ strokePaint.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.uwp.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.uwp.cs
new file mode 100644
index 000000000..2e19dfc9a
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.uwp.cs
@@ -0,0 +1,115 @@
+using System.ComponentModel;
+using System.Linq;
+using Windows.UI.Core;
+using Windows.UI.Input.Inking;
+using Windows.UI.Xaml.Controls;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.UWP;
+using Size = Windows.Foundation.Size;
+
+[assembly: ExportRenderer(typeof(DrawingView), typeof(DrawingViewRenderer))]
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ public class DrawingViewRenderer : ViewRenderer
+ {
+ InkCanvas canvas;
+ InkDrawingAttributes inkDrawingAttributes;
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == DrawingView.PointsProperty.PropertyName)
+ {
+ canvas.InkPresenter.StrokesCollected -= InkPresenter_StrokesCollected;
+ canvas.InkPresenter.StrokeContainer.Clear();
+ LoadPoints();
+ canvas.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected;
+ }
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+ if (Control == null && Element != null)
+ {
+ canvas = new InkCanvas();
+ inkDrawingAttributes = new InkDrawingAttributes
+ {
+ Color = Element.LineColor.ToWindowsColor(),
+ Size = new Size(Element.LineWidth, Element.LineWidth)
+ };
+ canvas.InkPresenter.UpdateDefaultDrawingAttributes(inkDrawingAttributes);
+ canvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse |
+ CoreInputDeviceTypes.Pen |
+ CoreInputDeviceTypes.Touch;
+ Element.Points.CollectionChanged += (sender, args) =>
+ {
+ LoadPoints();
+ };
+ SetNativeControl(canvas);
+ }
+
+ if (e.OldElement != null)
+ {
+ // Unsubscribe
+ canvas.InkPresenter.StrokeInput.StrokeStarted -= StrokeInput_StrokeStarted;
+ canvas.InkPresenter.StrokesCollected -= InkPresenter_StrokesCollected;
+ }
+
+ if (e.NewElement != null)
+ {
+ // Subscribe
+ canvas.InkPresenter.StrokeInput.StrokeStarted += StrokeInput_StrokeStarted;
+ canvas.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected;
+ }
+ }
+
+ void InkPresenter_StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args)
+ {
+ var points = args.Strokes.First()
+ .GetInkPoints()
+ .Select(point => new Point(point.Position.X, point.Position.Y))
+ .ToList();
+
+ Element.Points.Clear();
+ foreach (var point in points)
+ {
+ Element.Points.Add(point);
+ }
+
+ if (Element.Points.Any())
+ {
+ if (Element.DrawingCompletedCommand != null && Element.DrawingCompletedCommand.CanExecute(null))
+ {
+ Element.DrawingCompletedCommand.Execute(Element.Points);
+ }
+ }
+
+ if (Element.ClearOnFinish)
+ {
+ Element.Points.Clear();
+ }
+ }
+
+ void StrokeInput_StrokeStarted(InkStrokeInput sender, PointerEventArgs args)
+ {
+ canvas.InkPresenter.StrokeContainer.Clear();
+ Element.Points.Clear();
+ }
+
+ void LoadPoints()
+ {
+ var stylusPoints = Element?.Points?.Select(point => new Windows.Foundation.Point(point.X, point.Y))
+ .ToList();
+ if (stylusPoints != null && stylusPoints.Any())
+ {
+ var strokeBuilder = new InkStrokeBuilder();
+ strokeBuilder.SetDefaultDrawingAttributes(inkDrawingAttributes);
+ var stroke = strokeBuilder.CreateStroke(stylusPoints);
+ canvas.InkPresenter.StrokeContainer.AddStroke(stroke);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.wpf.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.wpf.cs
new file mode 100644
index 000000000..551e95b81
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Renderer/DrawingViewRenderer.wpf.cs
@@ -0,0 +1,115 @@
+using System.ComponentModel;
+using System.Linq;
+using System.Windows.Controls;
+using System.Windows.Ink;
+using System.Windows.Input;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.WPF;
+
+[assembly: ExportRenderer(typeof(DrawingView), typeof(DrawingViewRenderer))]
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ public class DrawingViewRenderer : ViewRenderer
+ {
+ InkCanvas canvas;
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == DrawingView.PointsProperty.PropertyName)
+ {
+ canvas.Strokes.StrokesChanged -= Strokes_StrokesChanged;
+ canvas.Strokes.Clear();
+ LoadPoints();
+ canvas.Strokes.StrokesChanged += Strokes_StrokesChanged;
+ }
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+ if (Control == null && Element != null)
+ {
+ canvas = new InkCanvas
+ {
+ DefaultDrawingAttributes =
+ {
+ Color = Element.LineColor.ToMediaColor(),
+ Width = Element.LineWidth,
+ Height = Element.LineWidth
+ },
+ Background = Element.BackgroundColor.ToBrush()
+ };
+ Element.Points.CollectionChanged += (sender, args) =>
+ {
+ LoadPoints();
+ };
+ SetNativeControl(canvas);
+ }
+
+ if (e.OldElement != null)
+ {
+ // Unsubscribe
+ canvas.Strokes.StrokesChanged -= Strokes_StrokesChanged;
+ if (Control != null)
+ {
+ Control.PreviewMouseDown -= Control_PreviewMouseDown;
+ }
+ }
+
+ if (e.NewElement != null)
+ {
+ // Subscribe
+ canvas.Strokes.StrokesChanged += Strokes_StrokesChanged;
+ if (Control != null)
+ {
+ Control.PreviewMouseDown += Control_PreviewMouseDown;
+ }
+ }
+ }
+
+ void Control_PreviewMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ canvas.Strokes.Clear();
+ Element.Points.Clear();
+ }
+
+ void Strokes_StrokesChanged(object sender, StrokeCollectionChangedEventArgs e)
+ {
+ if (e.Added.Any())
+ {
+ var points = e.Added.First().StylusPoints.Select(point => new Point(point.X, point.Y)).ToList();
+ Element.Points.Clear();
+ foreach (var point in points)
+ {
+ Element.Points.Add(point);
+ }
+
+ if (Element.Points.Any())
+ {
+ if (Element.DrawingCompletedCommand != null && Element.DrawingCompletedCommand.CanExecute(null))
+ {
+ Element.DrawingCompletedCommand.Execute(Element.Points);
+ }
+ }
+
+ if (Element.ClearOnFinish)
+ {
+ Element.Points.Clear();
+ }
+ }
+ }
+
+ void LoadPoints()
+ {
+ var stylusPoints = Element?.Points?.Select(point => new StylusPoint(point.X, point.Y)).ToList();
+ if (stylusPoints != null && stylusPoints.Any())
+ {
+ var stroke = new Stroke(new StylusPointCollection(stylusPoints), canvas.DefaultDrawingAttributes);
+ canvas.Strokes.Add(stroke);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.android.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.android.cs
new file mode 100644
index 000000000..e574bd35e
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.android.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Android.Graphics;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
+using Color = Xamarin.Forms.Color;
+using Point = Xamarin.Forms.Point;
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ static class DrawingViewService
+ {
+ public static Stream GetImageStream(IList points,
+ Size imageSize,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ if (points == null || points.Count < 2)
+ {
+ return Stream.Null;
+ }
+
+ var image = GetImageInternal(points, lineWidth, strokeColor, backgroundColor);
+ if (image == null)
+ {
+ return Stream.Null;
+ }
+
+ var resizedImage = MaxResizeImage(image, (float)imageSize.Width, (float)imageSize.Height);
+ using (resizedImage)
+ {
+ var stream = new MemoryStream();
+ var compressResult = resizedImage.Compress(Bitmap.CompressFormat.Jpeg, 100, stream);
+
+ resizedImage.Recycle();
+
+ if (!compressResult)
+ {
+ return null;
+ }
+
+ stream.Position = 0;
+ return stream;
+ }
+ }
+
+ static Bitmap GetImageInternal(IList points,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ var minPointX = points.Min(p => p.X);
+ var minPointY = points.Min(p => p.Y);
+ var drawingWidth = points.Max(p => p.X) - minPointX;
+ var drawingHeight = points.Max(p => p.Y) - minPointY;
+ const int minSize = 1;
+ if (drawingWidth < minSize || drawingHeight < minSize)
+ {
+ return null;
+ }
+
+ var image = Bitmap.CreateBitmap((int)drawingWidth, (int)drawingHeight, Bitmap.Config.Argb8888);
+ using (var canvas = new Canvas(image))
+ {
+ // background
+ canvas.DrawColor(backgroundColor.ToAndroid());
+
+ // strokes
+ using (var paint = new Paint())
+ {
+ paint.Color = strokeColor.ToAndroid();
+ paint.StrokeWidth = lineWidth;
+ paint.StrokeJoin = Paint.Join.Round;
+ paint.StrokeCap = Paint.Cap.Round;
+ paint.AntiAlias = true;
+ paint.SetStyle(Paint.Style.Stroke);
+
+ var pointsCount = points.Count;
+ for (var i = 0; i < pointsCount - 1; i++)
+ {
+ var p1 = points.ElementAt(i);
+ var p2 = points.ElementAt(i + 1);
+ canvas.DrawLine((float)(p1.X - minPointX), (float)(p1.Y - minPointY), (float)(p2.X - minPointX),
+ (float)(p2.Y - minPointY), paint);
+ }
+ }
+ }
+
+ return image;
+ }
+
+ static Bitmap MaxResizeImage(Bitmap sourceImage, float maxWidth, float maxHeight)
+ {
+ var sourceSize = new Size(sourceImage.Width, sourceImage.Height);
+ var maxResizeFactor = Math.Max(maxWidth / sourceSize.Width, maxHeight / sourceSize.Height);
+ if (maxResizeFactor > 1)
+ {
+ return sourceImage;
+ }
+
+ var width = maxResizeFactor * sourceSize.Width;
+ var height = maxResizeFactor * sourceSize.Height;
+ return Bitmap.CreateScaledBitmap(sourceImage, (int)width, (int)height, false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.gtk.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.gtk.cs
new file mode 100644
index 000000000..c48cbb8be
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.gtk.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using Xamarin.Forms.Platform.GTK.Extensions;
+using Color = Xamarin.Forms.Color;
+using Point = Xamarin.Forms.Point;
+using Size = Xamarin.Forms.Size;
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ static class DrawingViewService
+ {
+ public static Stream GetImageStream(IList points,
+ Size imageSize,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ if (points == null || points.Count < 2)
+ {
+ return Stream.Null;
+ }
+
+ var image = GetImageInternal(points, lineWidth, strokeColor, backgroundColor);
+ if (image == null)
+ {
+ return Stream.Null;
+ }
+
+ var resizedImage = MaxResizeImage(image, (float)imageSize.Width, (float)imageSize.Height);
+ using (resizedImage)
+ {
+ var stream = new MemoryStream();
+ resizedImage.Save(stream, ImageFormat.Jpeg);
+ stream.Position = 0;
+ return stream;
+ }
+ }
+
+ static Bitmap GetImageInternal(IList points,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ var minPointX = points.Min(p => p.X);
+ var minPointY = points.Min(p => p.Y);
+ var drawingWidth = points.Max(p => p.X) - minPointX;
+ var drawingHeight = points.Max(p => p.Y) - minPointY;
+ const int minSize = 1;
+ if (drawingWidth < minSize || drawingHeight < minSize)
+ {
+ return null;
+ }
+
+ var bm = new Bitmap((int)drawingWidth, (int)drawingHeight);
+ using (var gr = Graphics.FromImage(bm))
+ {
+ var drawingBackgroundColor = XamarinColorToDrawingColor(backgroundColor);
+ gr.Clear(drawingBackgroundColor);
+ using (var pen = new Pen(XamarinColorToDrawingColor(strokeColor), lineWidth))
+ {
+ var path = new GraphicsPath();
+ var pointsCount = points.Count;
+ for (var i = 0; i < pointsCount - 1; i++)
+ {
+ var p1 = XamarinPointToDrawingPoint(points.ElementAt(i));
+ var p2 = XamarinPointToDrawingPoint(points.ElementAt(i + 1));
+ path.AddLine(p1.X - (float)minPointX, p1.Y - (float)minPointY, p2.X - (float)minPointX,
+ p2.Y - (float)minPointY);
+ }
+
+ gr.DrawPath(pen, path);
+ }
+ }
+
+ return bm;
+ }
+
+ static System.Drawing.Color XamarinColorToDrawingColor(Color xamarinColor)
+ {
+ var mediaColor = xamarinColor.ToGtkColor();
+
+ return System.Drawing.Color.FromArgb(GetColorInByte(mediaColor.Red), GetColorInByte(mediaColor.Green),
+ GetColorInByte(mediaColor.Blue));
+
+ static byte GetColorInByte(ushort color)
+ {
+ return Convert.ToByte((color / 65535.0) * 255);
+ }
+ }
+
+ static PointF XamarinPointToDrawingPoint(Point xamarinPoint) => new PointF((float)xamarinPoint.X, (float)xamarinPoint.Y);
+
+ static Image MaxResizeImage(Image sourceImage, float maxWidth, float maxHeight)
+ {
+ var sourceSize = sourceImage.Size;
+ var maxResizeFactor = Math.Max(maxWidth / sourceSize.Width, maxHeight / sourceSize.Height);
+ if (maxResizeFactor > 1)
+ {
+ return sourceImage;
+ }
+
+ var width = maxResizeFactor * sourceSize.Width;
+ var height = maxResizeFactor * sourceSize.Height;
+ var bm = new Bitmap((int)width, (int)height);
+ using (var gr = Graphics.FromImage(bm))
+ {
+ gr.DrawImage(sourceImage, new Rectangle(0, 0, bm.Width, bm.Height));
+ }
+
+ return bm;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.ios.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.ios.cs
new file mode 100644
index 000000000..a1e6359af
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.ios.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using CoreGraphics;
+using UIKit;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.iOS;
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ static class DrawingViewService
+ {
+ public static Stream GetImageStream(IList points,
+ Size imageSize,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ if (points == null || points.Count < 2)
+ {
+ return Stream.Null;
+ }
+
+ var image = GetImageInternal(points, lineWidth, strokeColor, backgroundColor);
+ if (image == null)
+ {
+ return Stream.Null;
+ }
+
+ var resizedImage = MaxResizeImage(image, (float)imageSize.Width, (float)imageSize.Height);
+ return resizedImage.AsJPEG().AsStream();
+ }
+
+ static UIImage GetImageInternal(IList points,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ var minPointX = points.Min(p => p.X);
+ var minPointY = points.Min(p => p.Y);
+ var drawingWidth = points.Max(p => p.X) - minPointX;
+ var drawingHeight = points.Max(p => p.Y) - minPointY;
+ const int minSize = 1;
+ if (drawingWidth < minSize || drawingHeight < minSize)
+ {
+ return null;
+ }
+
+ var imageSize = new CGSize(drawingWidth, drawingHeight);
+ UIGraphics.BeginImageContextWithOptions(imageSize, false, 1);
+
+ var context = UIGraphics.GetCurrentContext();
+ if (context == null)
+ {
+ return null;
+ }
+
+ context.SetFillColor(backgroundColor.ToCGColor());
+ context.FillRect(new CGRect(CGPoint.Empty, imageSize));
+
+ context.SetStrokeColor(strokeColor.ToCGColor());
+ context.SetLineWidth(lineWidth);
+ context.SetLineCap(CGLineCap.Round);
+ context.SetLineJoin(CGLineJoin.Round);
+
+ context.AddLines(points.Select(p => new CGPoint(p.X - minPointX, p.Y - minPointY)).ToArray());
+ context.StrokePath();
+
+ var image = UIGraphics.GetImageFromCurrentImageContext();
+ UIGraphics.EndImageContext();
+ return image;
+ }
+
+ static UIImage MaxResizeImage(UIImage sourceImage, float maxWidth, float maxHeight)
+ {
+ var sourceSize = sourceImage.Size;
+ var maxResizeFactor = Math.Max(maxWidth / sourceSize.Width, maxHeight / sourceSize.Height);
+ if (maxResizeFactor > 1)
+ {
+ return sourceImage;
+ }
+
+ var width = maxResizeFactor * sourceSize.Width;
+ var height = maxResizeFactor * sourceSize.Height;
+ UIGraphics.BeginImageContext(new CGSize(width, height));
+ sourceImage.Draw(new CGRect(0, 0, width, height));
+ var resultImage = UIGraphics.GetImageFromCurrentImageContext();
+ UIGraphics.EndImageContext();
+ return resultImage;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.macos.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.macos.cs
new file mode 100644
index 000000000..288b24e23
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.macos.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using AppKit;
+using CoreGraphics;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.MacOS;
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ static class DrawingViewService
+ {
+ public static Stream GetImageStream(IList points,
+ Size imageSize,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ if (points == null || points.Count < 2)
+ {
+ return Stream.Null;
+ }
+
+ var image = GetImageInternal(points, lineWidth, strokeColor, backgroundColor);
+ return image == null ? Stream.Null : image.AsTiff().AsStream();
+ }
+
+ static NSImage GetImageInternal(IList points,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ var minPointX = points.Min(p => p.X);
+ var minPointY = points.Min(p => p.Y);
+ var drawingWidth = points.Max(p => p.X) - minPointX;
+ var drawingHeight = points.Max(p => p.Y) - minPointY;
+ const int minSize = 1;
+ if (drawingWidth < minSize || drawingHeight < minSize)
+ {
+ return null;
+ }
+
+ var imageSize = new CGSize(drawingWidth, drawingHeight);
+
+ NSImage image;
+ using (var context = new CGBitmapContext(IntPtr.Zero, (nint)drawingWidth, (nint)drawingHeight, 8,
+ (nint)drawingWidth * 4,
+ NSColorSpace.GenericRGBColorSpace.ColorSpace,
+ CGImageAlphaInfo.PremultipliedFirst))
+ {
+ context.SetFillColor(backgroundColor.ToCGColor());
+ context.FillRect(new CGRect(CGPoint.Empty, imageSize));
+
+ context.SetStrokeColor(strokeColor.ToCGColor());
+ context.SetLineWidth(lineWidth);
+ context.SetLineCap(CGLineCap.Round);
+ context.SetLineJoin(CGLineJoin.Round);
+
+ context.AddLines(points.Select(p => new CGPoint(p.X - minPointX, p.Y - minPointY)).ToArray());
+ context.StrokePath();
+ using (var cgImage = context.ToImage())
+ {
+ image = new NSImage(cgImage, imageSize);
+ }
+ }
+
+ return image;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.shared.cs
new file mode 100644
index 000000000..9c43c8c8a
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.shared.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using System.IO;
+using Color = Xamarin.Forms.Color;
+using Point = Xamarin.Forms.Point;
+using Size = Xamarin.Forms.Size;
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+#if NETSTANDARD || __WATCHOS__ || __TVOS__
+ static class DrawingViewService
+ {
+ public static Stream GetImageStream(IList points,
+ Size imageSize,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor) =>
+ Stream.Null;
+ }
+#endif
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.tizen.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.tizen.cs
new file mode 100644
index 000000000..d45ece565
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.tizen.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using SkiaSharp;
+using SkiaSharp.Views.Tizen;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Tizen;
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ static class DrawingViewService
+ {
+ public static Stream GetImageStream(IList points,
+ Size imageSize,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ if (points == null || points.Count < 2)
+ {
+ return Stream.Null;
+ }
+
+ var image = GetImageInternal(points, lineWidth, strokeColor, backgroundColor);
+ if (image == null)
+ {
+ return Stream.Null;
+ }
+
+ var resizedImage = MaxResizeImage(image, (float)imageSize.Width, (float)imageSize.Height);
+ using (resizedImage)
+ {
+ var stream = resizedImage.Encode(SKEncodedImageFormat.Jpeg, 100).AsStream();
+ stream.Position = 0;
+ return stream;
+ }
+ }
+
+ static SKImage GetImageInternal(IList points,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ var minPointX = points.Min(p => p.X);
+ var minPointY = points.Min(p => p.Y);
+ var drawingWidth = points.Max(p => p.X) - minPointX;
+ var drawingHeight = points.Max(p => p.Y) - minPointY;
+ const int minSize = 1;
+ if (drawingWidth < minSize || drawingHeight < minSize)
+ {
+ return null;
+ }
+
+ var bm = new SKBitmap((int)drawingWidth, (int)drawingHeight);
+ using (var gr = new SKCanvas(bm))
+ {
+ var drawingBackgroundColor = XamarinColorToDrawingColor(backgroundColor);
+ gr.Clear(drawingBackgroundColor);
+ using (var pen = new SKPaint
+ {
+ Color = XamarinColorToDrawingColor(strokeColor),
+ StrokeWidth = lineWidth
+ })
+ {
+ var pointsCount = points.Count;
+ for (var i = 0; i < pointsCount - 1; i++)
+ {
+ var p1 = XamarinPointToDrawingPoint(points.ElementAt(i));
+ var p2 = XamarinPointToDrawingPoint(points.ElementAt(i + 1));
+
+ gr.DrawLine(p1.X - (float)minPointX, p1.Y - (float)minPointY, p2.X - (float)minPointX,
+ p2.Y - (float)minPointY, pen);
+ }
+ }
+ }
+
+ return SKImage.FromBitmap(bm);
+ }
+
+ static SKColor XamarinColorToDrawingColor(Color xamarinColor) => xamarinColor.ToNative().ToSKColor();
+
+ static SKPoint XamarinPointToDrawingPoint(Point xamarinPoint) => new SKPoint((float)xamarinPoint.X, (float)xamarinPoint.Y);
+
+ static SKBitmap MaxResizeImage(SKImage sourceImage, float maxWidth, float maxHeight)
+ {
+ var sourceSize = new Size(sourceImage.Width, sourceImage.Height);
+ var maxResizeFactor = Math.Max(maxWidth / sourceSize.Width, maxHeight / sourceSize.Height);
+ if (maxResizeFactor > 1)
+ {
+ return SKBitmap.FromImage(sourceImage);
+ }
+
+ var width = maxResizeFactor * sourceSize.Width;
+ var height = maxResizeFactor * sourceSize.Height;
+ var bm = new SKBitmap((int)width, (int)height);
+ using (var gr = new SKCanvas(bm))
+ {
+ gr.DrawImage(sourceImage, new SKRect(0, 0, bm.Width, bm.Height));
+ }
+
+ return bm;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.uwp.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.uwp.cs
new file mode 100644
index 000000000..b5a74923f
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.uwp.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Windows.Storage.Streams;
+using Windows.UI.Input.Inking;
+using Microsoft.Graphics.Canvas;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.UWP;
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ static class DrawingViewService
+ {
+ public static Stream GetImageStream(IList points,
+ Size imageSize,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ if (points == null || points.Count < 2)
+ {
+ return Stream.Null;
+ }
+
+ var image = GetImageInternal(points, lineWidth, strokeColor, backgroundColor);
+ if (image == null)
+ {
+ return Stream.Null;
+ }
+
+ using (image)
+ {
+ var fileStream = new InMemoryRandomAccessStream();
+ image.SaveAsync(fileStream, CanvasBitmapFileFormat.Jpeg).GetAwaiter().GetResult();
+
+ var stream = fileStream.AsStream();
+ stream.Position = 0;
+
+ return stream;
+ }
+ }
+
+ static CanvasRenderTarget GetImageInternal(IList points,
+ float lineWidth,
+ Color lineColor,
+ Color backgroundColor)
+ {
+ var minPointX = points.Min(p => p.X);
+ var minPointY = points.Min(p => p.Y);
+ var drawingWidth = points.Max(p => p.X) - minPointX;
+ var drawingHeight = points.Max(p => p.Y) - minPointY;
+ const int minSize = 1;
+ if (drawingWidth < minSize || drawingHeight < minSize)
+ {
+ return null;
+ }
+
+ var device = CanvasDevice.GetSharedDevice();
+ var offscreen = new CanvasRenderTarget(device, (int)drawingWidth, (int)drawingHeight, 96);
+
+ using (var session = offscreen.CreateDrawingSession())
+ {
+ session.Clear(backgroundColor.ToWindowsColor());
+ var strokeBuilder = new InkStrokeBuilder();
+ var inkDrawingAttributes = new InkDrawingAttributes
+ {
+ Color = lineColor.ToWindowsColor(),
+ Size = new Windows.Foundation.Size(lineWidth, lineWidth)
+ };
+ strokeBuilder.SetDefaultDrawingAttributes(inkDrawingAttributes);
+ var strokes = new[]
+ {
+ strokeBuilder.CreateStroke(
+ points.Select(p => new Windows.Foundation.Point(p.X - minPointX, p.Y - minPointY)))
+ };
+ session.DrawInk(strokes);
+ }
+
+ return offscreen;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.wpf.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.wpf.cs
new file mode 100644
index 000000000..73cb38cac
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/DrawingView/Service/DrawingViewService.wpf.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using Xamarin.Forms.Platform.WPF;
+using Color = Xamarin.Forms.Color;
+using Point = Xamarin.Forms.Point;
+using Size = Xamarin.Forms.Size;
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ static class DrawingViewService
+ {
+ public static Stream GetImageStream(IList points,
+ Size imageSize,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ if (points == null || points.Count < 2)
+ {
+ return Stream.Null;
+ }
+
+ var image = GetImageInternal(points, lineWidth, strokeColor, backgroundColor);
+ if (image == null)
+ {
+ return Stream.Null;
+ }
+
+ var resizedImage = MaxResizeImage(image, (float)imageSize.Width, (float)imageSize.Height);
+ using (resizedImage)
+ {
+ var stream = new MemoryStream();
+ resizedImage.Save(stream, ImageFormat.Jpeg);
+ stream.Position = 0;
+ return stream;
+ }
+ }
+
+ static Bitmap GetImageInternal(IList points,
+ float lineWidth,
+ Color strokeColor,
+ Color backgroundColor)
+ {
+ var minPointX = points.Min(p => p.X);
+ var minPointY = points.Min(p => p.Y);
+ var drawingWidth = points.Max(p => p.X) - minPointX;
+ var drawingHeight = points.Max(p => p.Y) - minPointY;
+ const int minSize = 1;
+ if (drawingWidth < minSize || drawingHeight < minSize)
+ {
+ return null;
+ }
+
+ var bm = new Bitmap((int)drawingWidth, (int)drawingHeight);
+ using (var gr = Graphics.FromImage(bm))
+ {
+ var drawingBackgroundColor = XamarinColorToDrawingColor(backgroundColor);
+ gr.Clear(drawingBackgroundColor);
+ using (var pen = new Pen(XamarinColorToDrawingColor(strokeColor), lineWidth))
+ {
+ var path = new GraphicsPath();
+ var pointsCount = points.Count;
+ for (var i = 0; i < pointsCount - 1; i++)
+ {
+ var p1 = XamarinPointToDrawingPoint(points.ElementAt(i));
+ var p2 = XamarinPointToDrawingPoint(points.ElementAt(i + 1));
+ path.AddLine(p1.X - (float)minPointX, p1.Y - (float)minPointY, p2.X - (float)minPointX,
+ p2.Y - (float)minPointY);
+ }
+
+ gr.DrawPath(pen, path);
+ }
+ }
+
+ return bm;
+ }
+
+ static System.Drawing.Color XamarinColorToDrawingColor(Color xamarinColor)
+ {
+ var mediaColor = xamarinColor.ToMediaColor();
+ return System.Drawing.Color.FromArgb(mediaColor.A, mediaColor.R, mediaColor.G, mediaColor.B);
+ }
+
+ static PointF XamarinPointToDrawingPoint(Point xamarinPoint) => new PointF((float)xamarinPoint.X, (float)xamarinPoint.Y);
+
+ static Image MaxResizeImage(Image sourceImage, float maxWidth, float maxHeight)
+ {
+ var sourceSize = sourceImage.Size;
+ var maxResizeFactor = Math.Max(maxWidth / sourceSize.Width, maxHeight / sourceSize.Height);
+ if (maxResizeFactor > 1)
+ {
+ return sourceImage;
+ }
+
+ var width = maxResizeFactor * sourceSize.Width;
+ var height = maxResizeFactor * sourceSize.Height;
+ var bm = new Bitmap((int)width, (int)height);
+ using (var gr = Graphics.FromImage(bm))
+ {
+ gr.DrawImage(sourceImage, new Rectangle(0, 0, bm.Width, bm.Height));
+ }
+
+ return bm;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Helpers/SnackBarLayout.uwp.wpf.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Helpers/SnackBarLayout.uwp.wpf.cs
index 15f603785..431dd0d6b 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Helpers/SnackBarLayout.uwp.wpf.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Helpers/SnackBarLayout.uwp.wpf.cs
@@ -1,7 +1,7 @@
using System;
using Xamarin.CommunityToolkit.UI.Views.Options;
using System.Linq;
-#if UWP
+#if UAP10_0
using Xamarin.Forms.Platform.UWP;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
@@ -21,7 +21,7 @@ public SnackBarLayout(SnackBarOptions options)
{
RowDefinitions.Add(new RowDefinition());
ColumnDefinitions.Add(new ColumnDefinition());
-#if UWP
+#if UAP10_0
Background = options.BackgroundColor.ToBrush();
var messageLabel = new TextBlock
{
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Options/ToastOptions.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Options/ToastOptions.shared.cs
index 2ecafb626..662c1fed4 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Options/ToastOptions.shared.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Options/ToastOptions.shared.cs
@@ -23,7 +23,7 @@ public class ToastOptions
///
public Color BackgroundColor { get; set; } = DefaultBackgroundColor;
- public static Color DefaultBackgroundColor { get; set; } = Color.Default;
+ public static Color DefaultBackgroundColor { get; set; } = Color.White;
///
/// Is Right to left
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Xamarin.CommunityToolkit.csproj b/src/CommunityToolkit/Xamarin.CommunityToolkit/Xamarin.CommunityToolkit.csproj
index 9edabee3c..743f4518c 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Xamarin.CommunityToolkit.csproj
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Xamarin.CommunityToolkit.csproj
@@ -57,7 +57,7 @@
true
- UWP
+ $(DefineConstants);UAP10_0
@@ -139,6 +139,7 @@
+
@@ -201,6 +202,9 @@
..\..\..\Libs\gtk-sharp\gtk-sharp-2.0\pango-sharp.dll
False
+
+ ..\..\..\Libs\gtk-sharp\Mono.Cairo\Mono.Cairo.dll
+