From 7c617a443411b5b07a8d1a2e9ba398db7f67aa7b Mon Sep 17 00:00:00 2001 From: Nick Kovalsky Date: Wed, 4 Dec 2024 09:47:33 +0300 Subject: [PATCH 1/8] skiabutton apply backgroundcolor and backfround to mainframe if any removed tintcolor --- src/Engine/Controls/Button/SkiaButton.cs | 38 ++++++++++++++---------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/Engine/Controls/Button/SkiaButton.cs b/src/Engine/Controls/Button/SkiaButton.cs index 56556632..111d84c4 100644 --- a/src/Engine/Controls/Button/SkiaButton.cs +++ b/src/Engine/Controls/Button/SkiaButton.cs @@ -122,7 +122,8 @@ public virtual void ApplyProperties() if (MainFrame != null) { - MainFrame.BackgroundColor = this.TintColor; + MainFrame.BackgroundColor = this.BackgroundColor; + MainFrame.Background = this.Background; MainFrame.CornerRadius = this.CornerRadius; } } @@ -526,23 +527,8 @@ public object CommandLongPressingParameter } - - public static readonly BindableProperty TintColorProperty = BindableProperty.Create( - nameof(TintColor), - typeof(Color), - typeof(SkiaButton), - RedColor, - propertyChanged: NeedApplyProperties); - - protected SKPoint _lastDownPts; - public Color TintColor - { - get { return (Color)GetValue(TintColorProperty); } - set { SetValue(TintColorProperty, value); } - } - public static readonly BindableProperty TextColorProperty = BindableProperty.Create( nameof(TextColor), typeof(Color), @@ -577,7 +563,27 @@ private static void NeedApplyProperties(BindableObject bindable, object oldvalue } } + protected override void OnPropertyChanged(string propertyName = null) + { + base.OnPropertyChanged(propertyName); + + if (propertyName.IsEither(nameof(Background), nameof(BackgroundColor))) + { + ApplyProperties(); + } + } + #endregion + protected override bool SetupBackgroundPaint(SKPaint paint, SKRect destination) + { + if (MainFrame != null) + { + //will paint its background instead + return false; + } + + return base.SetupBackgroundPaint(paint, destination); + } } \ No newline at end of file From 9f84182dfbd302989118601d32be19854d69d091 Mon Sep 17 00:00:00 2001 From: Nick Kovalsky Date: Wed, 4 Dec 2024 09:48:54 +0300 Subject: [PATCH 2/8] templated rendering optimizations --- src/Engine/Controls/Labels/SkiaLabelFps.cs | 6 +- .../Draw/Base/SkiaControl.Invalidation.cs | 10 ++-- src/Engine/Draw/Base/SkiaControl.Shared.cs | 17 ++++-- src/Engine/Draw/Layout/LayoutStructure.cs | 10 +++- .../Draw/Layout/SkiaLayout.ColumnRow.cs | 16 ++++- .../Draw/Layout/SkiaLayout.ViewsAdapter.cs | 2 + src/Engine/Draw/Layout/SkiaLayout.cs | 11 ++-- src/Engine/Internals/Models/ControlInStack.cs | 6 +- src/Engine/Internals/Models/DynamicGrid.cs | 4 +- src/Engine/Views/DrawnView.cs | 16 ++--- .../Views/MainPageDynamicHeightCells.xaml | 58 +++++++++++-------- 11 files changed, 101 insertions(+), 55 deletions(-) diff --git a/src/Engine/Controls/Labels/SkiaLabelFps.cs b/src/Engine/Controls/Labels/SkiaLabelFps.cs index 8a6796c3..2b6f04b8 100644 --- a/src/Engine/Controls/Labels/SkiaLabelFps.cs +++ b/src/Engine/Controls/Labels/SkiaLabelFps.cs @@ -1,5 +1,4 @@ -using DrawnUi.Maui.Draw; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; namespace DrawnUi.Maui.Draw; @@ -8,6 +7,7 @@ public class SkiaLabelFps : SkiaLabel, ISkiaAnimator public SkiaLabelFps() { IsParentIndependent = true; + WillNotUpdateParent = true; Tag = "FPS"; MaxLines = 1; @@ -130,4 +130,4 @@ public void Start(double delayMs = 0) IsRunning = RegisterAnimator(this); } } -} \ No newline at end of file +} diff --git a/src/Engine/Draw/Base/SkiaControl.Invalidation.cs b/src/Engine/Draw/Base/SkiaControl.Invalidation.cs index ff0b0bd4..ea93ea0a 100644 --- a/src/Engine/Draw/Base/SkiaControl.Invalidation.cs +++ b/src/Engine/Draw/Base/SkiaControl.Invalidation.cs @@ -33,7 +33,7 @@ public virtual bool ShouldInvalidateByChildren } } - public bool IsParentIndependent { get; set; } + public void InvalidateParents() { @@ -103,7 +103,7 @@ public virtual void InvalidateParent() /// public virtual void InvalidateByChild(SkiaControl child) { - DirtyChildren.Add(child); + DirtyChildrenTracker.Add(child); Invalidate(); } @@ -125,13 +125,15 @@ public virtual void InvalidateViewport() } } - protected readonly ControlsTracker DirtyChildren = new(); + protected readonly ControlsTracker DirtyChildrenTracker = new(); protected HashSet DirtyChildrenInternal { get; set; } = new(); public virtual void UpdateByChild(SkiaControl control) { - DirtyChildren.Add(control); + if (UsingCacheType == SkiaCacheType.ImageComposite) + DirtyChildrenTracker.Add(control); + UpdateInternal(); } diff --git a/src/Engine/Draw/Base/SkiaControl.Shared.cs b/src/Engine/Draw/Base/SkiaControl.Shared.cs index 41e82cbb..de5fddad 100644 --- a/src/Engine/Draw/Base/SkiaControl.Shared.cs +++ b/src/Engine/Draw/Base/SkiaControl.Shared.cs @@ -5240,6 +5240,15 @@ public virtual bool WillClipEffects } } + /// + /// Will not invalidate the measurement of parent if True + /// + public bool IsParentIndependent { get; set; } + + /// + /// Will not call Update on Parent if True + /// + public bool WillNotUpdateParent { get; set; } protected virtual void UpdateInternal() { @@ -5252,10 +5261,10 @@ protected virtual void UpdateInternal() if (UpdateLocked) return; - if (IsParentIndependent) - return; - - Parent?.UpdateByChild(this); + if (!WillNotUpdateParent) + { + Parent?.UpdateByChild(this); + } } /// diff --git a/src/Engine/Draw/Layout/LayoutStructure.cs b/src/Engine/Draw/Layout/LayoutStructure.cs index fc2fb062..96889126 100644 --- a/src/Engine/Draw/Layout/LayoutStructure.cs +++ b/src/Engine/Draw/Layout/LayoutStructure.cs @@ -7,6 +7,11 @@ public LayoutStructure() } + public ControlInStack GetForIndex(int index) + { + return grid.Values.FirstOrDefault(x => x.ControlIndex == index); + } + public LayoutStructure(List> grid) { int row = 0; @@ -15,10 +20,13 @@ public LayoutStructure(List> grid) var col = 0; foreach (var controlInStack in line) { + controlInStack.Column = col; + controlInStack.Row = row; + Add(controlInStack, col, row); col++; } row++; } } -} \ No newline at end of file +} diff --git a/src/Engine/Draw/Layout/SkiaLayout.ColumnRow.cs b/src/Engine/Draw/Layout/SkiaLayout.ColumnRow.cs index 631947aa..81899c3b 100644 --- a/src/Engine/Draw/Layout/SkiaLayout.ColumnRow.cs +++ b/src/Engine/Draw/Layout/SkiaLayout.ColumnRow.cs @@ -134,11 +134,16 @@ public virtual ScaledSize MeasureStack(SKRect rectForChildrenPixels, float scale //preload with condition.. nonTemplated = GetUnorderedSubviews().Where(c => c.CanDraw).ToArray(); } + else + { + //List dirtyChildren = DirtyChildrenTracker.GetList(); + //Debug.WriteLine($"Dirty: {dirtyChildren.Count}"); + } bool smartMeasuring = false; /* - List dirtyChildren = DirtyChildren.GetList(); + List dirtyChildren = DirtyChildrenTracker.GetList(); if (Superview != null) { @@ -306,13 +311,14 @@ public virtual ScaledSize MeasureStack(SKRect rectForChildrenPixels, float scale var layoutStructure = BuildStackStructure(scale); bool standalone = false; - bool useOneTemplate = IsTemplated && ItemSizingStrategy == ItemSizingStrategy.MeasureFirstItem && + bool useOneTemplate = IsTemplated && //ItemSizingStrategy == ItemSizingStrategy.MeasureFirstItem && RecyclingTemplate != RecyclingTemplate.Disabled; if (useOneTemplate) { standalone = true; template = ChildrenFactory.GetTemplateInstance(); + template.IsParentIndependent = true; } var maybeSecondPass = true; @@ -397,7 +403,11 @@ public virtual ScaledSize MeasureStack(SKRect rectForChildrenPixels, float scale if (IsTemplated) { - bool needMeasure = (ItemSizingStrategy == ItemSizingStrategy.MeasureFirstItem && columnsCount != Split) || !(ItemSizingStrategy == ItemSizingStrategy.MeasureFirstItem && firstCell != null); + bool needMeasure = + needMeasureAll || + (ItemSizingStrategy == ItemSizingStrategy.MeasureFirstItem && columnsCount != Split) + || !(ItemSizingStrategy == ItemSizingStrategy.MeasureFirstItem && firstCell != null); + if (needMeasure) { measured = MeasureAndArrangeCell(rectFitChild, cell, child, rectForChildrenPixels, scale); diff --git a/src/Engine/Draw/Layout/SkiaLayout.ViewsAdapter.cs b/src/Engine/Draw/Layout/SkiaLayout.ViewsAdapter.cs index fb205ee7..abbac8ed 100644 --- a/src/Engine/Draw/Layout/SkiaLayout.ViewsAdapter.cs +++ b/src/Engine/Draw/Layout/SkiaLayout.ViewsAdapter.cs @@ -120,6 +120,7 @@ protected virtual void AttachView(SkiaControl view, int index) if (IsDisposed) return; + view.IsParentIndependent = true; // uneven rows ultimate fix view.Parent = _parent; if (index == 0 || view.ContextIndex != index) //if (view.BindingContext == null || _parent.RecyclingTemplate == RecyclingTemplate.Enabled) @@ -859,6 +860,7 @@ public SkiaControl GetStandalone() if (ret == null) { ret = CreateFromTemplate(); + ret.IsParentIndependent = true; //! } return ret; } diff --git a/src/Engine/Draw/Layout/SkiaLayout.cs b/src/Engine/Draw/Layout/SkiaLayout.cs index d7b8753f..aa13b736 100644 --- a/src/Engine/Draw/Layout/SkiaLayout.cs +++ b/src/Engine/Draw/Layout/SkiaLayout.cs @@ -950,7 +950,7 @@ protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float } else { - DirtyChildren.Clear(); + DirtyChildrenTracker.Clear(); } base.Paint(ctx, destination, scale, arguments); @@ -1000,7 +1000,7 @@ public override void OnDisposing() ClearChildren(); - DirtyChildren.Clear(); + DirtyChildrenTracker.Clear(); DirtyChildrenInternal.Clear(); @@ -1038,7 +1038,7 @@ void SetupRenderingWithComposition(SkiaDrawingContext ctx, SKRect destination) // Add more children that are not already added but intersect with the dirty regions - var asSpans = CollectionsMarshal.AsSpan(DirtyChildren.GetList()); + var asSpans = CollectionsMarshal.AsSpan(DirtyChildrenTracker.GetList()); foreach (var item in asSpans) { DirtyChildrenInternal.Add(item); @@ -1055,7 +1055,7 @@ void SetupRenderingWithComposition(SkiaDrawingContext ctx, SKRect destination) } } - DirtyChildren.Clear(); + DirtyChildrenTracker.Clear(); var count = 0; foreach (var dirtyChild in DirtyChildrenInternal) @@ -1073,11 +1073,12 @@ void SetupRenderingWithComposition(SkiaDrawingContext ctx, SKRect destination) //Super.Log($"[ImageComposite] {Tag} drawing new"); IsRenderingWithComposition = false; - DirtyChildren.Clear(); + DirtyChildrenTracker.Clear(); } } else { + DirtyChildrenTracker.Clear(); IsRenderingWithComposition = false; } } diff --git a/src/Engine/Internals/Models/ControlInStack.cs b/src/Engine/Internals/Models/ControlInStack.cs index da938243..544566a7 100644 --- a/src/Engine/Internals/Models/ControlInStack.cs +++ b/src/Engine/Internals/Models/ControlInStack.cs @@ -49,4 +49,8 @@ public ControlInStack() public bool IsVisible { get; set; } public int ZIndex { get; set; } -} \ No newline at end of file + + public int Column { get; set; } + + public int Row { get; set; } +} diff --git a/src/Engine/Internals/Models/DynamicGrid.cs b/src/Engine/Internals/Models/DynamicGrid.cs index da92b37e..3940ec14 100644 --- a/src/Engine/Internals/Models/DynamicGrid.cs +++ b/src/Engine/Internals/Models/DynamicGrid.cs @@ -4,7 +4,7 @@ namespace DrawnUi.Maui.Draw; public class DynamicGrid { - private Dictionary<(int, int), T> grid = new Dictionary<(int, int), T>(); + protected Dictionary<(int, int), T> grid = new Dictionary<(int, int), T>(); private Dictionary columnCountPerColumn = new Dictionary(); // Stores row counts for each column private Dictionary columnCountPerRow = new Dictionary(); // Stores column counts for each row @@ -106,4 +106,4 @@ public int GetColumnCountForRow(int row) } return 0; // Returns 0 if no columns are present for the row } -} \ No newline at end of file +} diff --git a/src/Engine/Views/DrawnView.cs b/src/Engine/Views/DrawnView.cs index 10ee253a..40bdc0e0 100644 --- a/src/Engine/Views/DrawnView.cs +++ b/src/Engine/Views/DrawnView.cs @@ -1513,7 +1513,7 @@ public double CanvasFps long renderedFrames; - #region DISPOSE STUFF + #region DISPOSE STUFF public void DisposeObject(IDisposable resource) { @@ -1701,10 +1701,10 @@ private void DisposeAllRemaining() } } - + #endregion - + public void PostponeInvalidation(SkiaControl key, Action action) @@ -1751,14 +1751,14 @@ protected void SwapInvalidations() /// public int DrawingThreads { get; protected set; } - protected Dictionary DirtyChildren = new(); + protected Dictionary DirtyChildrenTracker = new(); public void SetChildAsDirty(SkiaControl child) { if (dirtyChilrenProcessing) return; - DirtyChildren[child.Uid] = child; + DirtyChildrenTracker[child.Uid] = child; } private volatile bool dirtyChilrenProcessing; @@ -1876,7 +1876,7 @@ protected virtual void Draw(SkiaDrawingContext context, SKRect destination, floa ++renderedFrames; //Debug.WriteLine($"[DRAW] {Tag}"); - + if (IsDisposed || UpdateLocked) { @@ -1944,7 +1944,7 @@ protected virtual void Draw(SkiaDrawingContext context, SKRect destination, floa } dirtyChilrenProcessing = true; - foreach (var child in DirtyChildren.Values) + foreach (var child in DirtyChildrenTracker.Values) { if (child != null && !child.IsDisposing) { @@ -1952,7 +1952,7 @@ protected virtual void Draw(SkiaDrawingContext context, SKRect destination, floa child?.InvalidateParent(); } } - DirtyChildren.Clear(); + DirtyChildrenTracker.Clear(); dirtyChilrenProcessing = false; //notify registered tree final nodes of rendering tree state diff --git a/src/samples/Sandbox/Views/MainPageDynamicHeightCells.xaml b/src/samples/Sandbox/Views/MainPageDynamicHeightCells.xaml index 44e5bef8..7b7d5d7b 100644 --- a/src/samples/Sandbox/Views/MainPageDynamicHeightCells.xaml +++ b/src/samples/Sandbox/Views/MainPageDynamicHeightCells.xaml @@ -3,9 +3,9 @@ x:Class="Sandbox.Views.MainPageDynamicHeightCells" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + xmlns:controls="clr-namespace:Sandbox.Views.Controls" xmlns:demo="clr-namespace:Sandbox" xmlns:draw="http://schemas.appomobi.com/drawnUi/2023/draw" - xmlns:controls="clr-namespace:Sandbox.Views.Controls" xmlns:views="clr-namespace:Sandbox.Views" x:Name="ThisPage" x:DataType="demo:MainPageViewModel" @@ -30,20 +30,20 @@ HardwareAcceleration="Enabled" HeightRequest="500" HorizontalOptions="Fill" - VerticalOptions="Start"> + VerticalOptions="Center"> @@ -52,20 +52,21 @@ - + + + + + + + + + @@ -95,10 +105,10 @@ - - + + Date: Fri, 6 Dec 2024 10:14:48 +0300 Subject: [PATCH 3/8] safer disposal of hover effects might solve rsome andom native skia crashes --- .../Animations/Animators/RippleAnimator.cs | 85 -------- .../Animations/Animators/ShimmerAnimator.cs | 107 ---------- src/Engine/Internals/Interfaces/IDrawnBase.cs | 182 +++++++++--------- 3 files changed, 90 insertions(+), 284 deletions(-) delete mode 100644 src/Engine/Features/Animations/Animators/RippleAnimator.cs delete mode 100644 src/Engine/Features/Animations/Animators/ShimmerAnimator.cs diff --git a/src/Engine/Features/Animations/Animators/RippleAnimator.cs b/src/Engine/Features/Animations/Animators/RippleAnimator.cs deleted file mode 100644 index e24a216d..00000000 --- a/src/Engine/Features/Animations/Animators/RippleAnimator.cs +++ /dev/null @@ -1,85 +0,0 @@ -namespace DrawnUi.Maui.Draw; - -public class RippleAnimator : RenderingAnimator -{ - public RippleAnimator(IDrawnBase control) : base(control) - { - IsPostAnimator = true; - Speed = 500;//250; - mMinValue = 0; - mMaxValue = 1; - Color = SKColor.Parse("#FFFFFF"); - Easing = Easing.CubicIn; - } - - protected static long count; - - public SKColor Color { get; set; } - - public static double DiameterDefault = 300.0; - - public static double OpacityDefault = 0.20; - - /// - /// In pts relative to control X,Y. These are coords inside the control and not inside the canvas. - /// - public double X { get; set; } - - /// - /// In pts relative to control X,Y. These are coords inside the control and not inside the canvas. - /// - public double Y { get; set; } - - public double Diameter { get; set; } - - public double Opacity { get; set; } - - public override void Dispose() - { - Paint?.Dispose(); - - base.Dispose(); - } - - protected SKPaint Paint; - - protected override bool OnRendering(IDrawnBase control, SkiaDrawingContext context, double scale) - { - if (IsRunning && control != null && !control.IsDisposed && !control.IsDisposing) - { - var touchOffset = new SKPoint((float)(X * scale), (float)(Y * scale)); - var selfDrawingLocation = GetSelfDrawingLocation(control); - - DrawWithClipping(context, control, selfDrawingLocation, () => - { - Paint ??= new SKPaint(); - Paint.Style = SKPaintStyle.Fill; - Paint.Color = Color.WithAlpha((byte)(Opacity * 255)); - - touchOffset.Offset(selfDrawingLocation); - context.Canvas.DrawCircle(touchOffset.X, touchOffset.Y, (float)(Diameter * scale), Paint); - }); - - return true; - } - - return false; - } - - protected override double TransformReportedValue(long deltaT) - { - - var progress = base.TransformReportedValue(deltaT); - - var opacityProgress = progress * 1.15; - if (opacityProgress <= 1) - { - Opacity = OpacityDefault - OpacityDefault * opacityProgress; - } - - Diameter = DiameterDefault * progress; - - return progress; - } - -} \ No newline at end of file diff --git a/src/Engine/Features/Animations/Animators/ShimmerAnimator.cs b/src/Engine/Features/Animations/Animators/ShimmerAnimator.cs deleted file mode 100644 index 2a1149b6..00000000 --- a/src/Engine/Features/Animations/Animators/ShimmerAnimator.cs +++ /dev/null @@ -1,107 +0,0 @@ -namespace DrawnUi.Maui.Draw; - -public class ShimmerAnimator : RenderingAnimator -{ - public ShimmerAnimator(IDrawnBase control) : base(control) - { - IsPostAnimator = true; - Speed = 1000; - mMinValue = 0; - mMaxValue = 1; - Color = SKColor.Parse("#33FFFFFF"); - ShimmerAngle = 45; - ShimmerWidth = 100; - } - - protected static long count; - - public SKColor Color { get; set; } - public double ShimmerWidth { get; set; } - public double ShimmerAngle { get; set; } - - public override void Dispose() - { - Paint?.Dispose(); - - base.Dispose(); - } - - protected SKPaint Paint; - - protected override bool OnRendering(IDrawnBase control, SkiaDrawingContext context, double scale) - { - if (IsRunning) - { - var color = Color; - - // Original rectangle - var selfDrawingLocation = GetSelfDrawingLocation(control); - var originalRect = new SKRect(selfDrawingLocation.X, selfDrawingLocation.Y, - selfDrawingLocation.X + control.DrawingRect.Width, - selfDrawingLocation.Y + control.DrawingRect.Height); - - var maxSide = Math.Max(originalRect.Width, originalRect.Height); - var centerX = originalRect.MidX; - var centerY = originalRect.MidY; - - // Create a new square rectangle centered within the original rectangle - var rect = new SKRect(centerX - maxSide / 2, centerY - maxSide / 2, centerX + maxSide / 2, centerY + maxSide / 2); - - //var shimmerWidth = ShimmerWidth * scale; - var shimmerWidth = rect.Width; - - // Calculate the shimmer position based on the progress - double diagonalLength = Math.Sqrt(Math.Pow(rect.Width, 2) + Math.Pow(rect.Height, 2)); - - var shimmerStartX = (float)(rect.Left - shimmerWidth + (diagonalLength + shimmerWidth) * mValue); - var shimmerEndX = shimmerStartX + (float)(shimmerWidth); - - var canvas = context.Canvas; - - DrawWithClipping(context, control, selfDrawingLocation, () => - { - Paint ??= new SKPaint(); - Paint.Style = SKPaintStyle.Fill; - Paint.Color = color; - Paint.Shader = SKShader.CreateLinearGradient( - new SKPoint(shimmerStartX, 0), - new SKPoint(shimmerEndX, 0), - new SKColor[] - { - SKColors.Transparent, - color, - SKColors.Transparent - }, - new float[] { 0, 0.5f, 1 }, - SKShaderTileMode.Clamp); - - if (ShimmerAngle == 0) - { - canvas.DrawRect(rect, Paint); - } - else - { - var saved = canvas.Save(); - // Translate the canvas so that the center of the control is at the origin - var rotationX = rect.Left + rect.Width / 2f; - var rotationY = rect.Top + rect.Height / 2f; - canvas.Translate(rotationX, rotationY); - var m = SKMatrix.CreateRotationDegrees((float)ShimmerAngle); - canvas.Concat(ref m); - canvas.Translate(-rotationX, -rotationY); - - canvas.DrawRect(rect, Paint); - - canvas.RestoreToCount(saved); - } - }); - - return true; - } - - return false; - } - - - -} \ No newline at end of file diff --git a/src/Engine/Internals/Interfaces/IDrawnBase.cs b/src/Engine/Internals/Interfaces/IDrawnBase.cs index cb71470c..72791c6c 100644 --- a/src/Engine/Internals/Interfaces/IDrawnBase.cs +++ b/src/Engine/Internals/Interfaces/IDrawnBase.cs @@ -1,138 +1,136 @@ -using DrawnUi.Maui.Draw; -using DrawnUi.Maui.Draw; - -namespace DrawnUi.Maui.Draw; +namespace DrawnUi.Maui.Draw; public interface IDrawnBase : IDisposable, ICanBeUpdatedWithContext { - SKRect DrawingRect { get; } + SKRect DrawingRect { get; } - string Tag { get; } + string Tag { get; } - bool IsVisible { get; set; } + bool IsVisible { get; set; } - bool IsDisposed { get; } + bool IsDisposed { get; } - bool IsDisposing { get; } + bool IsDisposing { get; } - bool IsVisibleInViewTree(); + bool IsVisibleInViewTree(); - /// - /// Obtain rectangle visible on the screen to avoid offscreen rendering etc - /// - /// - public ScaledRect GetOnScreenVisibleArea(float inflateByPixels = 0); + /// + /// Obtain rectangle visible on the screen to avoid offscreen rendering etc + /// + /// + public ScaledRect GetOnScreenVisibleArea(float inflateByPixels = 0); - /// - /// Invalidates the measured size. May or may not call Update() inside, depends on control - /// - void Invalidate(); + /// + /// Invalidates the measured size. May or may not call Update() inside, depends on control + /// + void Invalidate(); - /// - /// If need the re-measure all parents because child-auto-size has changed - /// - void InvalidateParents(); + /// + /// If need the re-measure all parents because child-auto-size has changed + /// + void InvalidateParents(); - /// - /// Clip using internal custom settings of the control - /// - /// - /// - /// - public void ClipSmart(SKCanvas canvas, SKPath path, SKClipOperation operation = SKClipOperation.Intersect); + /// + /// Clip using internal custom settings of the control + /// + /// + /// + /// + public void ClipSmart(SKCanvas canvas, SKPath path, SKClipOperation operation = SKClipOperation.Intersect); - /// - /// Creates a new disposable SKPath for clipping content according to the control shape and size. - /// Create this control clip for painting content. - /// Pass arguments if you want to use some time-frozen data for painting at any time from any thread.. - /// If applyPosition is false will create clip without using drawing posiition, like if was drawing at 0,0. - /// - /// - SKPath CreateClip(object arguments, bool usePosition, SKPath path = null); + /// + /// Creates a new disposable SKPath for clipping content according to the control shape and size. + /// Create this control clip for painting content. + /// Pass arguments if you want to use some time-frozen data for painting at any time from any thread.. + /// If applyPosition is false will create clip without using drawing posiition, like if was drawing at 0,0. + /// + /// + SKPath CreateClip(object arguments, bool usePosition, SKPath path = null); - bool RegisterAnimator(ISkiaAnimator animator); + bool RegisterAnimator(ISkiaAnimator animator); - void UnregisterAnimator(Guid uid); + void UnregisterAnimator(Guid uid); - IEnumerable UnregisterAllAnimatorsByType(Type type); + IEnumerable UnregisterAllAnimatorsByType(Type type); - public void RegisterGestureListener(ISkiaGestureListener gestureListener); + public void RegisterGestureListener(ISkiaGestureListener gestureListener); - public void UnregisterGestureListener(ISkiaGestureListener gestureListener); + public void UnregisterGestureListener(ISkiaGestureListener gestureListener); - /// - /// Executed after the rendering - /// - public List PostAnimators { get; } + /// + /// Executed after the rendering + /// + public List PostAnimators { get; } - /// - /// For code-behind access of children, XAML is using Children property - /// - List Views { get; } + /// + /// For code-behind access of children, XAML is using Children property + /// + List Views { get; } - /// - /// Directly adds a view to the control, without any layouting. Use this instead of Views.Add() to avoid memory leaks etc - /// - /// - public void AddSubView(SkiaControl view); + /// + /// Directly adds a view to the control, without any layouting. Use this instead of Views.Add() to avoid memory leaks etc + /// + /// + public void AddSubView(SkiaControl view); - /// - /// Directly removes a view from the control, without any layouting. - /// Use this instead of Views.Remove() to avoid memory leaks etc - /// - /// - public void RemoveSubView(SkiaControl view); + /// + /// Directly removes a view from the control, without any layouting. + /// Use this instead of Views.Remove() to avoid memory leaks etc + /// + /// + public void RemoveSubView(SkiaControl view); - public ScaledSize MeasuredSize { get; } + public ScaledSize MeasuredSize { get; } - SKRect Destination { get; } + SKRect Destination { get; } - public double HeightRequest { get; set; } + public double HeightRequest { get; set; } - public double WidthRequest { get; set; } + public double WidthRequest { get; set; } - public double Height { get; } + public double Height { get; } - public double Width { get; } + public double Width { get; } - public double TranslationX { get; } + public double TranslationX { get; } - public double TranslationY { get; } + public double TranslationY { get; } - public bool InputTransparent { get; set; } + public bool InputTransparent { get; set; } - bool IsClippedToBounds { get; set; } + bool IsClippedToBounds { get; set; } - bool ClipEffects { get; set; } + bool ClipEffects { get; set; } - bool UpdateLocked { get; } + bool UpdateLocked { get; } - /// - /// This is needed by layout to track which child changed to sometimes avoid recalculating other children - /// - /// - void InvalidateByChild(SkiaControl skiaControl); + /// + /// This is needed by layout to track which child changed to sometimes avoid recalculating other children + /// + /// + void InvalidateByChild(SkiaControl skiaControl); - /// - /// To track dirty area when Updating parent - /// - /// - void UpdateByChild(SkiaControl skiaControl); + /// + /// To track dirty area when Updating parent + /// + /// + void UpdateByChild(SkiaControl skiaControl); - public bool ShouldInvalidateByChildren { get; } + public bool ShouldInvalidateByChildren { get; } - float RenderingScale { get; } + float RenderingScale { get; } - double X { get; } + double X { get; } - double Y { get; } + double Y { get; } - void InvalidateViewport(); + void InvalidateViewport(); - void Repaint(); + void Repaint(); - void InvalidateViewsList(); + void InvalidateViewsList(); -} \ No newline at end of file + void DisposeObject(IDisposable value); +} From 33e6e990e554377c73d436b7a562e0a37768d5df Mon Sep 17 00:00:00 2001 From: Nick Kovalsky Date: Fri, 6 Dec 2024 10:15:23 +0300 Subject: [PATCH 4/8] TextTransform prop for SkiaLabel --- .../Controls/Labels/SkiaMarkdownLabel.cs | 137 +++++++++--------- src/Engine/Draw/Text/SkiaLabel.cs | 72 ++++++++- .../Animations/HoverEffects/RippleAnimator.cs | 88 +++++++++++ .../HoverEffects/ShimmerAnimator.cs | 110 ++++++++++++++ src/Engine/Internals/Enums/TextTransform.cs | 21 +++ src/samples/Sandbox/ImageTestPage.xaml | 2 +- src/samples/Sandbox/MainPage.xaml | 2 +- .../Sandbox/MainPageCodeDeformation.cs | 119 --------------- src/samples/Sandbox/MainPageCodeDev.cs | 96 ++++++++++++ src/samples/Sandbox/MainPagePdfFixes.xaml | 3 +- src/samples/Sandbox/Resources/Styles.xaml | 2 +- .../Sandbox/Views/Controls/ButtonToRoot.xaml | 4 +- 12 files changed, 454 insertions(+), 202 deletions(-) create mode 100644 src/Engine/Features/Animations/HoverEffects/RippleAnimator.cs create mode 100644 src/Engine/Features/Animations/HoverEffects/ShimmerAnimator.cs create mode 100644 src/Engine/Internals/Enums/TextTransform.cs delete mode 100644 src/samples/Sandbox/MainPageCodeDeformation.cs create mode 100644 src/samples/Sandbox/MainPageCodeDev.cs diff --git a/src/Engine/Controls/Labels/SkiaMarkdownLabel.cs b/src/Engine/Controls/Labels/SkiaMarkdownLabel.cs index 35269c0c..fd3a3064 100644 --- a/src/Engine/Controls/Labels/SkiaMarkdownLabel.cs +++ b/src/Engine/Controls/Labels/SkiaMarkdownLabel.cs @@ -1,9 +1,7 @@ -using DrawnUi.Maui.Draw; +using System.Windows.Input; using Markdig; using Markdig.Syntax; using Markdig.Syntax.Inlines; -using Newtonsoft.Json.Linq; -using System.Windows.Input; namespace DrawnUi.Maui.Draw; @@ -33,7 +31,9 @@ public SkiaMarkdownLabel() public override void InvalidateText() { - var markdownDocument = Markdig.Markdown.Parse(Text, _pipeline); + base.InvalidateText(); + + var markdownDocument = Markdig.Markdown.Parse(TextInternal, _pipeline); Spans.Clear(); @@ -50,7 +50,6 @@ public override void InvalidateText() RenderBlock(block); } - base.InvalidateText(); } protected virtual TextSpan SpanWithAttributes(TextSpan span) @@ -265,74 +264,74 @@ protected void RenderInline(Inline inline) switch (inline) { - case LineBreakInline lineBreak: - //just add new line to previous span - var last = Spans.LastOrDefault(); - if (last == null) - { - Spans.Add(new() - { - Text = "\n" - }); ; - } - else - { - last.Text += "\n"; - } - break; + case LineBreakInline lineBreak: + //just add new line to previous span + var last = Spans.LastOrDefault(); + if (last == null) + { + Spans.Add(new() + { + Text = "\n" + }); ; + } + else + { + last.Text += "\n"; + } + break; - case LiteralInline literal: - //todo detect available font - AddTextSpan(literal.Content.ToStringSafe()); - break; + case LiteralInline literal: + //todo detect available font + AddTextSpan(literal.Content.ToStringSafe()); + break; - case CodeInline code: - AddCodeSpan(code); - break; + case CodeInline code: + AddCodeSpan(code); + break; - case LinkInline link: - AddLinkSpan(link); - break; + case LinkInline link: + AddLinkSpan(link); + break; - case EmphasisInline emphasis: - if (emphasis.DelimiterCount == 3) - { - if (emphasis.DelimiterChar is '_' or '*') - { - isBold = true; - isItalic = true; - } - } - else - if (emphasis.DelimiterCount == 2) - { - if (emphasis.DelimiterChar == '~') - { - isStrikethrough = true; - } - else - if (emphasis.DelimiterChar is '_' or '*') - { - isBold = true; - } - } - else - if (emphasis.DelimiterCount == 1) - { - if (emphasis.DelimiterChar is '_' or '*') - { - isItalic = true; - } - } + case EmphasisInline emphasis: + if (emphasis.DelimiterCount == 3) + { + if (emphasis.DelimiterChar is '_' or '*') + { + isBold = true; + isItalic = true; + } + } + else + if (emphasis.DelimiterCount == 2) + { + if (emphasis.DelimiterChar == '~') + { + isStrikethrough = true; + } + else + if (emphasis.DelimiterChar is '_' or '*') + { + isBold = true; + } + } + else + if (emphasis.DelimiterCount == 1) + { + if (emphasis.DelimiterChar is '_' or '*') + { + isItalic = true; + } + } - var child = emphasis.FirstChild; - while (child != null) - { - RenderInline(child); - child = child.NextSibling; - } + var child = emphasis.FirstChild; + while (child != null) + { + RenderInline(child); + child = child.NextSibling; + } - break; + break; } @@ -350,7 +349,7 @@ protected override void OnFontUpdated() { base.OnFontUpdated(); - OnTextChanged(this.Text); + OnTextChanged(this.TextInternal); } public virtual (SKTypeface, int) FindBestTypefaceForString(string text) @@ -699,4 +698,4 @@ public Color CodeBackgroundColor set { SetValue(CodeBackgroundColorProperty, value); } } -} \ No newline at end of file +} diff --git a/src/Engine/Draw/Text/SkiaLabel.cs b/src/Engine/Draw/Text/SkiaLabel.cs index f2179161..8d20cc5c 100644 --- a/src/Engine/Draw/Text/SkiaLabel.cs +++ b/src/Engine/Draw/Text/SkiaLabel.cs @@ -60,7 +60,7 @@ public override string ToString() { return string.Concat(Spans.Select(span => span.Text)); } - return this.Text; + return this.TextInternal; } } @@ -364,7 +364,7 @@ public override ScaledSize Measure(float widthConstraint, float heightConstraint string text = null; - Glyphs = GetGlyphs(Text, PaintDefault.Typeface); + Glyphs = GetGlyphs(TextInternal, PaintDefault.Typeface); if (AutoFont) { @@ -380,7 +380,7 @@ public override ScaledSize Measure(float widthConstraint, float heightConstraint ReplaceFont(); } } - text = Text; + text = TextInternal; } else { @@ -404,7 +404,7 @@ public override ScaledSize Measure(float widthConstraint, float heightConstraint } else { - text = Text; + text = TextInternal; } } @@ -490,7 +490,7 @@ public override ScaledSize Measure(float widthConstraint, float heightConstraint if (index == mergedLines.Count) //do not process last line break; - if (line.Value.Right(1) == " ") + if (line.Value.Right(1) == Splitter) { var span = line.Spans.LastOrDefault(); //if (span.Span != null) @@ -1102,7 +1102,7 @@ float PositionBaseline(float calcBaselineY) float MoveOffsetAdjustmentX(float x, string p) { - if (enlargeSpaceCharacter > 0 && p == " ") + if (enlargeSpaceCharacter > 0 && p == Splitter) { x += enlargeSpaceCharacter; } @@ -2400,7 +2400,7 @@ void AddEmptyLineInternal() bool severalWords = false; if (width > 0) //got some text from previous pass { - if (lineResult.Right(1) == " " || word.Left() == " ") + if (lineResult.Right(1) == Splitter || word.Left() == Splitter) { textLine = lineResult + word; } @@ -2896,6 +2896,18 @@ public string Text set { SetValue(TextProperty, value); } } + public static readonly BindableProperty TextTransformProperty = BindableProperty.Create(nameof(TextTransform), + typeof(TextTransform), + typeof(SkiaLabel), + TextTransform.None, + propertyChanged: NeedUpdateFont); + + public TextTransform TextTransform + { + get { return (TextTransform)GetValue(TextTransformProperty); } + set { SetValue(TextTransformProperty, value); } + } + protected virtual void OnTextChanged(string value) { InvalidateText(); @@ -2903,9 +2915,55 @@ protected virtual void OnTextChanged(string value) public virtual void InvalidateText() { + if (IsDisposed || IsDisposing) + return; + + SetTextInternal(); + InvalidateMeasure(); } + + public static string Splitter = " "; + + /// + /// Aplies transforms etc + /// + protected virtual void SetTextInternal() + { + if (Text != null) + { + switch (TextTransform) + { + case TextTransform.Uppercase: + TextInternal = Text.ToUpper(); + break; + + case TextTransform.Lowercase: + TextInternal = Text.ToLower(); + break; + + case TextTransform.Titlecase: + TextInternal = Text.ToTitleCase(Splitter, false); + break; + + case TextTransform.Phrasecase: + TextInternal = Text.ToPhraseCase(true); + break; + + default: + TextInternal = Text; + break; + } + } + else + { + TextInternal = string.Empty; + } + } + + protected string TextInternal { get; set; } + public static readonly BindableProperty FallbackCharacterProperty = BindableProperty.Create( nameof(FallbackCharacter), typeof(char), diff --git a/src/Engine/Features/Animations/HoverEffects/RippleAnimator.cs b/src/Engine/Features/Animations/HoverEffects/RippleAnimator.cs new file mode 100644 index 00000000..58227593 --- /dev/null +++ b/src/Engine/Features/Animations/HoverEffects/RippleAnimator.cs @@ -0,0 +1,88 @@ +namespace DrawnUi.Maui.Draw; + +public class RippleAnimator : RenderingAnimator +{ + public RippleAnimator(IDrawnBase control) : base(control) + { + IsPostAnimator = true; + Speed = 500;//250; + mMinValue = 0; + mMaxValue = 1; + Color = SKColor.Parse("#FFFFFF"); + Easing = Easing.CubicIn; + } + + protected static long count; + + public SKColor Color { get; set; } + + public static double DiameterDefault = 300.0; + + public static double OpacityDefault = 0.20; + + /// + /// In pts relative to control X,Y. These are coords inside the control and not inside the canvas. + /// + public double X { get; set; } + + /// + /// In pts relative to control X,Y. These are coords inside the control and not inside the canvas. + /// + public double Y { get; set; } + + public double Diameter { get; set; } + + public double Opacity { get; set; } + + public override void Dispose() + { + if (Parent != null) + { + Parent.DisposeObject(Paint); + } + + base.Dispose(); + } + + protected SKPaint Paint; + + protected override bool OnRendering(IDrawnBase control, SkiaDrawingContext context, double scale) + { + if (IsRunning && control != null && !control.IsDisposed && !control.IsDisposing) + { + var touchOffset = new SKPoint((float)(X * scale), (float)(Y * scale)); + var selfDrawingLocation = GetSelfDrawingLocation(control); + + DrawWithClipping(context, control, selfDrawingLocation, () => + { + Paint ??= new SKPaint(); + Paint.Style = SKPaintStyle.Fill; + Paint.Color = Color.WithAlpha((byte)(Opacity * 255)); + + touchOffset.Offset(selfDrawingLocation); + context.Canvas.DrawCircle(touchOffset.X, touchOffset.Y, (float)(Diameter * scale), Paint); + }); + + return true; + } + + return false; + } + + protected override double TransformReportedValue(long deltaT) + { + + var progress = base.TransformReportedValue(deltaT); + + var opacityProgress = progress * 1.15; + if (opacityProgress <= 1) + { + Opacity = OpacityDefault - OpacityDefault * opacityProgress; + } + + Diameter = DiameterDefault * progress; + + return progress; + } + +} diff --git a/src/Engine/Features/Animations/HoverEffects/ShimmerAnimator.cs b/src/Engine/Features/Animations/HoverEffects/ShimmerAnimator.cs new file mode 100644 index 00000000..bda7f5ec --- /dev/null +++ b/src/Engine/Features/Animations/HoverEffects/ShimmerAnimator.cs @@ -0,0 +1,110 @@ +namespace DrawnUi.Maui.Draw; + +public class ShimmerAnimator : RenderingAnimator +{ + public ShimmerAnimator(IDrawnBase control) : base(control) + { + IsPostAnimator = true; + Speed = 1000; + mMinValue = 0; + mMaxValue = 1; + Color = SKColor.Parse("#33FFFFFF"); + ShimmerAngle = 45; + ShimmerWidth = 100; + } + + protected static long count; + + public SKColor Color { get; set; } + public double ShimmerWidth { get; set; } + public double ShimmerAngle { get; set; } + + public override void Dispose() + { + if (Parent != null) + { + Parent.DisposeObject(Paint); + } + + base.Dispose(); + } + + protected SKPaint Paint; + + protected override bool OnRendering(IDrawnBase control, SkiaDrawingContext context, double scale) + { + if (IsRunning) + { + var color = Color; + + // Original rectangle + var selfDrawingLocation = GetSelfDrawingLocation(control); + var originalRect = new SKRect(selfDrawingLocation.X, selfDrawingLocation.Y, + selfDrawingLocation.X + control.DrawingRect.Width, + selfDrawingLocation.Y + control.DrawingRect.Height); + + var maxSide = Math.Max(originalRect.Width, originalRect.Height); + var centerX = originalRect.MidX; + var centerY = originalRect.MidY; + + // Create a new square rectangle centered within the original rectangle + var rect = new SKRect(centerX - maxSide / 2, centerY - maxSide / 2, centerX + maxSide / 2, centerY + maxSide / 2); + + //var shimmerWidth = ShimmerWidth * scale; + var shimmerWidth = rect.Width; + + // Calculate the shimmer position based on the progress + double diagonalLength = Math.Sqrt(Math.Pow(rect.Width, 2) + Math.Pow(rect.Height, 2)); + + var shimmerStartX = (float)(rect.Left - shimmerWidth + (diagonalLength + shimmerWidth) * mValue); + var shimmerEndX = shimmerStartX + (float)(shimmerWidth); + + var canvas = context.Canvas; + + DrawWithClipping(context, control, selfDrawingLocation, () => + { + Paint ??= new SKPaint(); + Paint.Style = SKPaintStyle.Fill; + Paint.Color = color; + Paint.Shader = SKShader.CreateLinearGradient( + new SKPoint(shimmerStartX, 0), + new SKPoint(shimmerEndX, 0), + new SKColor[] + { + SKColors.Transparent, + color, + SKColors.Transparent + }, + new float[] { 0, 0.5f, 1 }, + SKShaderTileMode.Clamp); + + if (ShimmerAngle == 0) + { + canvas.DrawRect(rect, Paint); + } + else + { + var saved = canvas.Save(); + // Translate the canvas so that the center of the control is at the origin + var rotationX = rect.Left + rect.Width / 2f; + var rotationY = rect.Top + rect.Height / 2f; + canvas.Translate(rotationX, rotationY); + var m = SKMatrix.CreateRotationDegrees((float)ShimmerAngle); + canvas.Concat(ref m); + canvas.Translate(-rotationX, -rotationY); + + canvas.DrawRect(rect, Paint); + + canvas.RestoreToCount(saved); + } + }); + + return true; + } + + return false; + } + + + +} diff --git a/src/Engine/Internals/Enums/TextTransform.cs b/src/Engine/Internals/Enums/TextTransform.cs new file mode 100644 index 00000000..03b95d94 --- /dev/null +++ b/src/Engine/Internals/Enums/TextTransform.cs @@ -0,0 +1,21 @@ +namespace DrawnUi.Maui.Draw +{ + public enum TextTransform + { + None = 0, + + Lowercase, + + Uppercase, + + /// + /// Every word starts with a capital letter, others do not change + /// + Titlecase, + + /// + /// First word starts with a capital letter, others do not change + /// + Phrasecase + } +} diff --git a/src/samples/Sandbox/ImageTestPage.xaml b/src/samples/Sandbox/ImageTestPage.xaml index 2af56bb8..f9466061 100644 --- a/src/samples/Sandbox/ImageTestPage.xaml +++ b/src/samples/Sandbox/ImageTestPage.xaml @@ -21,6 +21,7 @@ diff --git a/src/samples/Sandbox/MainPage.xaml b/src/samples/Sandbox/MainPage.xaml index 5130bcad..eb61383f 100644 --- a/src/samples/Sandbox/MainPage.xaml +++ b/src/samples/Sandbox/MainPage.xaml @@ -161,7 +161,7 @@ Text="{Binding Name}" TextColor="#ccffffff" TextStrokeColor="#99FF0000" - TintColor="{StaticResource ColorPrimary}" + BackgroundColor="{StaticResource ColorPrimary}" WidthRequest="150"> () - { - 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/MainPageCodeDev.cs b/src/samples/Sandbox/MainPageCodeDev.cs new file mode 100644 index 00000000..74c7e383 --- /dev/null +++ b/src/samples/Sandbox/MainPageCodeDev.cs @@ -0,0 +1,96 @@ +using Sandbox.Views; +using Canvas = DrawnUi.Maui.Views.Canvas; +using TextTransform = DrawnUi.Maui.Draw.TextTransform; + +namespace Sandbox +{ + + + public class MainPageCodeDev : BasePage, IDisposable + { + Canvas Canvas; + + public void Dispose() + { + this.Content = null; + Canvas?.Dispose(); + } + + + + + public MainPageCodeDev() + { +#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() + { + new SkiaShape() + { + HorizontalOptions = LayoutOptions.Fill, + HeightRequest = 128, + StrokeColor = Colors.Black, + StrokeWidth = 1, + Margin = 8, + CornerRadius = 10, + Padding = 10, + BackgroundColor = Colors.White, + Content = new SkiaMarkdownLabel() + { + Text = "hello ppl how are you\r\nNew line comes here\r\nAnd another one too", + TextTransform = TextTransform.Titlecase, + MaxLines = 2 + } + + } + + } + } + } + } + + + }; + + _reloads++; + + this.Content = Canvas; + } + + private void ReloadUI(Type[] obj) + { + MainThread.BeginInvokeOnMainThread(() => + { + Build(); + }); + } + + } +} diff --git a/src/samples/Sandbox/MainPagePdfFixes.xaml b/src/samples/Sandbox/MainPagePdfFixes.xaml index 8bb99dad..756a258f 100644 --- a/src/samples/Sandbox/MainPagePdfFixes.xaml +++ b/src/samples/Sandbox/MainPagePdfFixes.xaml @@ -21,14 +21,13 @@ diff --git a/src/samples/Sandbox/Resources/Styles.xaml b/src/samples/Sandbox/Resources/Styles.xaml index 8b5af01c..d69f83da 100644 --- a/src/samples/Sandbox/Resources/Styles.xaml +++ b/src/samples/Sandbox/Resources/Styles.xaml @@ -5,7 +5,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:draw="http://schemas.appomobi.com/drawnUi/2023/draw"> - diff --git a/src/samples/Sandbox/Views/Controls/ButtonToRoot.xaml b/src/samples/Sandbox/Views/Controls/ButtonToRoot.xaml index 3a108c40..0e940d5f 100644 --- a/src/samples/Sandbox/Views/Controls/ButtonToRoot.xaml +++ b/src/samples/Sandbox/Views/Controls/ButtonToRoot.xaml @@ -14,13 +14,13 @@ + Tapped="GoToRoot"> Date: Fri, 6 Dec 2024 10:23:56 +0300 Subject: [PATCH 5/8] apply MaxLines to Spans of SkiaLabel --- src/Engine/Draw/Text/SkiaLabel.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Engine/Draw/Text/SkiaLabel.cs b/src/Engine/Draw/Text/SkiaLabel.cs index 8d20cc5c..76f93ac5 100644 --- a/src/Engine/Draw/Text/SkiaLabel.cs +++ b/src/Engine/Draw/Text/SkiaLabel.cs @@ -1192,6 +1192,11 @@ float MoveOffsetAdjustmentX(float x, string p) spanIndex++; } + if (MaxLines > 0 && lineNb == MaxLines) + { + break; + } + if (LineHeightUniform) baselineY += (float)(useLineHeight + GetSpaceBetweenLines(useLineHeight)); else From 3abcd21ff1e7b106651988b08a200f60682f0b9c Mon Sep 17 00:00:00 2001 From: Nick Kovalsky Date: Fri, 6 Dec 2024 10:47:10 +0300 Subject: [PATCH 6/8] Super.DisplayException for drawn --- src/Engine/Super.Maui.cs | 244 +++++++++++++++++++++++++ src/Engine/Super.cs | 204 +-------------------- src/samples/Sandbox/MainPageCodeDev.cs | 14 +- src/samples/Sandbox/Sandbox.csproj | 8 + src/samples/Sandbox/View1.xaml | 12 ++ src/samples/Sandbox/View1.xaml.cs | 17 ++ 6 files changed, 290 insertions(+), 209 deletions(-) create mode 100644 src/Engine/Super.Maui.cs create mode 100644 src/samples/Sandbox/View1.xaml create mode 100644 src/samples/Sandbox/View1.xaml.cs diff --git a/src/Engine/Super.Maui.cs b/src/Engine/Super.Maui.cs new file mode 100644 index 00000000..b3f77a77 --- /dev/null +++ b/src/Engine/Super.Maui.cs @@ -0,0 +1,244 @@ +using System.Runtime.CompilerServices; + +namespace DrawnUi.Maui.Draw +{ + public partial class Super + { + public static Color ColorAccent { get; set; } = Colors.Orange; + + public static Color ColorPrimary { get; set; } = Colors.Gray; + + /// + /// Display xaml page creation exception + /// + /// + /// + public static void DisplayException(VisualElement view, Exception e) + { + Trace.WriteLine(e); + + if (view == null) + throw e; + + if (view is SkiaControl skia) + { + var scroll = new SkiaScroll() + { + HorizontalOptions = LayoutOptions.Fill, + VerticalOptions = LayoutOptions.Fill, + Content = new SkiaLabel + { + Margin = new Thickness(32), + TextColor = Colors.Red, + Text = $"{e}" + } + }; + + if (skia is ContentLayout content) + { + content.Content = scroll; + } + else + { + skia.AddSubView(scroll); + } + } + else + { + + var scroll = new ScrollView() + { + Content = new Label + { + Margin = new Thickness(32), + TextColor = Colors.Red, + Text = $"{e}" + } + }; + + if (view is ContentPage page) + { + page.Content = scroll; + } + else + if (view is ContentView contentView) + { + contentView.Content = scroll; + } + else + if (view is Grid grid) + { + grid.Children.Add(scroll); + } + else + if (view is StackLayout stack) + { + stack.Children.Add(scroll); + } + else + { + throw e; + } + } + + } + + + public static void Log(string message, [CallerMemberName] string caller = null) + { + //TODO use ILogger with levels etc + +#if WINDOWS + Trace.WriteLine(message); +#else + Console.WriteLine(message); +#endif + } + + + public static IServiceProvider Services + { + get + { + var services = AppContext?.Services; + if (services == null) + { + services = +#if WINDOWS10_0_17763_0_OR_GREATER + MauiWinUIApplication.Current.Services; +#elif ANDROID + MauiApplication.Current.Services; +#elif IOS || MACCATALYST + MauiUIApplicationDelegate.Current.Services; +#else + null; +#endif + } + return services; + } + } + + public static IMauiContext AppContext => Application.Current?.FindMauiContext(); + +#if WINDOWS + + // Win32 API constants and functions + private const int GWL_STYLE = -16; + private const int WS_THICKFRAME = 0x00040000; + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + private static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); + + public static void MakeWindowNonResizable(IntPtr hWnd) + { + // Get the current window style + IntPtr style = GetWindowLongPtr(hWnd, GWL_STYLE); + + // Remove the resize border (thick frame) from the style + style = new IntPtr(style.ToInt64() & ~WS_THICKFRAME); + + // Set the modified style + SetWindowLongPtr(hWnd, GWL_STYLE, style); + } + +#endif + + /// + /// For desktop platforms, will resize app window and eventually lock it from being resized. + /// + /// + /// + /// + /// + public static void ResizeWindow(Window window, int width, int height, bool isFixed) + { + + + var disp = DeviceDisplay.Current.MainDisplayInfo; + // move to screen center + var x = (disp.Width / disp.Density - window.Width) / 2; + var y = (disp.Height / disp.Density - window.Height) / 2; + + //this crashes in NET8 for CATALYST so.. +#if !MACCATALYST + + window.Width = width; + window.Height = height; + window.X = x; + window.Y = y; + +#else + + var platformWindow = window.Handler?.PlatformView as UIKit.UIWindow; + MainThread.BeginInvokeOnMainThread(() => + { + var frame = new CoreGraphics.CGRect(x, y, platformWindow.Frame.Width, platformWindow.Frame.Height); + + platformWindow.Frame = frame; + + var windowScene = UIKit.UIApplication.SharedApplication.ConnectedScenes.ToArray().First() as UIKit.UIWindowScene; + + platformWindow.WindowScene.KeyWindow.Frame = frame; + + windowScene.RequestGeometryUpdate( + new UIKit.UIWindowSceneGeometryPreferencesMac(frame), + error => + { + var stopp = 1; + }); + }); + +#endif + +#if WINDOWS + + if (isFixed) + { + var platformWindow = window.Handler?.PlatformView as Microsoft.Maui.MauiWinUIWindow; + var hWnd = platformWindow.WindowHandle; + MakeWindowNonResizable(hWnd); + } + +#elif MACCATALYST + + foreach (var scene in UIKit.UIApplication.SharedApplication.ConnectedScenes) + { + if (scene is UIKit.UIWindowScene windowScene) + { + if (isFixed) + { + //windowScene.SizeRestrictions.AllowsFullScreen = false; + //windowScene.SizeRestrictions.MinimumSize = new(width, height); + //windowScene.SizeRestrictions.MaximumSize = new(width, height); + + var scale = windowScene.Screen.Scale; + + // Tasks.StartDelayed(TimeSpan.FromSeconds(3),()=> + // { + //todo move to view appeared etc + // MainThread.BeginInvokeOnMainThread(()=> + // { + // windowScene.RequestGeometryUpdate( + // new UIKit.UIWindowSceneGeometryPreferencesMac(frame), + // error => + // { + // var stopp=1; + // }); + // + //}); + + // }); + + } + + } + } + +#endif + + } + } +} diff --git a/src/Engine/Super.cs b/src/Engine/Super.cs index 5695097e..be62711d 100644 --- a/src/Engine/Super.cs +++ b/src/Engine/Super.cs @@ -53,52 +53,6 @@ protected static void SetupFrameLooper() /// public static bool CanUseHardwareAcceleration = true; - /// - /// Display xaml page creation exception - /// - /// - /// - public static void DisplayException(Element view, Exception e) - { - Trace.WriteLine(e); - - if (view == null) - throw e; - - var scroll = new ScrollView() - { - Content = new Label - { - Margin = new Thickness(32), - TextColor = Colors.Red, - Text = $"{e}" - } - }; - - if (view is ContentPage page) - { - page.Content = scroll; - } - else - if (view is ContentView contentView) - { - contentView.Content = scroll; - } - else - if (view is Grid grid) - { - grid.Children.Add(scroll); - } - else - if (view is StackLayout stack) - { - stack.Children.Add(scroll); - } - else - { - throw e; - } - } public static void Log(Exception e, [CallerMemberName] string caller = null) { @@ -111,16 +65,6 @@ public static void Log(Exception e, [CallerMemberName] string caller = null) #endif } - public static void Log(string message, [CallerMemberName] string caller = null) - { - //TODO use ILogger with levels etc - -#if WINDOWS - Trace.WriteLine(message); -#else - Console.WriteLine(message); -#endif - } public static void SetLocale(string lang) { @@ -202,29 +146,6 @@ public static bool FontSubPixelRendering private static bool _servicesFromHandler; - public static IServiceProvider Services - { - get - { - var services = AppContext?.Services; - if (services == null) - { - services = -#if WINDOWS10_0_17763_0_OR_GREATER - MauiWinUIApplication.Current.Services; -#elif ANDROID - MauiApplication.Current.Services; -#elif IOS || MACCATALYST - MauiUIApplicationDelegate.Current.Services; -#else - null; -#endif - } - return services; - } - } - - public static IMauiContext AppContext => Application.Current?.FindMauiContext(); private static Screen _screen; public static Screen Screen @@ -271,8 +192,7 @@ public static long GetCurrentTimeNanos() /// public static double BottomTabsHeight { get; set; } = 56; - public static Color ColorAccent { get; set; } = Colors.Orange; - public static Color ColorPrimary { get; set; } = Colors.Gray; + public static double MaxFrameLengthMs { get; set; } @@ -289,128 +209,9 @@ public static long GetCurrentTimeNanos() public static Size InitialWindowSize; -#if WINDOWS - - // Win32 API constants and functions - private const int GWL_STYLE = -16; - private const int WS_THICKFRAME = 0x00040000; - - [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] - private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); - - [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] - private static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); - - public static void MakeWindowNonResizable(IntPtr hWnd) - { - // Get the current window style - IntPtr style = GetWindowLongPtr(hWnd, GWL_STYLE); - - // Remove the resize border (thick frame) from the style - style = new IntPtr(style.ToInt64() & ~WS_THICKFRAME); - - // Set the modified style - SetWindowLongPtr(hWnd, GWL_STYLE, style); - } - -#endif static bool _attachedToWindow; - /// - /// For desktop platforms, will resize app window and eventually lock it from being resized. - /// - /// - /// - /// - /// - public static void ResizeWindow(Window window, int width, int height, bool isFixed) - { - - - var disp = DeviceDisplay.Current.MainDisplayInfo; - // move to screen center - var x = (disp.Width / disp.Density - window.Width) / 2; - var y = (disp.Height / disp.Density - window.Height) / 2; - - //this crashes in NET8 for CATALYST so.. -#if !MACCATALYST - - window.Width = width; - window.Height = height; - window.X = x; - window.Y = y; - -#else - - var platformWindow = window.Handler?.PlatformView as UIKit.UIWindow; - MainThread.BeginInvokeOnMainThread(() => - { - var frame = new CoreGraphics.CGRect(x, y, platformWindow.Frame.Width, platformWindow.Frame.Height); - - platformWindow.Frame = frame; - - var windowScene = UIKit.UIApplication.SharedApplication.ConnectedScenes.ToArray().First() as UIKit.UIWindowScene; - - platformWindow.WindowScene.KeyWindow.Frame = frame; - - windowScene.RequestGeometryUpdate( - new UIKit.UIWindowSceneGeometryPreferencesMac(frame), - error => - { - var stopp = 1; - }); - }); - -#endif - -#if WINDOWS - - if (isFixed) - { - var platformWindow = window.Handler?.PlatformView as Microsoft.Maui.MauiWinUIWindow; - var hWnd = platformWindow.WindowHandle; - MakeWindowNonResizable(hWnd); - } - -#elif MACCATALYST - - foreach (var scene in UIKit.UIApplication.SharedApplication.ConnectedScenes) - { - if (scene is UIKit.UIWindowScene windowScene) - { - if (isFixed) - { - //windowScene.SizeRestrictions.AllowsFullScreen = false; - //windowScene.SizeRestrictions.MinimumSize = new(width, height); - //windowScene.SizeRestrictions.MaximumSize = new(width, height); - - var scale = windowScene.Screen.Scale; - - // Tasks.StartDelayed(TimeSpan.FromSeconds(3),()=> - // { - //todo move to view appeared etc - // MainThread.BeginInvokeOnMainThread(()=> - // { - // windowScene.RequestGeometryUpdate( - // new UIKit.UIWindowSceneGeometryPreferencesMac(frame), - // error => - // { - // var stopp=1; - // }); - // - //}); - - // }); - - } - - } - } - -#endif - - } public static void OnCreated() { @@ -619,5 +420,4 @@ protected static async Task ProcessBackgroundQueue() } } - -} \ No newline at end of file +} diff --git a/src/samples/Sandbox/MainPageCodeDev.cs b/src/samples/Sandbox/MainPageCodeDev.cs index 74c7e383..2ae961b4 100644 --- a/src/samples/Sandbox/MainPageCodeDev.cs +++ b/src/samples/Sandbox/MainPageCodeDev.cs @@ -1,6 +1,5 @@ using Sandbox.Views; using Canvas = DrawnUi.Maui.Views.Canvas; -using TextTransform = DrawnUi.Maui.Draw.TextTransform; namespace Sandbox { @@ -62,12 +61,13 @@ void Build() CornerRadius = 10, Padding = 10, BackgroundColor = Colors.White, - Content = new SkiaMarkdownLabel() - { - Text = "hello ppl how are you\r\nNew line comes here\r\nAnd another one too", - TextTransform = TextTransform.Titlecase, - MaxLines = 2 - } + Content = new View1() + //Content = new SkiaLabel() + //{ + // Text = "hello ppl how are you\r\nNew line comes here\r\nAnd another one too", + // TextTransform = TextTransform.Titlecase, + // MaxLines = 2 + //} } diff --git a/src/samples/Sandbox/Sandbox.csproj b/src/samples/Sandbox/Sandbox.csproj index 2ed170b6..1fa5d180 100644 --- a/src/samples/Sandbox/Sandbox.csproj +++ b/src/samples/Sandbox/Sandbox.csproj @@ -218,6 +218,11 @@ + + + + + MainPageShapes.xaml @@ -375,6 +380,9 @@ + + MSBuild:Compile + MSBuild:Compile diff --git a/src/samples/Sandbox/View1.xaml b/src/samples/Sandbox/View1.xaml new file mode 100644 index 00000000..458a4f07 --- /dev/null +++ b/src/samples/Sandbox/View1.xaml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/src/samples/Sandbox/View1.xaml.cs b/src/samples/Sandbox/View1.xaml.cs new file mode 100644 index 00000000..562d09cb --- /dev/null +++ b/src/samples/Sandbox/View1.xaml.cs @@ -0,0 +1,17 @@ +namespace Sandbox +{ + public partial class View1 : SkiaLayout + { + public View1() + { + try + { + InitializeComponent(); + } + catch (Exception e) + { + Super.DisplayException(this, e); + } + } + } +} From b7a45fc6503b94018819f385fdbfaa1b02c13199 Mon Sep 17 00:00:00 2001 From: Nick Kovalsky Date: Fri, 6 Dec 2024 13:04:03 +0300 Subject: [PATCH 7/8] Super.OffscreenRenderingAtCanvasLevel Reverted background rendering to faster previous --- src/Engine/Draw/Base/SkiaControl.Cache.cs | 67 ++++++++++++++- src/Engine/Super.cs | 15 +++- src/Engine/Views/DrawnView.cs | 86 +++++++++++-------- src/samples/Sandbox/Resources/Styles.xaml | 4 +- src/samples/Sandbox/View1.xaml | 16 ++-- .../Views/MainPageDynamicHeightCells.xaml | 4 +- 6 files changed, 141 insertions(+), 51 deletions(-) diff --git a/src/Engine/Draw/Base/SkiaControl.Cache.cs b/src/Engine/Draw/Base/SkiaControl.Cache.cs index e70c87f0..0bfd6369 100644 --- a/src/Engine/Draw/Base/SkiaControl.Cache.cs +++ b/src/Engine/Draw/Base/SkiaControl.Cache.cs @@ -469,12 +469,75 @@ public Action GetOffscreenRenderingAction() public void PushToOffscreenRendering(Action action) { - _offscreenCacheRenderingQueue.Push(action); - Superview?.PushToOffscreenRendering(this); + if (Super.OffscreenRenderingAtCanvasLevel) + { + _offscreenCacheRenderingQueue.Push(action); + Superview?.PushToOffscreenRendering(this); + } + else + { + _offscreenCacheRenderingQueue.Push(action); + if (!_processingOffscrenRendering) + { + _processingOffscrenRendering = true; + Task.Run(async () => + { + await ProcessOffscreenCacheRenderingAsync(); + }).ConfigureAwait(false); + } + } } + private bool _processingOffscrenRendering = false; + protected SemaphoreSlim semaphoreOffsecreenProcess = new(1); + + public async Task ProcessOffscreenCacheRenderingAsync() + { + + await semaphoreOffsecreenProcess.WaitAsync(); + + if (_offscreenCacheRenderingQueue.Count == 0) + return; + + _processingOffscrenRendering = true; + + try + { + Action action = _offscreenCacheRenderingQueue.Pop(); + while (!IsDisposed && !IsDisposing && action != null) + { + try + { + action.Invoke(); + + if (_offscreenCacheRenderingQueue.Count > 0) + action = _offscreenCacheRenderingQueue.Pop(); + else + break; + } + catch (Exception e) + { + Super.Log(e); + } + } + + //if (NeedUpdate || RenderObjectNeedsUpdate) //someone changed us while rendering inner content + //{ + // Update(); //kick + //} + + } + finally + { + _processingOffscrenRendering = false; + semaphoreOffsecreenProcess.Release(); + } + + } + + /// /// Used by the UseCacheDoubleBuffering process. This is the new cache beign created in background. It will be copied to RenderObject when ready. diff --git a/src/Engine/Super.cs b/src/Engine/Super.cs index be62711d..f4cdb67e 100644 --- a/src/Engine/Super.cs +++ b/src/Engine/Super.cs @@ -31,14 +31,21 @@ public partial class Super { static Super() { - //Tasks.StartDelayed(TimeSpan.FromSeconds(1), () => - //{ - // ProcessBackgroundQueue().ConfigureAwait(false); - //}); + } + /// + /// Experimental for dev use. Set OffscreenRenderingAtCanvasLevel to true when this is true. + /// Default is False + /// public static bool Multithreaded = false; + /// + /// If set to True will process all ofscreen rendering in one background thread at canvas level, otherwise every control will launch its own background processing thread. + /// Default is False + /// + public static bool OffscreenRenderingAtCanvasLevel { get; set; } + #if (!ONPLATFORM) protected static void SetupFrameLooper() diff --git a/src/Engine/Views/DrawnView.cs b/src/Engine/Views/DrawnView.cs index 40bdc0e0..cf5c6344 100644 --- a/src/Engine/Views/DrawnView.cs +++ b/src/Engine/Views/DrawnView.cs @@ -1774,14 +1774,28 @@ protected void CommitInvalidations() invalidations.Clear(); } - #region BACKGROUND RENDERING + + + + #region BACKGROUND RENDERING + protected object LockStartOffscreenQueue = new(); private bool _processingOffscrenRendering = false; + // Holds the incoming commands without blocking + private readonly ConcurrentQueue _incomingCommands = new ConcurrentQueue(); + + // Holds the latest commands for each control (only processed by background thread) + private readonly Dictionary _offscreenCommands = new(); + + protected SemaphoreSlim semaphoreOffscreenProcess = new(1); + + public record OffscreenCommand(SkiaControl Control); + /// - /// Make sure offscreen rendering queue is running + /// Ensures offscreen rendering queue is running /// public void KickOffscreenCacheRendering() { @@ -1790,60 +1804,73 @@ public void KickOffscreenCacheRendering() if (!_processingOffscrenRendering) { _processingOffscrenRendering = true; - Task.Run(async () => //100% background thread + Task.Run(async () => { await ProcessOffscreenCacheRenderingAsync(); - }).ConfigureAwait(false); } } } + /// + /// Push an offscreen rendering command without blocking the UI thread. + /// public void PushToOffscreenRendering(SkiaControl control) { - _offscreenCacheRenderingQueue.Enqueue(new OffscreenCommand(control)); + _incomingCommands.Enqueue(new OffscreenCommand(control)); KickOffscreenCacheRendering(); } - public record OffscreenCommand(SkiaControl Control); - - protected SemaphoreSlim semaphoreOffscreenProcess = new(1); - - private readonly Queue _offscreenCacheRenderingQueue = new(); - public async Task ProcessOffscreenCacheRenderingAsync() { - await semaphoreOffscreenProcess.WaitAsync(); - try { - if (_offscreenCacheRenderingQueue.Count == 0) + // Drain the ConcurrentQueue into a local list + var drainedCommands = new List(); + while (_incomingCommands.TryDequeue(out var cmd)) + { + drainedCommands.Add(cmd); + } + + // If nothing was drained, we can safely return + if (drainedCommands.Count == 0) return; + lock (_offscreenCommands) + { + foreach (var command in drainedCommands) + { + _offscreenCommands[command.Control] = command; + } + } + + // Process the latest commands now + // Reading dictionary under lock might not be strictly necessary if we trust only this background thread modifies it + // but we can snapshot under lock for safety. + OffscreenCommand[] toProcess; + lock (_offscreenCommands) + { + toProcess = _offscreenCommands.Values.ToArray(); + _offscreenCommands.Clear(); // We clear after processing to avoid memory growth + } + _processingOffscrenRendering = true; - var command = _offscreenCacheRenderingQueue.Dequeue(); - while (command != null) + foreach (var command in toProcess) { try { if (command.Control.IsDisposed || command.Control.IsDisposing) { - _offscreenCacheRenderingQueue.Clear(); - break; + // If control is no longer valid, skip it. + continue; } var action = command.Control.GetOffscreenRenderingAction(); action?.Invoke(); //command.Control.Repaint(); - - if (_offscreenCacheRenderingQueue.Count > 0) - command = _offscreenCacheRenderingQueue.Dequeue(); - else - break; - } catch (Exception e) { @@ -1851,26 +1878,17 @@ public async Task ProcessOffscreenCacheRenderingAsync() } } - //if (NeedUpdate || RenderObjectNeedsUpdate) //someone changed us while rendering inner content - //{ - // Update(); //kick - //} - //Update(); //kick - - - } finally { _processingOffscrenRendering = false; semaphoreOffscreenProcess.Release(); } - } - #endregion + protected virtual void Draw(SkiaDrawingContext context, SKRect destination, float scale) { ++renderedFrames; diff --git a/src/samples/Sandbox/Resources/Styles.xaml b/src/samples/Sandbox/Resources/Styles.xaml index d69f83da..18260473 100644 --- a/src/samples/Sandbox/Resources/Styles.xaml +++ b/src/samples/Sandbox/Resources/Styles.xaml @@ -5,7 +5,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:draw="http://schemas.appomobi.com/drawnUi/2023/draw"> - diff --git a/src/samples/Sandbox/View1.xaml b/src/samples/Sandbox/View1.xaml index 458a4f07..3dc5fbfa 100644 --- a/src/samples/Sandbox/View1.xaml +++ b/src/samples/Sandbox/View1.xaml @@ -1,12 +1,12 @@ - - + - - + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + xmlns:draw="http://schemas.appomobi.com/drawnUi/2023/draw"> - + + + \ No newline at end of file diff --git a/src/samples/Sandbox/Views/MainPageDynamicHeightCells.xaml b/src/samples/Sandbox/Views/MainPageDynamicHeightCells.xaml index 7b7d5d7b..31280f0d 100644 --- a/src/samples/Sandbox/Views/MainPageDynamicHeightCells.xaml +++ b/src/samples/Sandbox/Views/MainPageDynamicHeightCells.xaml @@ -66,14 +66,14 @@ --> + Type="Column" + VirtualisationInflated="350"> From d5414d7224491724b63f053c3f07f2d35b7b4bdd Mon Sep 17 00:00:00 2001 From: Nick Kovalsky Date: Fri, 6 Dec 2024 13:39:48 +0300 Subject: [PATCH 8/8] 1.2.9.9 * ImageDoubleBuffered rendering gets faster * TextTransform property for SkiaLabel: Lowercase, Uppercase, Titlecase, Phrasecase * SkiaButton TinColor removed, applying Background and BackgroundColor to button frame * Nuget iOS 17 compatibility restored * Some more fixes --- README.md | 31 +++++++++---------- dev/github_uploadnugets.bat | 4 +-- dev/nuget_uploadnugets.bat | 4 +-- .../DrawnUi.Maui.Camera.csproj | 6 ++-- .../DrawnUi.Maui.Game.csproj | 6 ++-- .../src/DrawnUi.Maui.MapsUi.csproj | 6 ++-- .../DrawnUi.Maui.Rive.csproj | 6 ++-- .../DrawnUi.MauiGraphics.csproj | 6 ++-- src/Directory.Build.props | 2 +- src/Engine/DrawnUi.Maui.csproj | 4 +-- src/samples/Sandbox/MainPage.xaml | 2 +- src/samples/Sandbox/Sandbox.csproj | 2 +- 12 files changed, 39 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index f27d66bf..7e522418 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,6 @@ 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 an improved common MAUI layout system. -**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. @@ -20,9 +16,23 @@ Supports **iOS**, **MacCatalyst**, **Android**, **Windows**. _The current development state is __ALPHA__, features remain to be implemented, documentation incoming._ +## What's New + +### Nuget 1.2.9.9 +_for SkiaSharp 2.88.9-preview.2.2_ + +* ImageDoubleBuffered rendering gets faster +* TextTransform property for SkiaLabel: Lowercase, Uppercase, Titlecase, Phrasecase +* SkiaButton TinColor removed, applying Background and BackgroundColor to button frame +* Nuget iOS 17 compatibility restored +* Some more fixes +* Still using SkiaSharp 2.88.9-preview.2.2 +until [scaling issue](https://github.com/taublast/DrawnUi.Maui/issues/130) is solved for Windows + + https://github.com/taublast/DrawnUi.Maui/assets/25801194/3b360229-ce3b-4d33-a85b-554d1cca8408 -___Please star ⭐ if you like it, helps very much!___ +___Please star ⭐ if you like it!___ ## Features @@ -92,17 +102,6 @@ ___Please star ⭐ if you like it, helps very much!___ * Not just accelerators, but full keyboard support, usage example inside __SpaceShooter__ game below. :) -## What's New - -### Nuget 1.2.9.8 -for SkiaSharp 2.88.9-preview.2.2 - -* DisposeObject optimized for background. -* Templated layout measurement optimized. -* HotFix for gestures crash on iOS when targeting ios lower than 18. -* Nuget targets ios 17. - - ## About [A small article](https://taublast.github.io/posts/MauiJuly/) about the library and why it was created diff --git a/dev/github_uploadnugets.bat b/dev/github_uploadnugets.bat index 0558fdbb..edf39b06 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.8*.nupkg" -set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.8*.*nupkg" +set "mask[1]=DrawnUi.Maui*.1.2.9.9*.nupkg" +set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.9*.*nupkg" set "mask_count=2" REM Loop through each file mask diff --git a/dev/nuget_uploadnugets.bat b/dev/nuget_uploadnugets.bat index 6b5d8d92..6456a782 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.8*.nupkg" -set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.8*.*nupkg" +set "mask[1]=DrawnUi.Maui*.1.2.9.9*.nupkg" +set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.9*.*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 566b3e62..cc40a97f 100644 --- a/src/Addons/DrawnUi.Maui.Camera/DrawnUi.Maui.Camera.csproj +++ b/src/Addons/DrawnUi.Maui.Camera/DrawnUi.Maui.Camera.csproj @@ -1,7 +1,7 @@  - net8.0;net8.0-android;net8.0-ios17.0;net8.0-maccatalyst17.0 - $(TargetFrameworks);net8.0-windows10.0.19041.0 + net8.0;net8.0-android34.0;net8.0-ios17.0;net8.0-maccatalyst17.0; + $(TargetFrameworks);net8.0-windows10.0.19041.0; true true enable @@ -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 1e2b06a2..e0f94d65 100644 --- a/src/Addons/DrawnUi.Maui.Game/DrawnUi.Maui.Game.csproj +++ b/src/Addons/DrawnUi.Maui.Game/DrawnUi.Maui.Game.csproj @@ -1,7 +1,7 @@  - net8.0;net8.0-android;net8.0-ios17.0;net8.0-maccatalyst17.0 - $(TargetFrameworks);net8.0-windows10.0.19041.0 + net8.0;net8.0-android34.0;net8.0-ios17.0;net8.0-maccatalyst17.0; + $(TargetFrameworks);net8.0-windows10.0.19041.0; true true enable @@ -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 afb2499a..20f1dc15 100644 --- a/src/Addons/DrawnUi.Maui.MapsUi/src/DrawnUi.Maui.MapsUi.csproj +++ b/src/Addons/DrawnUi.Maui.MapsUi/src/DrawnUi.Maui.MapsUi.csproj @@ -1,7 +1,7 @@  - net8.0;net8.0-android;net8.0-ios17.0;net8.0-maccatalyst17.0 - $(TargetFrameworks);net8.0-windows10.0.19041.0 + net8.0;net8.0-android34.0;net8.0-ios17.0;net8.0-maccatalyst17.0; + $(TargetFrameworks);net8.0-windows10.0.19041.0; true true enable @@ -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 0ff5d8ec..56db2fbd 100644 --- a/src/Addons/DrawnUi.Maui.Rive/DrawnUi.Maui.Rive.csproj +++ b/src/Addons/DrawnUi.Maui.Rive/DrawnUi.Maui.Rive.csproj @@ -1,7 +1,7 @@  - net8.0;net8.0-android;net8.0-ios17.0;net8.0-maccatalyst17.0 - $(TargetFrameworks);net8.0-windows10.0.19041.0 + net8.0;net8.0-android34.0;net8.0-ios17.0;net8.0-maccatalyst17.0; + $(TargetFrameworks);net8.0-windows10.0.19041.0; true true enable @@ -47,7 +47,7 @@ - + diff --git a/src/Addons/DrawnUi.MauiGraphics/DrawnUi.MauiGraphics.csproj b/src/Addons/DrawnUi.MauiGraphics/DrawnUi.MauiGraphics.csproj index bac1512b..ffc8d472 100644 --- a/src/Addons/DrawnUi.MauiGraphics/DrawnUi.MauiGraphics.csproj +++ b/src/Addons/DrawnUi.MauiGraphics/DrawnUi.MauiGraphics.csproj @@ -1,7 +1,7 @@  - net8.0;net8.0-android;net8.0-ios17.0;net8.0-maccatalyst17.0 - $(TargetFrameworks);net8.0-windows10.0.19041.0 + net8.0;net8.0-android34.0;net8.0-ios17.0;net8.0-maccatalyst17.0; + $(TargetFrameworks);net8.0-windows10.0.19041.0; true true enable @@ -41,7 +41,7 @@ - + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index fef7825c..a2df93fb 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.8 + 1.2.9.9 diff --git a/src/Engine/DrawnUi.Maui.csproj b/src/Engine/DrawnUi.Maui.csproj index 0b51f62a..7ffe072c 100644 --- a/src/Engine/DrawnUi.Maui.csproj +++ b/src/Engine/DrawnUi.Maui.csproj @@ -1,7 +1,7 @@  - net8.0;net8.0-android;net8.0-ios17.0;net8.0-maccatalyst17.0 - $(TargetFrameworks);net8.0-windows10.0.19041.0 + net8.0;net8.0-android34.0;net8.0-ios17.0;net8.0-maccatalyst17.0; + $(TargetFrameworks);net8.0-windows10.0.19041.0; true true enable diff --git a/src/samples/Sandbox/MainPage.xaml b/src/samples/Sandbox/MainPage.xaml index eb61383f..386505a5 100644 --- a/src/samples/Sandbox/MainPage.xaml +++ b/src/samples/Sandbox/MainPage.xaml @@ -154,6 +154,7 @@ UseCache="Image"> True - + manual