From 474181b653e2af81e020939fdc92a727b865a4e7 Mon Sep 17 00:00:00 2001 From: Nick Kovalsky Date: Thu, 21 Nov 2024 20:28:48 +0300 Subject: [PATCH] 1.2.9.2 --- README.md | 27 +- dev/DrawnUi.Maui-dev.sln | 1 - dev/github_uploadnugets.bat | 4 +- dev/nuget_uploadnugets.bat | 4 +- .../DrawnUi.Maui.Camera.csproj | 2 +- .../DrawnUi.Maui.Game.csproj | 2 +- .../src/DrawnUi.Maui.MapsUi.csproj | 2 +- .../DrawnUi.Maui.Rive.csproj | 2 +- .../DrawnUi.MauiGraphics.csproj | 2 +- src/Directory.Build.props | 2 +- src/Engine/Draw/Base/SkiaControl.Shared.cs | 13 +- src/Engine/Draw/Images/LoadedImageSource.cs | 27 +- src/Engine/Draw/Images/SkiaImage.cs | 24 +- .../Draw/Layout/SkiaLayout.Grid.Structure.cs | 7 +- src/Engine/Draw/SkiaShape.cs | 11 +- src/Engine/Draw/Svg/SkiaSvg.cs | 128 +++-- .../Features/Images/SkiaImageManager.cs | 2 +- src/Engine/Views/DrawnView.cs | 10 +- src/Engine/skia2.props | 9 +- src/samples/Sandbox/Chat/WidgetChatMessage.cs | 528 ++++++++++++++++++ src/samples/Sandbox/Chat/WidgetListCell.cs | 209 +++++++ .../Sandbox/MainPageCodeDeformation.cs | 119 ++++ src/samples/Sandbox/MainPageRepro.xaml | 34 +- src/samples/Sandbox/MainPageRepro.xaml.cs | 33 +- src/samples/Sandbox/Resources/Svg.xaml | 6 + src/samples/Sandbox/Sandbox.csproj | 8 + src/samples/Sandbox/ViewModels/ChatMessage.cs | 436 +++++++++++++++ .../Sandbox/ViewModels/MockChat2ViewModel.cs | 80 +++ .../Sandbox/ViewModels/MockChatViewModel.cs | 27 +- src/samples/Sandbox/ViewModels/Player.cs | 290 ++++++++++ 30 files changed, 1880 insertions(+), 169 deletions(-) create mode 100644 src/samples/Sandbox/Chat/WidgetChatMessage.cs create mode 100644 src/samples/Sandbox/Chat/WidgetListCell.cs create mode 100644 src/samples/Sandbox/MainPageCodeDeformation.cs create mode 100644 src/samples/Sandbox/ViewModels/ChatMessage.cs create mode 100644 src/samples/Sandbox/ViewModels/MockChat2ViewModel.cs create mode 100644 src/samples/Sandbox/ViewModels/Player.cs diff --git a/README.md b/README.md index b473276c..81d758e6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # DrawnUI for .NET MAUI ![License](https://img.shields.io/github/license/taublast/DrawnUi.Maui.svg) +![NuGet Version](https://img.shields.io/nuget/v/AppoMobi.Maui.DrawnUi.svg) ![NuGet Downloads](https://img.shields.io/nuget/dt/AppoMobi.Maui.DrawnUi.svg) Rendering engine to draw your UI on a Skia canvas, with gestures and animations, designed to draw pixel-perfect custom controls instead of using native ones, powered by [SkiaSharp](https://github.com/mono/SkiaSharp)😍. Create and render your custom controls on a hardware-accelerated Skia canvas, with effects and animations. +**IMPORTANT UPDATE** +Reverted SkiaSharp from 2.88.9 to 2.88.9-preview.2.2 +until [scaling issue](https://github.com/taublast/DrawnUi.Maui/issues/130) is solved for Windows + Supports **iOS**, **MacCatalyst**, **Android**, **Windows**. * To use inside a usual MAUI app, consume drawn controls here and there inside `Canvas` views. @@ -98,18 +103,16 @@ V3 preview: subclassed `SkiaShaderEffect`, implementing `ISkiaGestureProcessor`, ## What's New -* Nuget 1.2.9.1 stable for SkiaSharp 2.88.9 -* Pdf helper added -* Sandbox adapted, added example for multi-page pdf -* SkiaImageManager fix for non-loading images -* SkiaImage speed up loading from cached images -* ContentLayout changed to skialayout fixing padding and margin problem -* SkiaLayout invalidation and some others fixes -* SkiaSlider wrong initial position fix -* Gestures nuget updated -* Added FluentExtensions -* Another SkiaScroll fix for RefreshView -* Fix crash when disposing from different threads +### Nuget 1.2.9.2 +for SkiaSharp 2.88.9-preview.2.2 + +* Reverted SkiaSharp to 2.88.9-preview.2.2 back from 2.88.9 +until [scaling issue](https://github.com/taublast/DrawnUi.Maui/issues/130) is solved for Windows +* Antialiasing [issue](https://github.com/taublast/DrawnUi.Maui/issues/122) solved for SkiaShape +* SkiaImageManager cancelling loads fix +* Fixed native crash when using Super.ReuseBitmaps +* SkiaSvg made more GC-friendly +* Added `WithParent` to FluentExtensions * Some more ## Development Notes diff --git a/dev/DrawnUi.Maui-dev.sln b/dev/DrawnUi.Maui-dev.sln index dc47e8f8..32dbb91e 100644 --- a/dev/DrawnUi.Maui-dev.sln +++ b/dev/DrawnUi.Maui-dev.sln @@ -10,7 +10,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution movenugets.bat = movenugets.bat nuget_uploadnugets.bat = nuget_uploadnugets.bat ..\README.md = ..\README.md - ..\src\ToDo.txt = ..\src\ToDo.txt EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DrawnUi.Maui", "..\src\Engine\DrawnUi.Maui.csproj", "{D76B6239-94A0-482C-A0FF-A764017B7393}" diff --git a/dev/github_uploadnugets.bat b/dev/github_uploadnugets.bat index 8a9e95ea..c7b82b83 100644 --- a/dev/github_uploadnugets.bat +++ b/dev/github_uploadnugets.bat @@ -13,8 +13,8 @@ REM Define the source directory for the packages set "source_dir=E:\Nugets" REM Define the list of file masks for the packages -set "mask[1]=DrawnUi.Maui*.1.2.9.1*.nupkg" -set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.1*.*nupkg" +set "mask[1]=DrawnUi.Maui*.1.2.9.2*.nupkg" +set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.2*.*nupkg" set "mask_count=2" REM Loop through each file mask diff --git a/dev/nuget_uploadnugets.bat b/dev/nuget_uploadnugets.bat index 4dbf6119..43c915a0 100644 --- a/dev/nuget_uploadnugets.bat +++ b/dev/nuget_uploadnugets.bat @@ -13,8 +13,8 @@ REM Define the source directory for the packages set "source_dir=E:\Nugets" REM Define the list of file masks for the packages -set "mask[1]=DrawnUi.Maui*.1.2.9.1*.nupkg" -set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.1*.*nupkg" +set "mask[1]=DrawnUi.Maui*.1.2.9.2*.nupkg" +set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.2*.*nupkg" set "mask_count=2" REM Loop through each file mask diff --git a/src/Addons/DrawnUi.Maui.Camera/DrawnUi.Maui.Camera.csproj b/src/Addons/DrawnUi.Maui.Camera/DrawnUi.Maui.Camera.csproj index 4eabe18f..7430a7ef 100644 --- a/src/Addons/DrawnUi.Maui.Camera/DrawnUi.Maui.Camera.csproj +++ b/src/Addons/DrawnUi.Maui.Camera/DrawnUi.Maui.Camera.csproj @@ -44,7 +44,7 @@ - + \ No newline at end of file diff --git a/src/Addons/DrawnUi.Maui.Game/DrawnUi.Maui.Game.csproj b/src/Addons/DrawnUi.Maui.Game/DrawnUi.Maui.Game.csproj index 74935549..8ce580ea 100644 --- a/src/Addons/DrawnUi.Maui.Game/DrawnUi.Maui.Game.csproj +++ b/src/Addons/DrawnUi.Maui.Game/DrawnUi.Maui.Game.csproj @@ -42,7 +42,7 @@ - + diff --git a/src/Addons/DrawnUi.Maui.MapsUi/src/DrawnUi.Maui.MapsUi.csproj b/src/Addons/DrawnUi.Maui.MapsUi/src/DrawnUi.Maui.MapsUi.csproj index 55df7af7..06ce899e 100644 --- a/src/Addons/DrawnUi.Maui.MapsUi/src/DrawnUi.Maui.MapsUi.csproj +++ b/src/Addons/DrawnUi.Maui.MapsUi/src/DrawnUi.Maui.MapsUi.csproj @@ -54,7 +54,7 @@ - + diff --git a/src/Addons/DrawnUi.Maui.Rive/DrawnUi.Maui.Rive.csproj b/src/Addons/DrawnUi.Maui.Rive/DrawnUi.Maui.Rive.csproj index 68f9e050..272db962 100644 --- a/src/Addons/DrawnUi.Maui.Rive/DrawnUi.Maui.Rive.csproj +++ b/src/Addons/DrawnUi.Maui.Rive/DrawnUi.Maui.Rive.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/Addons/DrawnUi.MauiGraphics/DrawnUi.MauiGraphics.csproj b/src/Addons/DrawnUi.MauiGraphics/DrawnUi.MauiGraphics.csproj index 67abbb57..26973555 100644 --- a/src/Addons/DrawnUi.MauiGraphics/DrawnUi.MauiGraphics.csproj +++ b/src/Addons/DrawnUi.MauiGraphics/DrawnUi.MauiGraphics.csproj @@ -41,7 +41,7 @@ - + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 4c3a490a..3d6f4fde 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,7 +5,7 @@ Using SkiaSharp 2.xx. Checkout the DrawnUi Sandbox project for usage example. - 1.2.9.1 + 1.2.9.2 diff --git a/src/Engine/Draw/Base/SkiaControl.Shared.cs b/src/Engine/Draw/Base/SkiaControl.Shared.cs index 230ca60d..bec80af1 100644 --- a/src/Engine/Draw/Base/SkiaControl.Shared.cs +++ b/src/Engine/Draw/Base/SkiaControl.Shared.cs @@ -35,6 +35,8 @@ public SkiaControl() Init(); } + protected static SKBlendMode DefaultBlendMode = SKBlendMode.SrcOver; + public virtual bool IsVisibleInViewTree() { var isVisible = IsVisible && !IsDisposed; @@ -3436,6 +3438,9 @@ void PostProcessMeasuredChild(ScaledSize measured, SkiaControl child, bool ignor /// public virtual void SetInheritedBindingContext(object context) { + if (IsDisposing) + return; + BindingContext = context; } @@ -3444,7 +3449,10 @@ public virtual void SetInheritedBindingContext(object context) /// public virtual void ApplyBindingContext() { - foreach (var content in this.Views) + if (IsDisposing) + return; + + foreach (var content in this.Views.ToList()) { content.SetInheritedBindingContext(BindingContext); } @@ -3461,6 +3469,9 @@ public virtual void ApplyBindingContext() /// protected override void OnBindingContextChanged() { + if (IsDisposing) + return; + BindingContextWasSet = true; try diff --git a/src/Engine/Draw/Images/LoadedImageSource.cs b/src/Engine/Draw/Images/LoadedImageSource.cs index c8028f27..7e6c240e 100644 --- a/src/Engine/Draw/Images/LoadedImageSource.cs +++ b/src/Engine/Draw/Images/LoadedImageSource.cs @@ -14,18 +14,28 @@ public LoadedImageSource Clone() // Clone the SKBitmap var bitmapClone = new SKBitmap(Bitmap.Width, Bitmap.Height, Bitmap.ColorType, Bitmap.AlphaType); Bitmap.CopyTo(bitmapClone); - return new LoadedImageSource(bitmapClone); + return new LoadedImageSource(bitmapClone) + { + ProtectBitmapFromDispose = this.ProtectBitmapFromDispose, + ProtectFromDispose = this.ProtectFromDispose + }; } else if (Image != null) { // Clone the SKImage var imageClone = SKImage.FromBitmap(SKBitmap.FromImage(Image)); - return new LoadedImageSource(imageClone); + return new LoadedImageSource(imageClone) + { + ProtectFromDispose = this.ProtectFromDispose + }; } else { // If there's no image or bitmap, return a new empty instance - return new LoadedImageSource(); + return new LoadedImageSource() + { + ProtectFromDispose = this.ProtectFromDispose + }; } } @@ -36,14 +46,23 @@ public LoadedImageSource Clone() /// public bool ProtectFromDispose { get; set; } + /// + /// Should be set to true for loaded with SkiaImageManager.ReuseBitmaps + /// + public bool ProtectBitmapFromDispose { get; set; } + public void Dispose() { if (!IsDisposed && !ProtectFromDispose) { IsDisposed = true; - Bitmap?.Dispose(); + if (!ProtectBitmapFromDispose) + { + Bitmap?.Dispose(); + } Bitmap = null; + Image?.Dispose(); Image = null; } diff --git a/src/Engine/Draw/Images/SkiaImage.cs b/src/Engine/Draw/Images/SkiaImage.cs index 4732c2bb..6a68cb0b 100644 --- a/src/Engine/Draw/Images/SkiaImage.cs +++ b/src/Engine/Draw/Images/SkiaImage.cs @@ -33,7 +33,7 @@ protected virtual LoadedImageSource SetImage(LoadedImageSource loaded) if (kill != null) { if (SkiaImageManager.ReuseBitmaps) - kill.Bitmap = null; //do not dispose shared cached image + kill.ProtectBitmapFromDispose = true; //do not dispose shared cached image DisposeObject(kill); } @@ -322,7 +322,10 @@ public void SetFromBase64(string input) var bitmap = SKBitmap.Decode(pixelArray); - ImageBitmap = new LoadedImageSource(bitmap); + ImageBitmap = new LoadedImageSource(bitmap) + { + ProtectBitmapFromDispose = SkiaImageManager.ReuseBitmaps + }; //SetImage(new InstancedBitmap(bitmap)); } @@ -360,7 +363,8 @@ public LoadedImageSource SetBitmapInternal(SKBitmap bitmap, bool protectFromDisp { return SetImage(new LoadedImageSource(bitmap) { - ProtectFromDispose = protectFromDispose + ProtectFromDispose = protectFromDispose, + ProtectBitmapFromDispose = SkiaImageManager.ReuseBitmaps }); } @@ -368,7 +372,8 @@ public LoadedImageSource SetImageInternal(SKImage image, bool protectFromDispose { return SetImage(new LoadedImageSource(image) { - ProtectFromDispose = protectFromDispose + ProtectFromDispose = protectFromDispose, + ProtectBitmapFromDispose = SkiaImageManager.ReuseBitmaps }); } @@ -451,7 +456,10 @@ public virtual void SetImageSource(ImageSource source) var cachedBitmap = SkiaImageManager.Instance.GetFromCache(uri); if (cachedBitmap != null) { - ImageBitmap = new LoadedImageSource(cachedBitmap); + ImageBitmap = new LoadedImageSource(cachedBitmap) + { + ProtectBitmapFromDispose = SkiaImageManager.ReuseBitmaps + }; OnSuccess?.Invoke(this, new ContentLoadedEventArgs(uri)); return; } @@ -518,7 +526,10 @@ async Task LoadAction() if (bitmap != null) { CancelLoading?.Cancel(); - ImageBitmap = new LoadedImageSource(bitmap); //at the end will use SetImage(new InstancedBitmap(bitmap)); + ImageBitmap = new LoadedImageSource(bitmap) + { + ProtectBitmapFromDispose = SkiaImageManager.ReuseBitmaps + }; //at the end will use SetImage(new InstancedBitmap(bitmap)); TraceLog($"[SkiaImage] Loaded {source}"); OnSuccess?.Invoke(this, new ContentLoadedEventArgs(url)); OnSourceSuccess(); @@ -1036,6 +1047,7 @@ protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float ImagePaint = new() { IsAntialias = true, + IsDither = IsDistorted, FilterQuality = SKFilterQuality.High }; } diff --git a/src/Engine/Draw/Layout/SkiaLayout.Grid.Structure.cs b/src/Engine/Draw/Layout/SkiaLayout.Grid.Structure.cs index f13d1fd9..df99eaec 100644 --- a/src/Engine/Draw/Layout/SkiaLayout.Grid.Structure.cs +++ b/src/Engine/Draw/Layout/SkiaLayout.Grid.Structure.cs @@ -7,13 +7,18 @@ namespace DrawnUi.Maui.Draw; public static class FluentExtensions { - public static T With(this T view, Action action) where T : SkiaControl { action?.Invoke(view); return view; } + public static T WithParent(this T view, IDrawnBase parent) where T : SkiaControl + { + parent.AddSubView(view); + return view; + } + public static T CenterX(this T view) where T : SkiaControl { view.HorizontalOptions = LayoutOptions.Center; diff --git a/src/Engine/Draw/SkiaShape.cs b/src/Engine/Draw/SkiaShape.cs index 7185decf..c07edbf2 100644 --- a/src/Engine/Draw/SkiaShape.cs +++ b/src/Engine/Draw/SkiaShape.cs @@ -533,17 +533,10 @@ protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float RenderingPaint ??= new SKPaint() { - //IsAntialias = true, + IsAntialias = true }; - //if (IsDistorted) - //{ - // RenderingPaint.FilterQuality = SKFilterQuality.Medium; - //} - //else - //{ - // RenderingPaint.FilterQuality = SKFilterQuality.None; - //} + RenderingPaint.IsDither = IsDistorted; if (BackgroundColor != null) { diff --git a/src/Engine/Draw/Svg/SkiaSvg.cs b/src/Engine/Draw/Svg/SkiaSvg.cs index 045fa3e0..448c4952 100644 --- a/src/Engine/Draw/Svg/SkiaSvg.cs +++ b/src/Engine/Draw/Svg/SkiaSvg.cs @@ -1,8 +1,8 @@ -using DrawnUi.Maui.Features.Images; -using Svg.Skia; -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using DrawnUi.Maui.Features.Images; +using Svg.Skia; namespace DrawnUi.Maui.Draw { @@ -472,6 +472,8 @@ protected string LoadedString } } + protected SKPaint RenderingPaint { get; set; } + public override void OnDisposing() { LoadedString = null; @@ -479,6 +481,9 @@ public override void OnDisposing() Svg?.Dispose(); Svg = null; + RenderingPaint?.Dispose(); + RenderingPaint = null; + base.OnDisposing(); } @@ -824,29 +829,29 @@ public static SKRect CalculateDisplayRect(SKRect dest, float bmpWidth, float bmp switch (horizontal) { case DrawImageAlignment.Center: - x = (dest.Width - bmpWidth) / 2.0f; - break; + x = (dest.Width - bmpWidth) / 2.0f; + break; case DrawImageAlignment.Start: - break; + break; case DrawImageAlignment.End: - x = dest.Width - bmpWidth; - break; + x = dest.Width - bmpWidth; + break; } switch (vertical) { case DrawImageAlignment.Center: - y = (dest.Height - bmpHeight) / 2.0f; - break; + y = (dest.Height - bmpHeight) / 2.0f; + break; case DrawImageAlignment.Start: - break; + break; case DrawImageAlignment.End: - y = dest.Height - bmpHeight; - break; + y = dest.Height - bmpHeight; + break; } x += dest.Left; @@ -1085,82 +1090,81 @@ protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float if (Svg != null)// !string.IsNullOrEmpty(LoadedString)) { + RenderingPaint ??= new SKPaint() + { + IsAntialias = true + }; + RenderingPaint.IsDither = IsDistorted; + RenderingPaint.BlendMode = DefaultBlendMode; + SKMatrix matrix = CreateSvgMatrix(area, scale); SKPath clipPath = null; if (TintColor != Colors.Transparent && !UseGradient) { + RenderingPaint.Shader = null;//todo dispose - using (var paint = new SKPaint()) - { - //drop shadow - AddShadow(paint, scale); - paint.IsAntialias = true; - paint.ColorFilter = SKColorFilter.CreateBlendMode(TintColor.ToSKColor(), SKBlendMode.SrcIn); - - ctx.Canvas.DrawPicture(Svg.Picture, ref matrix, paint); - } + AddShadow(RenderingPaint, scale); + RenderingPaint.ColorFilter = SKColorFilter.CreateBlendMode(TintColor.ToSKColor(), SKBlendMode.SrcIn); + ctx.Canvas.DrawPicture(Svg.Picture, ref matrix, RenderingPaint); } else if (UseGradient) //todo use standart gradient property instead { + RenderingPaint.ColorFilter = null; //todo dispose + ctx.Canvas.DrawPicture(Svg.Picture, ref matrix); - //draw gradient rectangle above - using (var paint = new SKPaint()) - { - //drop shadow - AddShadow(paint, scale); - paint.IsAntialias = true; - paint.Shader = SKShader.CreateLinearGradient( - new SKPoint(Destination.Width * StartXRatio, Destination.Height * StartYRatio), - new SKPoint(Destination.Width * EndXRatio, Destination.Height * EndYRatio), - new SKColor[] - { + //will draw gradient rectangle above + + //drop shadow + AddShadow(RenderingPaint, scale); + RenderingPaint.Shader = SKShader.CreateLinearGradient( + new SKPoint(Destination.Width * StartXRatio, Destination.Height * StartYRatio), + new SKPoint(Destination.Width * EndXRatio, Destination.Height * EndYRatio), + new SKColor[] + { StartColor.ToSKColor(), EndColor.ToSKColor() - }, - null, - SKShaderTileMode.Clamp); + }, + null, + SKShaderTileMode.Clamp); - paint.BlendMode = GradientBlendMode; - - ctx.Canvas.DrawRect(Destination, paint); - } + RenderingPaint.BlendMode = GradientBlendMode; + ctx.Canvas.DrawRect(Destination, RenderingPaint); } else { - using (var paint = new SKPaint()) - { - paint.IsAntialias = true; - //drop shadow - AddShadow(paint, scale); + RenderingPaint.Shader = null;//todo dispose + RenderingPaint.ColorFilter = null; //todo dispose + + //drop shadow + AddShadow(RenderingPaint, scale); - // paint.ColorFilter = SKColorFilter.CreateBlendMode(BackgroundColors.ToSKColor(), SKBlendMode.SrcIn); + // paint.ColorFilter = SKColorFilter.CreateBlendMode(BackgroundColors.ToSKColor(), SKBlendMode.SrcIn); - if (Clipping != null) + if (Clipping != null) + { + if (clipPath == null) { - if (clipPath == null) - { - clipPath = new SKPath(); - Clipping.Invoke(clipPath, Destination); - } + clipPath = new SKPath(); + Clipping.Invoke(clipPath, Destination); + } - var saved = ctx.Canvas.Save(); - ClipSmart(ctx.Canvas, clipPath); + var saved = ctx.Canvas.Save(); + ClipSmart(ctx.Canvas, clipPath); - ctx.Canvas.DrawPicture(Svg.Picture, ref matrix, paint); + ctx.Canvas.DrawPicture(Svg.Picture, ref matrix, RenderingPaint); - ctx.Canvas.RestoreToCount(saved); + ctx.Canvas.RestoreToCount(saved); - clipPath.Dispose(); - } - else - { - ctx.Canvas.DrawPicture(Svg.Picture, ref matrix, paint); - } + clipPath.Dispose(); + } + else + { + ctx.Canvas.DrawPicture(Svg.Picture, ref matrix, RenderingPaint); } } } diff --git a/src/Engine/Features/Images/SkiaImageManager.cs b/src/Engine/Features/Images/SkiaImageManager.cs index 80b3e4cd..d4596b4e 100644 --- a/src/Engine/Features/Images/SkiaImageManager.cs +++ b/src/Engine/Features/Images/SkiaImageManager.cs @@ -569,7 +569,7 @@ private async void ProcessQueue() { try { - if (IsLoadingLocked || semaphoreLoad.CurrentCount < 1) + if (IsLoadingLocked) { TraceLog($"ImageLoadManager: Loading Locked!"); await Task.Delay(50); diff --git a/src/Engine/Views/DrawnView.cs b/src/Engine/Views/DrawnView.cs index ccc2d4f1..f3b5cf7e 100644 --- a/src/Engine/Views/DrawnView.cs +++ b/src/Engine/Views/DrawnView.cs @@ -1638,10 +1638,16 @@ public async Task ProcessOffscreenCacheRenderingAsync() _processingOffscrenRendering = true; var command = _offscreenCacheRenderingQueue.Dequeue(); - while (!command.Control.IsDisposed && !command.Control.IsDisposing) + while (command != null) { try { + if (command.Control.IsDisposed || command.Control.IsDisposing) + { + _offscreenCacheRenderingQueue.Clear(); + break; + } + var action = command.Control.GetOffscreenRenderingAction(); action?.Invoke(); @@ -2619,6 +2625,4 @@ public virtual void ClipSmart(SKCanvas canvas, SKPath path, SKClipOperation oper } - - } diff --git a/src/Engine/skia2.props b/src/Engine/skia2.props index 9d98f31c..ba701db7 100644 --- a/src/Engine/skia2.props +++ b/src/Engine/skia2.props @@ -1,9 +1,10 @@ - - - - + + + + + \ No newline at end of file diff --git a/src/samples/Sandbox/Chat/WidgetChatMessage.cs b/src/samples/Sandbox/Chat/WidgetChatMessage.cs new file mode 100644 index 00000000..9f1f40bc --- /dev/null +++ b/src/samples/Sandbox/Chat/WidgetChatMessage.cs @@ -0,0 +1,528 @@ +using System.ComponentModel; +using DrawnUi.Maui.Extensions; +using Sandbox; + +namespace AppoMobi.Forms.Controls +{ + + public class WidgetChatMessage : WidgetListCell + { + //public override void Draw(SKCanvas canvas, SKImageInfo info, SKRect destination, double scale = 1.0, SkiaAnchor anchor = SkiaAnchor.TopLeft) + //{ + // if (IsRootView(ParentControl, info, destination)) + // canvas.Clear(ClearColors.ToSKColor()); + + // if (IsSelected) + // { + // Opacity = 0.7; + // } + // else + // { + // Opacity = 1.0; + // } + + // base.Draw(canvas, info, destination, scale); + //} + + + public SkiaShape FrameNewDate { get; set; } + + public SkiaLabel LabelFirstDate { get; set; } + + public SkiaLayout MainHorizontalStack { get; set; } + + public SkiaLayout MessageStack { get; set; } + + //public SkiaLayout StackBubble { get; set; } + + public SkiaImage Banner { get; set; } + + public SkiaLabel LabelMessage { get; set; } + + public SkiaLabel LabelTime { get; set; } + + public SkiaSvg IconAttachment { get; set; } + + public SkiaSvg BubbleArrowIncoming { get; set; } + + public SkiaSvg BubbleArrowOutcoming { get; set; } + + public SkiaSvg IconWasSent { get; set; } + + public SkiaSvg IconWasDelivered { get; set; } + + private ChatMessage _oldContext; + + public override void OnDisposing() + { + if (_oldContext != null) + _oldContext.PropertyChanged -= OnContextPropertyChanged; + + Banner.OnError -= OnImageError; + + DetachGestures(); + + base.OnDisposing(); + } + + private void OnImageError(object sender, EventArgs e) + { + //todo AvatarDefault.IsVisible = true; + Update(); + } + + private void OnContextPropertyChanged(object sender, PropertyChangedEventArgs e) + { + + if (e.PropertyName == "Id")//Message sent + { + SetContentFull(BindingContext as ChatMessage); + return; + } + + if (e.PropertyName == "Read" + || e.PropertyName == "Sent" + || e.PropertyName == "Delivered") + { + UpdateStatus(BindingContext as ChatMessage); + Update(); + } + else + if (e.PropertyName == "Notify") + { + var item = BindingContext as ChatMessage; + if (item != null) + { + IsNew = item.Notify; + } + else + { + IsNew = false; + } + UpdateContainer(BindingContext as ChatMessage); + Update(); + } + } + + private void OnFirstTimeMeasured(object sender, ScaledSize scaledSize) + { + var label = sender as SkiaLabel; + + FrameNewDate.WidthRequest = label.MeasuredSize.Units.Width + label.Margin.Left + label.Margin.Right; + } + + //this is more smooth when invoked here comparing to actions inside OnBindingContextChanged + void OnTextSizeChanged(object sender, ScaledSize scaledSize) + { + var label = sender as SkiaLabel; + var requestContainerWidth = label.MeasuredSize.Units.Width + label.Margin.Left + label.Margin.Right + 8; + + if (Template == ChatMetaType.File) + { + requestContainerWidth += 16 + 8 + 4; + } + + if (MainFrame.Destination.Width != requestContainerWidth) + { + MainFrame.WidthRequest = requestContainerWidth; + } + + var addHeight = 0; + if (ShowDate) + { + addHeight = 24 + 16; + } + + //set forms view dynamic height... + if (Template == ChatMetaType.Image || Template == ChatMetaType.Video || Template == ChatMetaType.Article) + { + HeightRequest = addHeight + label.MeasuredSize.Units.Height + label.Margin.Top + label.Margin.Bottom + Banner.HeightRequest + 10; + } + else + { + HeightRequest = addHeight + label.MeasuredSize.Units.Height + label.Margin.Top + label.Margin.Bottom + 10; + } + } + + public WidgetChatMessage() + { + + MainHorizontalStack = new SkiaLayout() + { + Tag = "MainHorizontalStack", + Type = LayoutType.Row, + Padding = new Thickness(8, 0, 8, 0), + Spacing = 0, + VerticalOptions = LayoutOptions.Fill, + HorizontalOptions = LayoutOptions.End + }.WithParent(this); + + FrameNewDate = new SkiaShape() + { + Tag = "FrameNewDate", + Type = ShapeType.Rectangle, + CornerRadius = 8, + HeightRequest = 24, + WidthRequest = 100, + HorizontalOptions = LayoutOptions.Center, + Margin = new Thickness(8, 8, 8, 8), + }.WithParent(this); + + LabelFirstDate = new SkiaLabel() + { + TranslationY = -4, + Margin = new Thickness(10, 0, 10, 0), + LineBreakMode = LineBreakMode.NoWrap, + MaxLines = 1, + FontAttributes = FontAttributes.Bold, + Text = $"Time", + FontSize = 10, + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + TextColor = App.Current.Resources.Get("ColorPrimary") + }.With((c) => + { + c.Measured += OnFirstTimeMeasured; + }).WithParent(FrameNewDate); + + + + BubbleArrowIncoming = new SkiaSvg() + { + Tag = "arrowL", + TranslationY = 5, + TintColor = Colors.White, + SvgString = App.Current.Resources.Get("SvgChatFromLeft"), + VerticalOptions = LayoutOptions.Start, + HorizontalOptions = LayoutOptions.Start, + HeightRequest = 8, + WidthRequest = 8 + }.WithParent(MainHorizontalStack); + + #region BUBBLE + + MainFrame = new SkiaShape() + { + Tag = "MainFrame", + Margin = new Thickness(0, 0, 0, 8), + CornerRadius = 8, + BackgroundColor = App.Current.Resources.Get("ColorPaperSecondary"), + // StrokeColor = StaticResources.ColorPaperSecondary, + HorizontalOptions = LayoutOptions.Fill, + VerticalOptions = LayoutOptions.Fill, + }.WithParent(MainHorizontalStack); + + MessageStack = new SkiaLayout() + { + Tag = "MessageStack", + IsClippedToBounds = true, + Type = LayoutType.Row, + Spacing = 4, + VerticalOptions = LayoutOptions.Fill, + HorizontalOptions = LayoutOptions.End + }.WithParent(MainFrame); + + Banner = new SkiaImage() + { + IsVisible = false, + Tag = "Banner", + Background = App.Current.Resources.Get("ColorPrimaryLight"), + //Margin = new Thickness(0, 0, 0, 0), + HorizontalOptions = LayoutOptions.Fill, + VerticalOptions = LayoutOptions.Start, + HeightRequest = 200, + }.WithParent(MessageStack).With((c) => + { + c.OnError += OnImageError; + }); + + MessageStack.BreakLine(); + + IconAttachment = new SkiaSvg() + { + Tag = "Attachment", + Margin = new Thickness(8, 0, 0, 0), + SvgString = App.Current.Resources.Get("SvgAttachment"), + VerticalOptions = LayoutOptions.Center, + HorizontalOptions = LayoutOptions.Start, + TintColor = App.Current.Resources.Get("ColorAccentLight"), + HeightRequest = 16, + WidthRequest = 16 + }.WithParent(MessageStack); + + LabelMessage = new SkiaLabel() + { + Tag = "Message", + //TintColor = Colors.FloralWhite, + LineBreakMode = LineBreakMode.WordWrap, + MaxLines = -1, + Text = $"Message", + FontSize = 12, + HorizontalOptions = LayoutOptions.Fill, + Margin = new Thickness(8, 8, 8, 8), + VerticalOptions = LayoutOptions.Center, + TextColor = App.Current.Resources.Get("ColorText"), + }.WithParent(MessageStack).With((c) => + { + c.Measured += OnTextSizeChanged; + }); + + LabelTime = new SkiaLabel() + { + Tag = "LabelTime", + LineBreakMode = LineBreakMode.NoWrap, + MaxLines = 1, + Text = $"Time", + FontSize = 9, + HorizontalOptions = LayoutOptions.End, + Margin = new Thickness(0, 0, 2, 2), + VerticalOptions = LayoutOptions.End, + //TextColor = StaticResources.ColorPrimaryLight, + }.WithParent(MainFrame); + + IconWasSent = new SkiaSvg() + { + Tag = "WasDelivered", + Margin = new Thickness(0, 0, 7, 3), + TintColor = App.Current.Resources.Get("ColorPrimaryLight"), + SvgString = App.Current.Resources.Get("SvgCheck"), + VerticalOptions = LayoutOptions.End, + HorizontalOptions = LayoutOptions.End, + HeightRequest = 11, + WidthRequest = 11 + }.WithParent(MainFrame); + + IconWasDelivered = new SkiaSvg() + { + Tag = "WasSeen", + //TranslationY = 5, + Margin = new Thickness(0, 0, 3, 3), + TintColor = App.Current.Resources.Get("ColorPrimaryLight"), + SvgString = App.Current.Resources.Get("SvgCheck"), + VerticalOptions = LayoutOptions.End, + HorizontalOptions = LayoutOptions.End, + HeightRequest = 11, + WidthRequest = 11 + }.WithParent(MainFrame); + + #endregion + + BubbleArrowOutcoming = new SkiaSvg() + { + Tag = "arrowR", + TranslationY = 5, + SvgString = App.Current.Resources.Get("SvgChatFromRight"), + VerticalOptions = LayoutOptions.Start, + HorizontalOptions = LayoutOptions.Start, + HeightRequest = 8, + WidthRequest = 8 + }.WithParent(MainHorizontalStack); + + // GESTURES + AttachGestures(); + + } + + + + public bool ShowDate { get; set; } + + void UpdateContainer(ChatMessage item) + { + if (item == null) + return; + + IconWasSent.IsVisible = false; + IconWasDelivered.IsVisible = false; + + if (ShowDate) + { + FrameNewDate.IsVisible = true; + MainHorizontalStack.Padding = new Thickness(0, 24 + 16, 0, 0); + } + else + { + FrameNewDate.IsVisible = false; + MainHorizontalStack.Padding = new Thickness(0, 0, 0, 0); + } + + if (item.Outgoing) + { + LabelTime.Margin = new Thickness(0, 0, 10, 2); + + LabelTime.TextColor = App.Current.Resources.Get("ColorPrimary"); + MainFrame.Background = App.Current.Resources.Get("ColorPaperSecondary"); + + MainHorizontalStack.HorizontalOptions = LayoutOptions.End; + BubbleArrowIncoming.IsVisible = false; + //BubbleArrowOutcoming.IsVisible = true; + if (item.IsFirst) + { + BubbleArrowOutcoming.IsVisible = true; + MainFrame.Margin = new Thickness(0, 0, 0, 8); + } + else + { + BubbleArrowOutcoming.IsVisible = false; + MainFrame.Margin = new Thickness(0, 0, 8, 8); + } + + } + else + { + LabelTime.Margin = new Thickness(0, 0, 2, 2); + + LabelTime.TextColor = App.Current.Resources.Get("ColorPrimaryLight"); + MainFrame.Background = Colors.White; + + MainHorizontalStack.HorizontalOptions = LayoutOptions.Start; + BubbleArrowOutcoming.IsVisible = false; + if (item.IsFirst) + { + BubbleArrowIncoming.IsVisible = true; + MainFrame.Margin = new Thickness(0, 0, 0, 8); + } + else + { + BubbleArrowIncoming.IsVisible = false; + MainFrame.Margin = new Thickness(8, 0, 0, 8); + } + } + + if (Template == ChatMetaType.File) + { + IconAttachment.IsVisible = true; + LabelMessage.TextColor = App.Current.Resources.Get("ColorAccentLight"); + } + else + { + IconAttachment.IsVisible = false; + LabelMessage.TextColor = App.Current.Resources.Get("ColorText"); + } + + if (Template == ChatMetaType.Image || Template == ChatMetaType.Video) + { + Banner.HeightRequest = 200; + Banner.IsVisible = true; + } + else + if (Template == ChatMetaType.Article) + { + Banner.HeightRequest = 80; + Banner.IsVisible = true; + } + else + { + Banner.IsVisible = false; + } + + } + + void UpdateStatus(ChatMessage item) + { + if (item == null) + return; + + if (item.Outgoing) + { + if (item.Read) + { + IconWasSent.TintColor = Colors.DeepSkyBlue; + IconWasDelivered.TintColor = Colors.DeepSkyBlue; + IconWasSent.IsVisible = true; + IconWasDelivered.IsVisible = true; + } + else + { + IconWasSent.TintColor = App.Current.Resources.Get("ColorPrimary"); + IconWasDelivered.TintColor = App.Current.Resources.Get("ColorPrimary"); + IconWasSent.IsVisible = item.Sent; + IconWasDelivered.IsVisible = item.Delivered; + } + } + } + + void UpdateContent(ChatMessage item) + { + if (item == null) + return; + + if (ShowDate) + LabelFirstDate.Text = item.WhenDesc; + else + LabelFirstDate.Text = ""; + + if (item.Outgoing) + { + LabelMessage.Text = item.Text + " "; + } + else + { + LabelMessage.Text = item.Text + " "; + } + + LabelMessage.Measure(Width - 100, Height); //reduce by side padding + + LabelTime.Text = item.DisplayTime; + + if (Template == ChatMetaType.Image || Template == ChatMetaType.Video) + { + Banner.Source = item.ImageMain; + } + else + if (Template == ChatMetaType.Article) + { + Banner.Source = item.Metadata.Image; + } + else + { + Banner.Source = null; + } + } + + public ChatMetaType Template { get; protected set; } = ChatMetaType.Default; + + void SetContentFull(ChatMessage item) + { + IsNew = item.Notify; + Template = item.PresentAs; + ShowDate = item.IsFirstDate; + CanBeTapped = Template != ChatMetaType.Default; + + //if (_oldContext != null) + //{ + // if (_oldContext.Outgoing != item.Outgoing || _oldContext.PresentAs != item.PresentAs) + // UpdateBubble(item); + //} + //else + //{ + // UpdateBubble(item); + //} + UpdateContainer(item); + UpdateContent(item); + UpdateStatus(item); + Update(); + } + + protected override void OnBindingContextChanged() + { + var item = this.BindingContext as ChatMessage; + if (item != null && _oldContext != item) + { + if (_oldContext != null) + _oldContext.PropertyChanged -= OnContextPropertyChanged; + + item.PropertyChanged += OnContextPropertyChanged; + _oldContext = item; + + SetContentFull(item); + } + + } + + + + + } + +} diff --git a/src/samples/Sandbox/Chat/WidgetListCell.cs b/src/samples/Sandbox/Chat/WidgetListCell.cs new file mode 100644 index 00000000..b714f487 --- /dev/null +++ b/src/samples/Sandbox/Chat/WidgetListCell.cs @@ -0,0 +1,209 @@ +using System.Diagnostics; +using System.Windows.Input; +using AppoMobi.Maui.Gestures; +using DrawnUi.Maui.Extensions; +using Newtonsoft.Json; +using Sandbox; + + +namespace AppoMobi.Forms.Controls +{ + public enum ChatMetaType + { + Default, + Image, + Video, + Article, + File, + Separator, + System + } + + public class WidgetListCell : SkiaLayout + { + public static SkiaImage CreateStandartAvatar(ISkiaControl parent, double scale) + { + return new SkiaImage() + { + Tag = "avatar", + BackgroundColor = App.Current.Resources.Get("ColorPrimaryLight"), + Margin = new Thickness(2), + HorizontalOptions = LayoutOptions.Fill, + VerticalOptions = LayoutOptions.Fill, + IsClippedToBounds = true, + Clipping = (path, dest) => + { + //avatar circle + path.AddCircle(dest.Left + dest.Width / 2, dest.Top + dest.Height / 2, + dest.Width / 2); + } + }.WithParent(parent); + } + + public static SkiaSvg CreateStandartEmptyAvatar(ISkiaControl parent, double scale) + { + return new SkiaSvg() + { + Tag = "EmptyAvatar", + IsVisible = false, + TranslationY = -2, + IsClippedToBounds = true, + SvgString = App.Current.Resources.Get("SvgAvatarEmpty"), + VerticalOptions = LayoutOptions.Center, + HorizontalOptions = LayoutOptions.Center, + HeightRequest = 32, + WidthRequest = 32 + }.WithParent(parent); + } + + public static bool DisableShadows => false; + + public static readonly BindableProperty CommandTappedProperty = BindableProperty.Create(nameof(CommandTapped), typeof(ICommand), typeof(WidgetListCell), + null); + public ICommand CommandTapped + { + get { return (ICommand)GetValue(CommandTappedProperty); } + set { SetValue(CommandTappedProperty, value); } + } + + public static readonly BindableProperty CommandLongPressingProperty = BindableProperty.Create(nameof(CommandLongPressing), typeof(ICommand), typeof(WidgetListCell), + null); + public ICommand CommandLongPressing + { + get { return (ICommand)GetValue(CommandLongPressingProperty); } + set { SetValue(CommandLongPressingProperty, value); } + } + + public SkiaShape MainFrame { get; set; } + + public bool HasGestures + { + get + { + return _touchHandler != null; + } + } + + protected void DetachGestures() + { + if (!HasGestures) + return; + + _touchHandler.Dispose(); + } + + + protected void AttachGestures() + { + if (HasGestures) + return; + + _touchHandler = new TouchEffect + { + Capture = true + }; + _touchHandler.LongPressing += OnLongPressing; + _touchHandler.Tapped += OnTapped; + _touchHandler.Down += OnDown; + _touchHandler.Up += OnUp; + _touchHandler.TouchAction += OnTouch; + this.Effects.Add(_touchHandler); + } + + public bool IsSelected { get; set; } + + public bool CanBeSelected { get; set; } = true; + + public bool CanBeTapped { get; set; } = true; + + public bool IsNew { get; set; } + + private void OnLongPressing(object sender, TouchActionEventArgs args) + { + + Debug.WriteLine($"[TOUCH] LongPressing!"); + + if (CommandLongPressing != null) + { + if (CanBeSelected) + { + IsSelected = true; + Update(); + } + Device.StartTimer(TimeSpan.FromMilliseconds(2500), () => + { + if (CanBeSelected) + { + IsSelected = false; + Update(); + } + return false; + }); + CommandLongPressing.Execute(BindingContext); + + } + + } + + private bool lockTap; + private TouchEffect _touchHandler; + + private void OnTapped(object sender, TouchActionEventArgs args) + { + if (!CanBeTapped) + return; + + if (lockTap) + return; + + Debug.WriteLine($"[TOUCH] Tapped!"); + + lockTap = true; + if (CanBeSelected) + { + IsSelected = true; + Update(); + } + + Device.StartTimer(TimeSpan.FromMilliseconds(50), () => + { + //invoke action + CommandTapped?.Execute(BindingContext); + + Device.StartTimer(TimeSpan.FromMilliseconds(2500), () => + { + if (CanBeSelected) + { + IsSelected = false; + Update(); + } + lockTap = false; + return false; + }); + + return false; + }); + + } + + private void OnUp(object sender, TouchActionEventArgs args) + { + Debug.WriteLine($"[TOUCH] UP"); + + //MainFrame.StrokeWidth = 1 / RenderingScale; // 1 precise pixel + //MainFrame.StrokeColor = StaticResources.DropShadow; + //Update(); + } + + private void OnDown(object sender, TouchActionEventArgs args) + { + Debug.WriteLine($"[TOUCH] DOWN"); + } + + private void OnTouch(object sender, TouchActionEventArgs args) + { + Debug.WriteLine($"[TOUCH] {args.Type} {JsonConvert.SerializeObject(args)}"); + } + + } +} diff --git a/src/samples/Sandbox/MainPageCodeDeformation.cs b/src/samples/Sandbox/MainPageCodeDeformation.cs new file mode 100644 index 00000000..d810a368 --- /dev/null +++ b/src/samples/Sandbox/MainPageCodeDeformation.cs @@ -0,0 +1,119 @@ +using Sandbox.Views; +using Canvas = DrawnUi.Maui.Views.Canvas; + +namespace Sandbox +{ + + + public class MainPageCodeDeformation : BasePage, IDisposable + { + Canvas Canvas; + + public void Dispose() + { + this.Content = null; + Canvas?.Dispose(); + } + + + public static (SkiaShape Shape, Point Point) CreateSkiaLine(Color color, double x1, double y1, double x2, double y2, int width = 5) + { + var distX = x1 - x2; + var distY = y1 - y2; + var lineDist = Math.Sqrt(distX * distX + distY * distY); + + var lineCenterX = x2 + (distX / 2); + var lineCenterY = y2 + (distY / 2); + var lineFinalX = lineCenterX - (lineDist / 2); + + var lineRotateAngle = Math.Acos(distX / lineDist) * (180 / Math.PI); + + var line = new SkiaShape() + { + Type = ShapeType.Rectangle, + BackgroundColor = color, + WidthRequest = lineDist, + HeightRequest = width, + Rotation = (distY < 0) ? 360 - lineRotateAngle : lineRotateAngle + }; + + return (line, new Point(lineFinalX, lineCenterY)); + } + + public MainPageCodeDeformation() + { +#if DEBUG + HotReloadService.UpdateApplicationEvent += ReloadUI; +#endif + Build(); + } + + private int _reloads; + + void Build() + { + Canvas?.Dispose(); + + Canvas = new Canvas() + { + Gestures = GesturesMode.Enabled, + HardwareAcceleration = HardwareAccelerationMode.Disabled, + + VerticalOptions = LayoutOptions.Fill, + HorizontalOptions = LayoutOptions.Fill, + BackgroundColor = Colors.LightGray, + + Content = new SkiaLayout() + { + VerticalOptions = LayoutOptions.Fill, + HorizontalOptions = LayoutOptions.Fill, + Children = new List() + { + new SkiaLayout() + { + Children = new List() + { + CreateSkiaLine(Colors.Red, 50, 50, 300, 102, 5).Shape + + } + }.With((c) => + { + foreach (Point node in new Point[] + { + new(150, 150), + new(250,200), + new(250,150), + new(250,300), + new(250,250), + new(350,150), + new(450,150), + new(300,250), + new(300,300), + new(300,360), + new(150,350) + }) + { + c.AddSubView(CreateSkiaLine(Colors.Blue, 75, 175, node.X, node.Y).Shape); + } + }) + } + } + + + }; + + _reloads++; + + this.Content = Canvas; + } + + private void ReloadUI(Type[] obj) + { + MainThread.BeginInvokeOnMainThread(() => + { + Build(); + }); + } + + } +} diff --git a/src/samples/Sandbox/MainPageRepro.xaml b/src/samples/Sandbox/MainPageRepro.xaml index 1dd41a69..afc5ca8f 100644 --- a/src/samples/Sandbox/MainPageRepro.xaml +++ b/src/samples/Sandbox/MainPageRepro.xaml @@ -3,25 +3,53 @@ x:Class="MauiNet8.MainPageRepro" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + xmlns:draw="http://schemas.appomobi.com/drawnUi/2023/draw" xmlns:mauiNet8="clr-namespace:MauiNet8" xmlns:sandbox="clr-namespace:Sandbox" xmlns:views="clr-namespace:Sandbox.Views" x:DataType="sandbox:MainPageViewModel" BackgroundColor="#000000"> + + + + + + + + + + diff --git a/src/samples/Sandbox/MainPageRepro.xaml.cs b/src/samples/Sandbox/MainPageRepro.xaml.cs index a8d3e9d0..bcd0b444 100644 --- a/src/samples/Sandbox/MainPageRepro.xaml.cs +++ b/src/samples/Sandbox/MainPageRepro.xaml.cs @@ -19,8 +19,6 @@ public MainPageRepro() } } - - } public class DebugCanvas : Canvas @@ -29,15 +27,14 @@ public class DebugCanvas : Canvas public DebugCanvas() { - var shape = CreateSkiaLine(Colors.White, 10, 10, 100, 100, 5); - Children.Add(shape.Item1); + } protected override void Draw(SkiaDrawingContext context, SKRect destination, float scale) { base.Draw(context, destination, scale); - + /* if (Height > 0 && Width > 0) { var canvas = context.Canvas; @@ -66,31 +63,9 @@ protected override void Draw(SkiaDrawingContext context, SKRect destination, flo } + */ } - private static (SkiaShape, Point) CreateSkiaLine(Color color, double x1, double y1, double x2, double y2, int width = 5) - { - var distX = x1 - x2; - var distY = y1 - y2; - var lineDist = Math.Sqrt(distX * distX + distY * distY); - - var lineCenterX = x2 + (distX / 2); - var lineCenterY = y2 + (distY / 2); - var lineFinalX = lineCenterX - (lineDist / 2); - - var lineRotateAngle = Math.Acos(distX / lineDist) * (180 / Math.PI); - - var line = new SkiaShape() - { - Type = ShapeType.Rectangle, - BackgroundColor = color, - WidthRequest = lineDist, - HeightRequest = width, - Rotation = (distY < 0) ? 360 - lineRotateAngle : lineRotateAngle - }; - - return (line, new Point(lineFinalX, lineCenterY)); - } (SKSurface, SKImage) CreateImage(int width, int height, float renderingScale) { @@ -120,4 +95,6 @@ private static (SkiaShape, Point) CreateSkiaLine(Color color, double x1, double return (surface, image); } + + } diff --git a/src/samples/Sandbox/Resources/Svg.xaml b/src/samples/Sandbox/Resources/Svg.xaml index 9a252f07..514256a9 100644 --- a/src/samples/Sandbox/Resources/Svg.xaml +++ b/src/samples/Sandbox/Resources/Svg.xaml @@ -4,6 +4,12 @@ xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> + + + ]]> + + + + MainDrawnCells.xaml + + + MainPageXaml2PdfPages.xaml @@ -371,6 +376,9 @@ MSBuild:Compile + + MSBuild:Compile + MSBuild:Compile diff --git a/src/samples/Sandbox/ViewModels/ChatMessage.cs b/src/samples/Sandbox/ViewModels/ChatMessage.cs new file mode 100644 index 00000000..8e8430ae --- /dev/null +++ b/src/samples/Sandbox/ViewModels/ChatMessage.cs @@ -0,0 +1,436 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using AppoMobi.Forms.Controls; +using AppoMobi.Specials; +using Newtonsoft.Json; + +namespace Sandbox +{ + public class ChatMessageMetadata + { + public ChatMetaType Type { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public string Url { get; set; } + public string Domain { get; set; } + public string SiteName { get; set; } + public string Image { get; set; } + public string Video { get; set; } + } + + public enum ChatMessageType + { + Default, + Attachment, + Call, + Action //use attachment field for more + } + + public class ChatMessage : MockChatViewModel.ChatMessageDto, INotifyPropertyChanged + { + public string Id { get; set; } + + public int DbId { get; set; } + + public string Author { get; set; } + + public string Meta { get; set; } + + public ChatMessageType MessageType { get; set; } + + public DateTime CreatedTime { get; set; } + + public string Group { get; set; } + + [JsonProperty("AType")] + public int AttachmentType { get; set; } + + + + [JsonProperty("AStatus")] + public int AttachmentStatus { get; set; } + + //todo attachments + [JsonProperty("Thumb")] + public string Thumbnail { get; set; } + + /// + /// Blacklisted + /// + public bool Blocked { get; set; } + + /// + /// by server or from client when sending + /// + public bool MetaChecked { get; set; } + + private bool _Notify; + public bool Notify + { + get { return _Notify; } + set + { + if (_Notify != value) + { + _Notify = value; + OnPropertyChanged(); + } + } + } + + public bool Outgoing + { + get + { + return Author == "71980d5c-2659-4ccb-ba79-e4433514fae5"; + } + } + + #region INotifyPropertyChanged + + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + var changed = PropertyChanged; + if (changed == null) + return; + + changed.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + #endregion + + #region DTO UPDATABLES + + private int _Status; + public new int Status + { + get { return _Status; } + set + { + if (_Status != value) + { + _Status = value; + OnPropertyChanged(); + } + } + } + + private string _Text; + public new string Text + { + get { return _Text; } + set + { + if (_Text != value) + { + _Text = value; + OnPropertyChanged(); + } + } + } + + private string _Attachment; + public new string Attachment + { + get { return _Attachment; } + set + { + if (_Attachment != value) + { + _Attachment = value; + OnPropertyChanged(); + } + } + } + + #endregion + + private string _ImageMain; + public string ImageMain + { + get { return _ImageMain; } + set + { + if (_ImageMain != value) + { + _ImageMain = value; + OnPropertyChanged(); + } + } + } + + public void Init() + { + + try + { + if (!string.IsNullOrEmpty(this.Meta)) + Metadata = JsonConvert.DeserializeObject(this.Meta); + } + catch (Exception e) + { + Console.WriteLine(e); + } + + if (!string.IsNullOrEmpty(Thumbnail)) + ImageMain = Thumbnail; + else + ImageMain = Attachment; + + Sent = Status == 1; + + //RU + DisplayDateTime = $"{this.CreatedTime.UtcToLocal():HH:mm d.m.yy}"; + DisplayTime = $"{this.CreatedTime.UtcToLocal():HH:mm}"; + + var lastOnline = CreatedTime.UtcToLocal(); + var todayDate = DateTime.Now; + var today = todayDate.Date; + var when = lastOnline.ToLocalTime().Date; + var delta = when - today; + var days = Math.Abs(delta.Days); + + WhenDesc = "todo when";//ProjectExtensions.CorrectStringUponNumber(days, + // ResStrings.ExplainDate_Today, + // ResStrings.ExplainDate_Yest, + // ResStrings.ExplainDate_X1past, + // ResStrings.ExplainDate_X2past, + // ResStrings.ExplainDate_Xpast); + + OnPropertyChanged("Outgoing"); + OnPropertyChanged("IsText"); + + } + + private bool _Read; + public bool Read + { + get { return _Read; } + set + { + if (_Read != value) + { + _Read = value; + OnPropertyChanged(); + } + } + } + + + + private string _DisplayDateTime; + public string DisplayDateTime + { + get { return _DisplayDateTime; } + set + { + if (_DisplayDateTime != value) + { + _DisplayDateTime = value; + OnPropertyChanged(); + } + } + } + + private string _WhenDesc; + public string WhenDesc + { + get { return _WhenDesc; } + set + { + if (_WhenDesc != value) + { + _WhenDesc = value; + OnPropertyChanged(); + } + } + } + + private bool _IsFirst; + public bool IsFirst + { + get { return _IsFirst; } + set + { + if (_IsFirst != value) + { + _IsFirst = value; + OnPropertyChanged(); + } + } + } + + private bool _IsFirstDate; + public bool IsFirstDate + { + get { return _IsFirstDate; } + set + { + if (_IsFirstDate != value) + { + _IsFirstDate = value; + OnPropertyChanged(); + } + } + } + + private bool _Delivered; + public bool Delivered + { + get { return _Delivered; } + set + { + if (_Delivered != value) + { + _Delivered = value; + OnPropertyChanged(); + } + } + } + + private bool _Sent; + public bool Sent + { + get { return _Sent; } + set + { + if (_Sent != value) + { + _Sent = value; + OnPropertyChanged(); + } + } + } + + + private ChatMessageMetadata _metadata; + public ChatMessageMetadata Metadata + { + get { return _metadata; } + set + { + if (_metadata != value) + { + _metadata = value; + OnPropertyChanged(); + } + } + } + + private string _DisplayTime; + public string DisplayTime + { + get { return _DisplayTime; } + set + { + if (_DisplayTime != value) + { + _DisplayTime = value; + OnPropertyChanged(); + } + } + } + + private bool _IsBusy; + public bool IsBusy + { + get { return _IsBusy; } + set + { + if (_IsBusy != value) + { + _IsBusy = value; + OnPropertyChanged(); + } + } + } + + + public ChatMetaType PresentAs + { + get + { + if (AttachmentType == (int)ChatMetaType.Separator) + return ChatMetaType.Separator; + + if (AttachmentType == (int)ChatMetaType.System) + return ChatMetaType.System; + + if (AttachmentType == (int)ChatMetaType.Image) + return ChatMetaType.Image; + + if (AttachmentType == (int)ChatMetaType.Video) + return ChatMetaType.Video; + + if (AttachmentType > 0) + return ChatMetaType.File; + + if (Metadata != null) + return Metadata.Type; + + return ChatMetaType.Default; + } + } + + public bool Attached { get; set; } + + protected string Linkify(string SearchText) + { + if (string.IsNullOrEmpty(SearchText)) + return ""; + // this will find links like: + // http://www.mysite.com + // as well as any links with other characters directly in front of it like: + // href="http://www.mysite.com" + // you can then use your own logic to determine which links to linkify + Regex regx = new Regex(@"\b(((\S+)?)(@|mailto\:|(news|(ht|f)tp(s?))\://)\S+)\b", RegexOptions.IgnoreCase); + SearchText = SearchText.Replace(" ", " "); + MatchCollection matches = regx.Matches(SearchText); + + foreach (Match match in matches) + { + if (match.Value.StartsWith("http")) + { // if it starts with anything else then dont linkify -- may already be linked! + SearchText = SearchText.Replace(match.Value, "" + match.Value + ""); + } + } + + return SearchText; + } + + // var invisibleGlyph = "⠀"; + + + + + private string _PlayerName = "Аноним"; + public string PlayerName + { + get { return _PlayerName; } + set + { + if (_PlayerName != value) + { + _PlayerName = value; + OnPropertyChanged(); + } + } + } + + private Player _player; + public Player Player + { + get { return _player; } + set + { + if (_player != value) + { + _player = value; + OnPropertyChanged(); + } + } + } + } +} diff --git a/src/samples/Sandbox/ViewModels/MockChat2ViewModel.cs b/src/samples/Sandbox/ViewModels/MockChat2ViewModel.cs new file mode 100644 index 00000000..c20a4420 --- /dev/null +++ b/src/samples/Sandbox/ViewModels/MockChat2ViewModel.cs @@ -0,0 +1,80 @@ +using System.Collections.ObjectModel; +using Newtonsoft.Json; + +namespace Sandbox +{ + public class MockChat2ViewModel : BindableObject + { + + public class ChatMessageDto + { + public string Id { get; set; } + /// + /// Profile Key + /// + public string Author { get; set; } + + public string Text { get; set; } + + /// + /// Utc + /// + public DateTime CreatedTime { get; set; } + } + + + + private bool _IsBusy; + public bool IsBusy + { + get + { + return _IsBusy; + } + set + { + if (_IsBusy != value) + { + _IsBusy = value; + OnPropertyChanged(); + } + } + } + + public ObservableCollection Items { get; } = new(); + + public async Task LoadData() + { + + try + { + IsBusy = true; + + using var stream = await FileSystem.OpenAppPackageFileAsync("Json/chat.json"); + using var reader = new StreamReader(stream); + var json = await reader.ReadToEndAsync(); + var messages = JsonConvert.DeserializeObject>(json); + + MainThread.BeginInvokeOnMainThread(() => + { + foreach (var chatMessage in messages) + { + Items.Add(chatMessage); + } + }); + + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + finally + { + IsBusy = false; + } + + + } + } +} diff --git a/src/samples/Sandbox/ViewModels/MockChatViewModel.cs b/src/samples/Sandbox/ViewModels/MockChatViewModel.cs index 10dbe4d2..b3b8e5b2 100644 --- a/src/samples/Sandbox/ViewModels/MockChatViewModel.cs +++ b/src/samples/Sandbox/ViewModels/MockChatViewModel.cs @@ -1,29 +1,8 @@ -using Newtonsoft.Json; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Runtime.CompilerServices; +using System.Collections.ObjectModel; +using Newtonsoft.Json; namespace Sandbox; -public class ChatMessage : MockChatViewModel.ChatMessageDto, INotifyPropertyChanged -{ - - #region INotifyPropertyChanged - - public event PropertyChangedEventHandler PropertyChanged; - - protected void OnPropertyChanged([CallerMemberName] string propertyName = "") - { - var changed = PropertyChanged; - if (changed == null) - return; - - changed.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - #endregion - -} - public class MockChatViewModel : BindableObject { @@ -97,4 +76,4 @@ public async Task LoadData() } -} \ No newline at end of file +} diff --git a/src/samples/Sandbox/ViewModels/Player.cs b/src/samples/Sandbox/ViewModels/Player.cs new file mode 100644 index 00000000..a9cfadc0 --- /dev/null +++ b/src/samples/Sandbox/ViewModels/Player.cs @@ -0,0 +1,290 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using AppoMobi.Specials; +using Newtonsoft.Json; + +namespace Sandbox +{ + public class Player : INotifyPropertyChanged, IHasStringId, ICanBeSelected//, IPatchableDto + { + + private string _Id; + public string Id + { + get + { + return _Id; + } + set + { + if (_Id != value) + { + _Id = value; + OnPropertyChanged(); + } + } + } + + public bool IsMe + { + get + { + return "71980d5c-2659-4ccb-ba79-e4433514fae5" == this.Id; + } + } + + public Player() + { + Id = Guid.NewGuid().ToString(); + } + + private string _PersonDesc; + public string PersonDesc + { + get { return _PersonDesc; } + set + { + if (_PersonDesc != value) + { + _PersonDesc = value; + OnPropertyChanged(); + } + } + } + + private bool _IsOnline; + public new bool IsOnline + { + get + { + return _IsOnline; + } + set + { + if (_IsOnline != value) + { + _IsOnline = value; + OnPropertyChanged(); + OnPropertyChanged("OnlineStatus"); + OnPropertyChanged("OnlineDesc"); + } + } + } + + private bool _IsDND; + public new bool IsDND + { + get { return _IsDND; } + set + { + if (_IsDND != value) + { + _IsDND = value; + OnPropertyChanged(); + OnPropertyChanged("OnlineStatus"); + OnPropertyChanged("OnlineDesc"); + } + } + } + + [JsonIgnore] + public int OnlineStatus + { + get + { + if (IsDND && IsOnline) + { + return 2; + } + + return IsOnline ? 1 : 0; + } + //set + //{ + // if (_OnlineStatus != value) + // { + // _OnlineStatus = value; + // OnPropertyChanged(); + // } + //} + } + + + + + private string _ImageMain; + public string ImageMain + { + get { return _ImageMain; } + set + { + if (_ImageMain != value) + { + _ImageMain = value; + OnPropertyChanged(); + } + } + } + + + + private string _SpokenLanguagesDesc; + public string SpokenLanguagesDesc + { + get { return _SpokenLanguagesDesc; } + set + { + if (_SpokenLanguagesDesc != value) + { + _SpokenLanguagesDesc = value; + OnPropertyChanged(); + } + } + } + + + + private bool _Selected; + public bool Selected + { + get { return _Selected; } + set + { + if (_Selected != value) + { + _Selected = value; + OnPropertyChanged("Selected"); + } + } + } + + public void RaiseProperties() + { + var props = this.GetType().GetProperties(); + foreach (var property in props) + { + if (property.CanRead) + { + OnPropertyChanged(property.Name); + } + } + } + + private bool _NeedShowLetter; + public bool NeedShowLetter + { + get { return _NeedShowLetter; } + set + { + if (_NeedShowLetter != value) + { + _NeedShowLetter = value; + OnPropertyChanged(); + } + } + } + + private string _DisplayLetter; + public string DisplayLetter + { + get { return _DisplayLetter; } + set + { + if (_DisplayLetter != value) + { + _DisplayLetter = value; + OnPropertyChanged(); + } + } + } + + + private decimal _PayAmount; + public decimal PayAmount + { + get { return _PayAmount; } + set + { + if (_PayAmount != value) + { + _PayAmount = value; + OnPropertyChanged(); + } + } + } + + private bool _ManualAmount; + public bool ManualAmount + { + get { return _ManualAmount; } + set + { + if (_ManualAmount != value) + { + _ManualAmount = value; + OnPropertyChanged(); + } + } + } + + private bool _IsBusy; + public bool IsBusy + { + get { return _IsBusy; } + set + { + if (_IsBusy != value) + { + _IsBusy = value; + OnPropertyChanged(); + } + } + } + + + + + #region INotifyPropertyChanged + + public void RaisePropertyChanged(string propertyName) + { + var changed = PropertyChanged; + if (changed == null) + return; + + changed.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public event PropertyChangedEventHandler PropertyChanged; + protected void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + var changed = PropertyChanged; + if (changed == null) + return; + + changed.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + #endregion + + private bool _Notify; + public bool Notify + { + get { return _Notify; } + set + { + if (_Notify != value) + { + _Notify = value; + OnPropertyChanged(); + } + } + } + + + + + + + + } +}