From e80192b4384409b563d949d28c48d71e0c78d761 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Wed, 9 Nov 2022 08:17:54 +0900 Subject: [PATCH] Implements tizen platform (#692) * Implements tizen platform * Apply review comment * Update src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.tizen.cs * Update src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.tizen.cs * Update src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.tizen.cs * Update src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.tizen.cs Co-authored-by: Pedro Jesus * Apply review comment * attempt to use 6.0.300 * 6.0.400 * 6.0.424 * 486 * 540 * Update azure-pipelines.yml * Formatting Updates * Add * Remove duplicate enums * Fix variable conflicts * Defer to thread pool thread * Fix MauiPopup anchor position * Apply review comment * Fix GetImageStream without await * Add UnsupportedOSPlatform attribute on unsupported behaviors Co-authored-by: Vladislav Antonyuk <33021114+VladislavAntonyuk@users.noreply.github.com> Co-authored-by: Pedro Jesus Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Co-authored-by: Jay Cho --- Directory.Build.props | 3 + azure-pipelines.yml | 16 +- .../CommunityToolkit.Maui.Sample.csproj | 2 + .../Platforms/Tizen/Main.cs | 16 ++ .../Platforms/Tizen/tizen-manifest.xml | 15 ++ .../Views/Popups/ButtonPopup.xaml | 2 +- .../Views/Popups/CsharpBindingPopup.xaml | 2 +- .../Views/Popups/MultipleButtonPopup.xaml | 2 +- .../Popups/NoOutsideTapDismissPopup.xaml | 2 +- .../Views/Popups/OpenedEventSimplePopup.xaml | 2 +- .../Views/Popups/ReturnResultPopup.xaml | 2 +- .../Views/Popups/SimplePopup.xaml | 2 +- .../Views/Popups/ToggleSizePopup.xaml | 2 +- .../Views/Popups/XamlBindingPopup.xaml | 2 +- .../CommunityToolkit.Maui.Core.csproj | 7 + .../DrawingView/DrawingViewHandler.shared.cs | 2 +- .../Handlers/Popup/PopupHandler.tizen.cs | 103 ++++++++ .../Platform/StatusBar/StatusBar.tizen.cs | 18 ++ .../PlatformView/MauiDrawingView.tizen.cs | 78 ++++++ .../Service/DrawingViewService.tizen.cs | 222 ++++++++++++++++++ .../Views/Popup/MauiPopup.tizen.cs | 138 +++++++++++ .../Alerts/Snackbar/Snackbar.tizen.cs | 181 ++++++++++++++ .../Alerts/Toast/Toast.tizen.cs | 39 +++ .../IconTintColorBehavior.shared.cs | 2 +- .../SelectAllTextBehavior.tizen.cs | 46 ++++ .../StatusBar/StatusBarBehavior.shared.cs | 4 +- .../CommunityToolkit.Maui.csproj | 7 + 27 files changed, 901 insertions(+), 16 deletions(-) create mode 100644 samples/CommunityToolkit.Maui.Sample/Platforms/Tizen/Main.cs create mode 100644 samples/CommunityToolkit.Maui.Sample/Platforms/Tizen/tizen-manifest.xml create mode 100644 src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.tizen.cs create mode 100644 src/CommunityToolkit.Maui.Core/Platform/StatusBar/StatusBar.tizen.cs create mode 100644 src/CommunityToolkit.Maui.Core/Views/DrawingView/PlatformView/MauiDrawingView.tizen.cs create mode 100644 src/CommunityToolkit.Maui.Core/Views/DrawingView/Service/DrawingViewService.tizen.cs create mode 100644 src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs create mode 100644 src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.tizen.cs create mode 100644 src/CommunityToolkit.Maui/Alerts/Toast/Toast.tizen.cs create mode 100644 src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/SelectAllText/SelectAllTextBehavior.tizen.cs diff --git a/Directory.Build.props b/Directory.Build.props index d437ceffb..3c381232f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,4 +20,7 @@ + + true + \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ddfad169d..ca7cc98ad 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,7 +3,7 @@ variables: PreviewNumber: $[counter(variables['CurrentSemanticVersionBase'], 1001)] CurrentSemanticVersion: '$(CurrentSemanticVersionBase)-preview$(PreviewNumber)' NugetPackageVersion: '$(CurrentSemanticVersion)' - TOOLKIT_NET_VERSION: '6.0.300' + TOOLKIT_NET_VERSION: '6.0.400' LATEST_NET_VERSION: '6.0.x' PathToLibrarySolution: 'src/CommunityToolkit.Maui.sln' PathToSamplesSolution: 'samples/CommunityToolkit.Maui.Sample.sln' @@ -15,7 +15,7 @@ variables: PathToCommunityToolkitSourceGeneratorsCsproj: 'src/CommunityToolkit.Maui.SourceGenerators/CommunityToolkit.Maui.SourceGenerators.csproj' PathToCommunityToolkitAnalyzersCodeFixCsproj: 'src/CommunityToolkit.Maui.Analyzers.CodeFixes/CommunityToolkit.Maui.Analyzers.CodeFixes.csproj' PathToCommunityToolkitAnalyzersUnitTestCsproj: 'src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj' - DotNetMauiRollbackFile: 'https://maui.blob.core.windows.net/metadata/rollbacks/6.0.312.json' + DotNetMauiRollbackFile: 'https://maui.blob.core.windows.net/metadata/rollbacks/6.0.540.json' ShouldCheckDependencies: true trigger: @@ -62,6 +62,11 @@ jobs: - powershell: dotnet workload install maui displayName: Install Latest .NET MAUI Workload + - pwsh: | + Invoke-WebRequest 'https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.ps1' -OutFile 'workload-install.ps1' + .\workload-install.ps1 -DotnetTargetVersionBand $(TOOLKIT_NET_VERSION) + displayName: Install Tizen Workload + # build sample - task: CmdLine@2 displayName: 'Build Community Toolkit Sample' @@ -112,6 +117,11 @@ jobs: inputs: script: dotnet workload install maui --from-rollback-file $(DotNetMauiRollbackFile) --source https://api.nuget.org/v3/index.json + - pwsh: | + Invoke-WebRequest 'https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.ps1' -OutFile 'workload-install.ps1' + .\workload-install.ps1 -DotnetTargetVersionBand $(TOOLKIT_NET_VERSION) + displayName: Install Tizen Workload + - task: CmdLine@2 displayName: 'Build CommunityToolkit.Maui.Analyzers' inputs: @@ -205,4 +215,4 @@ jobs: displayName: 'Publish NuGets' inputs: artifactName: nuget - pathToPublish: '$(Build.ArtifactStagingDirectory)' \ No newline at end of file + pathToPublish: '$(Build.ArtifactStagingDirectory)' diff --git a/samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.csproj b/samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.csproj index 810750301..a130b6c23 100644 --- a/samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.csproj @@ -3,6 +3,7 @@ net6.0-ios;net6.0-android;net6.0-maccatalyst $(TargetFrameworks);net6.0-windows10.0.19041.0 + $(TargetFrameworks);net6.0-tizen Exe true true @@ -50,6 +51,7 @@ 14.0 21.0 10.0.17763.0 + 6.5 10.0.17763.0 diff --git a/samples/CommunityToolkit.Maui.Sample/Platforms/Tizen/Main.cs b/samples/CommunityToolkit.Maui.Sample/Platforms/Tizen/Main.cs new file mode 100644 index 000000000..c3a1b02ba --- /dev/null +++ b/samples/CommunityToolkit.Maui.Sample/Platforms/Tizen/Main.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.Maui; +using Microsoft.Maui.Hosting; + +namespace CommunityToolkit.Maui.Sample; + +class Program : MauiApplication +{ + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + + static void Main(string[] args) + { + var app = new Program(); + app.Run(args); + } +} \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/Platforms/Tizen/tizen-manifest.xml b/samples/CommunityToolkit.Maui.Sample/Platforms/Tizen/tizen-manifest.xml new file mode 100644 index 000000000..2233229bd --- /dev/null +++ b/samples/CommunityToolkit.Maui.Sample/Platforms/Tizen/tizen-manifest.xml @@ -0,0 +1,15 @@ + + + + + + maui-appicon-placeholder + + + + + http://tizen.org/privilege/internet + + + + \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/Views/Popups/ButtonPopup.xaml b/samples/CommunityToolkit.Maui.Sample/Views/Popups/ButtonPopup.xaml index 5a5fa85cc..09436fa23 100644 --- a/samples/CommunityToolkit.Maui.Sample/Views/Popups/ButtonPopup.xaml +++ b/samples/CommunityToolkit.Maui.Sample/Views/Popups/ButtonPopup.xaml @@ -25,7 +25,7 @@ diff --git a/samples/CommunityToolkit.Maui.Sample/Views/Popups/MultipleButtonPopup.xaml b/samples/CommunityToolkit.Maui.Sample/Views/Popups/MultipleButtonPopup.xaml index 0e829d0bb..c6e746967 100644 --- a/samples/CommunityToolkit.Maui.Sample/Views/Popups/MultipleButtonPopup.xaml +++ b/samples/CommunityToolkit.Maui.Sample/Views/Popups/MultipleButtonPopup.xaml @@ -25,7 +25,7 @@ diff --git a/samples/CommunityToolkit.Maui.Sample/Views/Popups/OpenedEventSimplePopup.xaml b/samples/CommunityToolkit.Maui.Sample/Views/Popups/OpenedEventSimplePopup.xaml index d3db689ac..9f25f615f 100644 --- a/samples/CommunityToolkit.Maui.Sample/Views/Popups/OpenedEventSimplePopup.xaml +++ b/samples/CommunityToolkit.Maui.Sample/Views/Popups/OpenedEventSimplePopup.xaml @@ -24,7 +24,7 @@ diff --git a/samples/CommunityToolkit.Maui.Sample/Views/Popups/ReturnResultPopup.xaml b/samples/CommunityToolkit.Maui.Sample/Views/Popups/ReturnResultPopup.xaml index 6e4b86b06..f6acc30ba 100644 --- a/samples/CommunityToolkit.Maui.Sample/Views/Popups/ReturnResultPopup.xaml +++ b/samples/CommunityToolkit.Maui.Sample/Views/Popups/ReturnResultPopup.xaml @@ -24,7 +24,7 @@ diff --git a/samples/CommunityToolkit.Maui.Sample/Views/Popups/ToggleSizePopup.xaml b/samples/CommunityToolkit.Maui.Sample/Views/Popups/ToggleSizePopup.xaml index 43043d69d..fa0854036 100644 --- a/samples/CommunityToolkit.Maui.Sample/Views/Popups/ToggleSizePopup.xaml +++ b/samples/CommunityToolkit.Maui.Sample/Views/Popups/ToggleSizePopup.xaml @@ -26,7 +26,7 @@ diff --git a/src/CommunityToolkit.Maui.Core/CommunityToolkit.Maui.Core.csproj b/src/CommunityToolkit.Maui.Core/CommunityToolkit.Maui.Core.csproj index 65df674b9..789ca7edf 100644 --- a/src/CommunityToolkit.Maui.Core/CommunityToolkit.Maui.Core.csproj +++ b/src/CommunityToolkit.Maui.Core/CommunityToolkit.Maui.Core.csproj @@ -3,6 +3,7 @@ net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst $(TargetFrameworks);net6.0-windows10.0.19041.0 + $(TargetFrameworks);net6.0-tizen true true true @@ -73,6 +74,12 @@ + + + + + + diff --git a/src/CommunityToolkit.Maui.Core/Handlers/DrawingView/DrawingViewHandler.shared.cs b/src/CommunityToolkit.Maui.Core/Handlers/DrawingView/DrawingViewHandler.shared.cs index 3f72b3ad2..121ab2cac 100644 --- a/src/CommunityToolkit.Maui.Core/Handlers/DrawingView/DrawingViewHandler.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Handlers/DrawingView/DrawingViewHandler.shared.cs @@ -49,7 +49,7 @@ public DrawingViewHandler() : this(DrawingViewMapper, DrawingViewCommandMapper) } } -#if ANDROID || IOS || MACCATALYST || WINDOWS +#if ANDROID || IOS || MACCATALYST || WINDOWS || TIZEN public partial class DrawingViewHandler : ViewHandler, IDrawingViewHandler { diff --git a/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.tizen.cs b/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.tizen.cs new file mode 100644 index 000000000..174a0a44c --- /dev/null +++ b/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.tizen.cs @@ -0,0 +1,103 @@ +using CommunityToolkit.Maui.Core.Views; +using Tizen.UIExtensions.NUI; + +namespace CommunityToolkit.Maui.Core.Handlers; + +public partial class PopupHandler : Microsoft.Maui.Handlers.ElementHandler +{ + /// + /// Action that's triggered when the Popup is closed. + /// + /// An instance of . + /// An instance of . + /// The result that should return from this Popup. + public static void MapOnClosed(PopupHandler handler, IPopup view, object? result) + { + var popup = handler.PlatformView; + + if (popup.IsOpen) + { + popup.Close(); + } + + handler.DisconnectHandler(popup); + } + + /// + /// Action that's triggered when the Popup is Opened. + /// + /// An instance of . + /// An instance of . + /// We don't need to provide the result parameter here. + public static void MapOnOpened(PopupHandler handler, IPopup view, object? result) + { + handler.PlatformView.ShowPopup(); + } + + /// + /// Action that's triggered when the Popup is dismissed by tapping outside of the Popup. + /// + /// An instance of . + /// An instance of . + /// The result that should return from this Popup. + public static void MapOnDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view, object? result) + { + if (view.CanBeDismissedByTappingOutsideOfPopup) + { + view.OnDismissedByTappingOutsideOfPopup(); + } + } + + /// + /// Action that's triggered when the Popup property changes. + /// + /// An instance of . + /// An instance of . + public static void MapAnchor(PopupHandler handler, IPopup view) + { + // On Tizen, Anchor only update when popup is opened + } + + /// + /// Action that's triggered when the Popup property changes. + /// + /// An instance of . + /// An instance of . + public static void MapCanBeDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view) + { + // this property directly access on platform view + } + + /// + /// Action that's triggered when the Popup property changes. + /// + /// An instance of . + /// An instance of . + public static void MapColor(PopupHandler handler, IPopup view) + { + // this property directly access on platform view + } + + /// + /// Action that's triggered when the Popup property changes. + /// + /// An instance of . + /// An instance of . + public static void MapSize(PopupHandler handler, IPopup view) + { + handler.PlatformView.UpdateContentSize(); + } + + /// + protected override void ConnectHandler(MauiPopup platformView) + { + platformView.SetElement(VirtualView); + } + + /// + protected override MauiPopup CreatePlatformElement() + { + var mauiContext = MauiContext ?? throw new InvalidOperationException("${nameof(MauiContext)} cannot be null"); + return new MauiPopup(mauiContext); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Platform/StatusBar/StatusBar.tizen.cs b/src/CommunityToolkit.Maui.Core/Platform/StatusBar/StatusBar.tizen.cs new file mode 100644 index 000000000..86c85aeaf --- /dev/null +++ b/src/CommunityToolkit.Maui.Core/Platform/StatusBar/StatusBar.tizen.cs @@ -0,0 +1,18 @@ +using System.Runtime.Versioning; +using Microsoft.Maui.Platform; + +namespace CommunityToolkit.Maui.Core.Platform; + +[UnsupportedOSPlatform("Tizen")] +static partial class StatusBar +{ + static void PlatformSetColor(Color color) + { + throw new NotSupportedException("Tizen does not currently support changing the status bar color"); + } + + static void PlatformSetStyle(StatusBarStyle style) + { + throw new NotSupportedException("Tizen does not currently support changing the status bar color"); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Views/DrawingView/PlatformView/MauiDrawingView.tizen.cs b/src/CommunityToolkit.Maui.Core/Views/DrawingView/PlatformView/MauiDrawingView.tizen.cs new file mode 100644 index 000000000..772c45099 --- /dev/null +++ b/src/CommunityToolkit.Maui.Core/Views/DrawingView/PlatformView/MauiDrawingView.tizen.cs @@ -0,0 +1,78 @@ +using Microsoft.Maui.Platform; +using NPointStateType = Tizen.NUI.PointStateType; + +namespace CommunityToolkit.Maui.Core.Views; + +public partial class MauiDrawingView : PlatformTouchGraphicsView +{ + /// + /// Initialize a new instance of . + /// + public MauiDrawingView() : base(null) + { + previousPoint = new(); + TouchEvent += OnTouch; + } + + /// + /// Initialize resources + /// + public void Initialize() + { + Drawable = new DrawingViewDrawable(this); + Lines.CollectionChanged += OnLinesCollectionChanged; + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + currentPath.Dispose(); + TouchEvent -= OnTouch; + } + + base.Dispose(disposing); + } + + bool OnTouch(object source, TouchEventArgs e) + { + var point = new PointF(e.Touch.GetLocalPosition(0).X.ToScaledDP(), e.Touch.GetLocalPosition(0).Y.ToScaledDP()); + var pointStateType = e.Touch.GetState(0); + + switch (pointStateType) + { + case NPointStateType.Leave: + case NPointStateType.Stationary: + break; + + case NPointStateType.Down: + OnStart(point); + break; + + case NPointStateType.Motion: + OnMoving(point); + break; + + case NPointStateType.Up: + OnFinish(); + break; + + case NPointStateType.Interrupted: + OnCancel(); + break; + + default: + throw new NotSupportedException($"{pointStateType} not supported"); + } + + Redraw(); + + return true; + } + + void Redraw() + { + Invalidate(); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Views/DrawingView/Service/DrawingViewService.tizen.cs b/src/CommunityToolkit.Maui.Core/Views/DrawingView/Service/DrawingViewService.tizen.cs new file mode 100644 index 000000000..42cc97695 --- /dev/null +++ b/src/CommunityToolkit.Maui.Core/Views/DrawingView/Service/DrawingViewService.tizen.cs @@ -0,0 +1,222 @@ +using Microsoft.Maui.Graphics.Skia; +using SkiaSharp; + +namespace CommunityToolkit.Maui.Core.Views; + +/// +/// Drawing view service +/// +public static class DrawingViewService +{ + /// + /// Get image stream from points + /// + /// Drawing points + /// Image size + /// Line Width + /// Line color + /// Image background + /// Image stream + public static ValueTask GetImageStream(IList points, Size imageSize, float lineWidth, Color strokeColor, Paint? background) + { + var image = GetBitmapForPoints(points, lineWidth, strokeColor, background); + + if (image is null) + { + return ValueTask.FromResult(Stream.Null); + } + + // Defer to thread pool thread https://github.com/CommunityToolkit/Maui/pull/692#pullrequestreview-1150202727 + return new ValueTask(Task.Run(() => + { + var resized = image.Resize(new SKImageInfo((int)imageSize.Width, (int)imageSize.Height, SKColorType.Bgra8888, SKAlphaType.Opaque), SKFilterQuality.High); + var data = resized.Encode(SKEncodedImageFormat.Png, 100); + + var stream = new MemoryStream(); + data.SaveTo(stream); + stream.Seek(0, SeekOrigin.Begin); + + return stream; + })); + } + + /// + /// Get image stream from lines + /// + /// Drawing lines + /// Image size + /// Image background + /// Image stream + public static ValueTask GetImageStream(IList lines, Size imageSize, Paint? background) + { + var image = GetBitmapForLines(lines, background); + + if (image is null) + { + return ValueTask.FromResult(Stream.Null); + } + + // Defer to thread pool thread https://github.com/CommunityToolkit/Maui/pull/692#pullrequestreview-1150202727 + return new ValueTask(Task.Run(() => + { + var resized = image.Resize(new SKImageInfo((int)imageSize.Width, (int)imageSize.Height, SKColorType.Bgra8888, SKAlphaType.Opaque), SKFilterQuality.High); + var data = resized.Encode(SKEncodedImageFormat.Png, 100); + + var stream = new MemoryStream(); + data.SaveTo(stream); + stream.Seek(0, SeekOrigin.Begin); + + return stream; + })); + } + + static (SKBitmap?, SizeF offset) GetBitmap(in ICollection points) + { + if (points.Count is 0) + { + return (null, SizeF.Zero); + } + + const int minSize = 1; + var minPointX = points.Min(static p => p.X); + var minPointY = points.Min(static p => p.Y); + var drawingWidth = points.Max(static p => p.X) - minPointX; + var drawingHeight = points.Max(static p => p.Y) - minPointY; + + if (drawingWidth < minSize || drawingHeight < minSize) + { + return (null, SizeF.Zero); + } + + var bitmap = new SKBitmap((int)drawingWidth, (int)drawingHeight, SKColorType.Bgra8888, SKAlphaType.Opaque); + + return (bitmap, new SizeF(minPointX, minPointY)); + } + + static SKBitmap? GetBitmapForLines(in IList lines, in Paint? background) + { + var points = lines.SelectMany(static x => x.Points).ToList(); + var (image, offset) = GetBitmap(points); + + if (image is null) + { + return null; + } + + using var canvas = new SKCanvas(image); + + DrawBackground(canvas, image.Info, background); + + foreach (var line in lines) + { + DrawStrokes(canvas, line.Points, line.LineWidth, line.LineColor, offset); + } + + return image; + } + + static SKBitmap? GetBitmapForPoints(in ICollection points, in float lineWidth, in Color strokeColor, in Paint? background) + { + var (image, offset) = GetBitmap(points); + if (image is null) + { + return null; + } + + using var canvas = new SKCanvas(image); + + DrawBackground(canvas, image.Info, background); + DrawStrokes(canvas, points, lineWidth, strokeColor, offset); + + return image; + } + + static void DrawBackground(in SKCanvas canvas, in SKImageInfo info, in Paint? brush) + { + switch (brush) + { + case SolidPaint solidColorBrush: + canvas.DrawColor(solidColorBrush.Color is not null + ? solidColorBrush.Color.AsSKColor() + : DrawingViewDefaults.BackgroundColor.AsSKColor()); + break; + + case LinearGradientPaint linearGradientBrush: + var paint = new SKPaint(); + var colors = new SKColor[linearGradientBrush.GradientStops.Length]; + var positions = new float[linearGradientBrush.GradientStops.Length]; + + for (var index = 0; index < linearGradientBrush.GradientStops.Length; index++) + { + var gradientStop = linearGradientBrush.GradientStops[index]; + colors[index] = gradientStop.Color.AsSKColor(); + positions[index] = gradientStop.Offset; + } + + var x1 = (float)linearGradientBrush.StartPoint.X * info.Width; + var y1 = (float)linearGradientBrush.StartPoint.Y * info.Height; + var x2 = (float)linearGradientBrush.EndPoint.X * info.Width; + var y2 = (float)linearGradientBrush.EndPoint.Y * info.Height; + + var shader = SKShader.CreateLinearGradient(new SKPoint(x1, y1), + new SKPoint(x2, y2), + colors, + positions, + SKShaderTileMode.Clamp); + paint.Shader = shader; + canvas.DrawRect(0, 0, info.Width, info.Height, paint); + break; + + case RadialGradientPaint radialGradientBrush: + var skPaint = new SKPaint(); + var skColors = new SKColor[radialGradientBrush.GradientStops.Length]; + var positionsArray = new float[radialGradientBrush.GradientStops.Length]; + + for (var index = 0; index < radialGradientBrush.GradientStops.Length; index++) + { + var gradientStop = radialGradientBrush.GradientStops[index]; + skColors[index] = gradientStop.Color.AsSKColor(); + positionsArray[index] = gradientStop.Offset; + } + + float centerX = (float)(radialGradientBrush.Center.X * info.Width); + float centerY = (float)(radialGradientBrush.Center.Y * info.Height); + float radius = (float)(radialGradientBrush.Radius * info.Width); + + var skShader = SKShader.CreateRadialGradient(new SKPoint(centerX, centerX), + radius, + skColors, + positionsArray, + SKShaderTileMode.Clamp); + skPaint.Shader = skShader; + canvas.DrawRect(0, 0, info.Width, info.Height, skPaint); + break; + + default: + canvas.DrawColor(DrawingViewDefaults.BackgroundColor.AsSKColor()); + break; + } + } + + static void DrawStrokes(in SKCanvas canvas, in ICollection points, in float lineWidth, in Color strokeColor, in SizeF offset) + { + using var paint = new SKPaint + { + StrokeWidth = lineWidth, + StrokeJoin = SKStrokeJoin.Round, + StrokeCap = SKStrokeCap.Round, + IsAntialias = true, + Color = strokeColor.AsSKColor(), + Style = SKPaintStyle.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(p1.X - offset.Width, p1.Y - offset.Height, p2.X - offset.Width, p2.Y - offset.Height, paint); + } + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs new file mode 100644 index 000000000..ffd21b410 --- /dev/null +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs @@ -0,0 +1,138 @@ +using Microsoft.Maui.Platform; +using Microsoft.Maui.Primitives; +using Tizen.NUI; +using Tizen.UIExtensions.NUI; +using NHorizontalAlignment = Tizen.NUI.HorizontalAlignment; +using NVerticalAlignment = Tizen.NUI.VerticalAlignment; + +namespace CommunityToolkit.Maui.Core.Views; + +/// +/// The native implementation of Popup control. +/// +public class MauiPopup : Popup +{ + readonly IMauiContext mauiContext; + + /// + /// Constructor of . + /// + /// An instance of . + /// If is null an exception will be thrown. + public MauiPopup(IMauiContext mauiContext) + { + this.mauiContext = mauiContext ?? throw new ArgumentNullException(nameof(mauiContext)); + OutsideClicked += OnOutsideClicked; + } + + /// + /// An instance of the . + /// + public IPopup? VirtualView { get; private set; } + + /// + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + OutsideClicked -= OnOutsideClicked; + } + + base.Dispose(isDisposing); + } + + /// + /// Method to initialize the native implementation. + /// + /// An instance of . + public void SetElement(IPopup? element) + { + VirtualView = element; + } + + /// + /// Method to show the Popup + /// + public void ShowPopup() + { + _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} cannot be null"); + Content = VirtualView.Content?.ToPlatform(mauiContext) ?? throw new InvalidOperationException($"{nameof(VirtualView.Content)} cannot be null"); + + BackgroundColor = new Tizen.NUI.Color(0.1f, 0.1f, 0.1f, 0.5f); + Content.BackgroundColor = (VirtualView.Color ?? Colors.Transparent).ToNUIColor(); + + if (VirtualView.Anchor is not null) + { + var anchorView = VirtualView.Anchor.ToPlatform(); + var anchorPosition = anchorView.ScreenPosition; + Layout = new AbsoluteLayout(); + Content.UpdatePosition(new Tizen.UIExtensions.Common.Point(anchorPosition.X, anchorPosition.Y)); + } + else + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Vertical, + VerticalAlignment = ToVerticalAlignment(VirtualView.VerticalOptions), + HorizontalAlignment = ToHorizontalAlignment(VirtualView.HorizontalOptions), + }; + Content.UpdatePosition(new Tizen.UIExtensions.Common.Point(0, 0)); + } + + UpdateContentSize(); + + Open(); + VirtualView.OnOpened(); + } + + /// + /// Method to update size of Content + /// + /// + public void UpdateContentSize() + { + _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} cannot be null"); + if (Content is null) + { + return; + } + + if (VirtualView.Size.Width > 0 && VirtualView.Size.Height > 0) + { + Content.UpdateSize(VirtualView.Size.ToPixel()); + } + else + { + var measured = VirtualView.Content?.Measure(double.PositiveInfinity, double.PositiveInfinity).ToPixel() ?? new Tizen.UIExtensions.Common.Size(0, 0); + Content.UpdateSize(measured); + } + } + + static NVerticalAlignment ToVerticalAlignment(LayoutAlignment align) => align switch + { + LayoutAlignment.Start => NVerticalAlignment.Top, + LayoutAlignment.End => NVerticalAlignment.Bottom, + _ => NVerticalAlignment.Center + }; + + static NHorizontalAlignment ToHorizontalAlignment(LayoutAlignment align) => align switch + { + LayoutAlignment.Start => NHorizontalAlignment.Begin, + LayoutAlignment.End => NHorizontalAlignment.End, + _ => NHorizontalAlignment.Center + }; + + void OnOutsideClicked(object? sender, EventArgs e) + { + if (VirtualView?.Handler is null) + { + return; + } + + if (VirtualView.CanBeDismissedByTappingOutsideOfPopup) + { + Close(); + VirtualView.Handler.Invoke(nameof(IPopup.OnDismissedByTappingOutsideOfPopup)); + } + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.tizen.cs b/src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.tizen.cs new file mode 100644 index 000000000..ae9a01b8c --- /dev/null +++ b/src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.tizen.cs @@ -0,0 +1,181 @@ +using Microsoft.Maui.Platform; +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using NLinearLayout = Tizen.NUI.LinearLayout; +using NVerticalAlignment = Tizen.NUI.VerticalAlignment; +using NView = Tizen.NUI.BaseComponents.View; +using TButton = Tizen.UIExtensions.NUI.Button; +using TLabel = Tizen.UIExtensions.NUI.Label; +using TPopup = Tizen.UIExtensions.NUI.Popup; + +namespace CommunityToolkit.Maui.Alerts; + +public partial class Snackbar +{ + static TPopup? PopupStatic { get; set; } + + TPopup? PopupInstance { get; set; } + + /// + /// Dispose Snackbar + /// + protected virtual void Dispose(bool isDisposing) + { + if (isDisposed) + { + return; + } + + if (isDisposing) + { + PopupInstance?.Close(); + PopupInstance?.Dispose(); + PopupInstance = null; + } + isDisposed = true; + } + + /// + Task ShowPlatform(CancellationToken token) + { + var dispatcher = Dispatcher.GetForCurrentThread() ?? Application.Current?.Dispatcher ?? throw new InvalidOperationException($"There is no IDispatcher object, application is not initalized"); + + // close and cleanup the previously opened snackbar + if (PopupStatic is not null && PopupStatic.IsOpen) + { + PopupStatic.Close(); + PopupStatic.Dispose(); + PopupStatic = null; + } + + var popup = new OutsidePassThroughPopup + { + Layout = new NLinearLayout + { + LinearOrientation = NLinearLayout.Orientation.Vertical, + VerticalAlignment = NVerticalAlignment.Bottom, + } + }; + + var content = new NView + { + Margin = new Extents((ushort)10d.ToScaledPixel()), + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.WrapContent, + + BackgroundColor = VisualOptions.BackgroundColor.ToNUIColor(), + CornerRadius = new Vector4(10, 10, 10, 10), + Layout = new NLinearLayout + { + LinearOrientation = NLinearLayout.Orientation.Horizontal, + VerticalAlignment = NVerticalAlignment.Center, + } + }; + + var margin = (ushort)10d.ToScaledPixel(); + + var message = new TLabel() + { + MultiLine = true, + Margin = margin, + WidthSpecification = LayoutParamPolicies.MatchParent, + Text = Text, + TextColor = VisualOptions.TextColor.ToPlatform(), + PixelSize = VisualOptions.Font.Size.ToPixel(), + FontAttributes = LabelExtensions.GetFontAttributes(VisualOptions.Font), + }; + content.Add(message); + + var actionButton = new TButton() + { + Margin = margin, + WidthSpecification = LayoutParamPolicies.WrapContent, + BackgroundColor = Tizen.NUI.Color.Transparent, + TextColor = VisualOptions.ActionButtonTextColor.ToPlatform(), + Text = ActionButtonText, + }; + actionButton.TextLabel.PixelSize = 15d.ToPixel(); + actionButton.SizeWidth = actionButton.TextLabel.NaturalSize.Width + 15d.ToPixel() * 2; + actionButton.Clicked += (s, e) => + { + if (Action is not null) + { + Action.Invoke(); + } + else + { + popup.Close(); + } + }; + + content.Add(actionButton); + popup.Content = content; + + if (Anchor is not null) + { + var anchorPlatformView = Anchor.ToPlatform(); + + // can't measure height of content on NUI + var maximumHeight = 100d.ToScaledPixel(); + popup.SizeHeight = maximumHeight; + + if (maximumHeight < anchorPlatformView.ScreenPosition.Y) + { + // display top outside of anchor + popup.Position = new Position(0, anchorPlatformView.ScreenPosition.Y - popup.SizeHeight); + } + else + { + // display bottom innter of anchor + popup.Position = new Position(0, anchorPlatformView.ScreenPosition.Y - (maximumHeight - anchorPlatformView.SizeHeight)); + } + } + else + { + popup.Position = new Position(0, 0); + popup.WidthSpecification = LayoutParamPolicies.MatchParent; + popup.HeightSpecification = LayoutParamPolicies.MatchParent; + } + + popup.Closed += (s, e) => OnDismissed(); + popup.Open(); + + var timer = dispatcher.CreateTimer(); + timer.IsRepeating = false; + timer.Interval = Duration; + timer.Tick += (s, e) => popup.Close(); + timer.Start(); + + PopupStatic = popup; + PopupInstance = popup; + + OnShown(); + + return Task.CompletedTask; + } + + /// + Task DismissPlatform(CancellationToken token) + { + token.ThrowIfCancellationRequested(); + + if (PopupInstance is null) + { + return Task.CompletedTask; + } + + PopupInstance.Close(); + PopupInstance.Dispose(); + PopupInstance = null; + + return Task.CompletedTask; + } + + class OutsidePassThroughPopup : TPopup + { + protected override bool HitTest(Touch touch) + { + return false; + } + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Alerts/Toast/Toast.tizen.cs b/src/CommunityToolkit.Maui/Alerts/Toast/Toast.tizen.cs new file mode 100644 index 000000000..02476e5ee --- /dev/null +++ b/src/CommunityToolkit.Maui/Alerts/Toast/Toast.tizen.cs @@ -0,0 +1,39 @@ +namespace CommunityToolkit.Maui.Alerts; + +public partial class Toast +{ + /// + /// Dispose Toast + /// + protected virtual void Dispose(bool isDisposing) + { + if (isDisposed) + { + return; + } + + isDisposed = true; + } + + /// + /// Show Toast + /// + void ShowPlatform(CancellationToken token) + { + DismissPlatform(token); + token.ThrowIfCancellationRequested(); + + new Tizen.Applications.ToastMessage + { + Message = Text + }.Post(); + } + + /// + /// Dismiss Toast + /// + static void DismissPlatform(CancellationToken token) + { + token.ThrowIfCancellationRequested(); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/IconTintColor/IconTintColorBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/IconTintColor/IconTintColorBehavior.shared.cs index 10aa7e92f..5f122e5fd 100644 --- a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/IconTintColor/IconTintColorBehavior.shared.cs +++ b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/IconTintColor/IconTintColorBehavior.shared.cs @@ -5,7 +5,7 @@ namespace CommunityToolkit.Maui.Behaviors; /// /// A behavior that allows you to tint an icon with a specified . /// -[UnsupportedOSPlatform("windows")] +[UnsupportedOSPlatform("windows"), UnsupportedOSPlatform("Tizen")] public partial class IconTintColorBehavior : PlatformBehavior { /// diff --git a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/SelectAllText/SelectAllTextBehavior.tizen.cs b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/SelectAllText/SelectAllTextBehavior.tizen.cs new file mode 100644 index 000000000..8cdeccfef --- /dev/null +++ b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/SelectAllText/SelectAllTextBehavior.tizen.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tizen.NUI.BaseComponents; +using NView = Tizen.NUI.BaseComponents.View; + +namespace CommunityToolkit.Maui.Behaviors; + +/// +/// A behavior that selects all text when the view is focused. +/// +public class SelectAllTextBehavior : PlatformBehavior +{ + /// + protected override void OnAttachedTo(InputView bindable, NView platformView) => ApplyEffect(true, platformView); + + /// + protected override void OnDetachedFrom(InputView bindable, NView platformView) => ApplyEffect(false, platformView); + + + void ApplyEffect(bool apply, NView inputView) + { + ArgumentNullException.ThrowIfNull(inputView); + + inputView.FocusGained -= OnFocused; + + if (apply) + { + inputView.FocusGained += OnFocused; + } + + static void OnFocused(object? sender, EventArgs e) + { + if (sender is TextField tf && tf.HasFocus()) + { + tf.SelectWholeText(); + } + else if (sender is TextEditor te && te.HasFocus()) + { + te.SelectWholeText(); + } + } + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/StatusBar/StatusBarBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/StatusBar/StatusBarBehavior.shared.cs index e33adc511..a6e9cc6f9 100644 --- a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/StatusBar/StatusBarBehavior.shared.cs +++ b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/StatusBar/StatusBarBehavior.shared.cs @@ -10,7 +10,7 @@ namespace CommunityToolkit.Maui.Behaviors; /// /// that controls the Status bar color /// -[UnsupportedOSPlatform("Windows"), UnsupportedOSPlatform("MacCatalyst"), UnsupportedOSPlatform("MacOS")] +[UnsupportedOSPlatform("Windows"), UnsupportedOSPlatform("MacCatalyst"), UnsupportedOSPlatform("MacOS"), UnsupportedOSPlatform("Tizen")] public class StatusBarBehavior : PlatformBehavior { /// @@ -44,7 +44,7 @@ public StatusBarStyle StatusBarStyle set => SetValue(StatusBarStyleProperty, value); } -#if !(WINDOWS || MACCATALYST) +#if !(WINDOWS || MACCATALYST || TIZEN) /// #if IOS diff --git a/src/CommunityToolkit.Maui/CommunityToolkit.Maui.csproj b/src/CommunityToolkit.Maui/CommunityToolkit.Maui.csproj index a9ae3cad2..b9e2033a5 100644 --- a/src/CommunityToolkit.Maui/CommunityToolkit.Maui.csproj +++ b/src/CommunityToolkit.Maui/CommunityToolkit.Maui.csproj @@ -3,6 +3,7 @@ net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst $(TargetFrameworks);net6.0-windows10.0.19041.0 + $(TargetFrameworks);net6.0-tizen true true true @@ -74,6 +75,12 @@ + + + + + +