diff --git a/README.md b/README.md
index 81d758e6..20343c54 100644
--- a/README.md
+++ b/README.md
@@ -103,17 +103,17 @@ V3 preview: subclassed `SkiaShaderEffect`, implementing `ISkiaGestureProcessor`,
## What's New
-### Nuget 1.2.9.2
+### Nuget 1.2.9.4
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
+* SkiaShape new Types: Polygon and Line. New property for their Points: Smooth (0-1) to smooth angles.
+* Shapes demo page inside SandBox project.
+* VisualElement Shadow property now supported everywhere as an optional addition to existing shadows.
+* Removed SkiaImage clipping to better support shadows.
+* SkiaLabel new property AutoFont: Find and set system font where the first glyph in text is present. Useful for some quick unicode rendering like emoji etc.
+* Updated Getsures nuget for correct lock inside MAUI native ScrollView, use Getures="Lock" for Canvas.
+* Fixed controls sometimes not invalidated when canvas suface size changes
+* Other fixes.
## Development Notes
diff --git a/dev/github_uploadnugets.bat b/dev/github_uploadnugets.bat
index c7b82b83..b34e6ae1 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.2*.nupkg"
-set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.2*.*nupkg"
+set "mask[1]=DrawnUi.Maui*.1.2.9.4*.nupkg"
+set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.4*.*nupkg"
set "mask_count=2"
REM Loop through each file mask
diff --git a/dev/nuget_uploadnugets.bat b/dev/nuget_uploadnugets.bat
index 43c915a0..f7511333 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.2*.nupkg"
-set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.2*.*nupkg"
+set "mask[1]=DrawnUi.Maui*.1.2.9.4*.nupkg"
+set "mask[2]=AppoMobi.Maui.DrawnUi.1.2.9.4*.*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 7430a7ef..0b2fb24c 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 8ce580ea..b3f84001 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 06ce899e..b43dc124 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 272db962..6db77439 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 26973555..be0af46e 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 3d6f4fde..bb1a9409 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.2
+ 1.2.9.4
diff --git a/src/Engine/Controls/Carousel/SkiaCarousel.cs b/src/Engine/Controls/Carousel/SkiaCarousel.cs
index 2ceadef2..4eeeca00 100644
--- a/src/Engine/Controls/Carousel/SkiaCarousel.cs
+++ b/src/Engine/Controls/Carousel/SkiaCarousel.cs
@@ -1,4 +1,5 @@
-using System.Numerics;
+using System.Collections.Concurrent;
+using System.Numerics;
using SkiaControl = DrawnUi.Maui.Draw.SkiaControl;
namespace DrawnUi.Maui.Controls;
@@ -65,7 +66,7 @@ public override void ScrollToNearestAnchor(Vector2 location, Vector2 velocity)
public event EventHandler Stopped;
- protected Dictionary ItemsVisibility { get; } = new();
+ protected ConcurrentDictionary ItemsVisibility { get; } = new();
void SendVisibility(int index, bool state)
{
@@ -86,6 +87,8 @@ void SendVisibility(int index, bool state)
}
}
+
+
void InitializeItemsVisibility(int count, bool force)
{
if (force || ItemsVisibility.Count != count)
@@ -1355,93 +1358,93 @@ void ResetPan()
{
case TouchActionResult.Down:
- // if (!IsUserFocused) //first finger down
- if (args.Event.NumberOfTouches == 1) //first finger down
- {
- ResetPan();
- }
+ // if (!IsUserFocused) //first finger down
+ if (args.Event.NumberOfTouches == 1) //first finger down
+ {
+ ResetPan();
+ }
- consumed = this;
+ consumed = this;
- break;
+ break;
case TouchActionResult.Panning when args.Event.NumberOfTouches == 1:
- if (!IsUserPanning)
- {
- //first pan
- if (args.Event.Distance.Total.X == 0 || Math.Abs(args.Event.Distance.Total.Y) > Math.Abs(args.Event.Distance.Total.X) || Math.Abs(args.Event.Distance.Total.X) < 2)
+ if (!IsUserPanning)
{
- return null;
+ //first pan
+ if (args.Event.Distance.Total.X == 0 || Math.Abs(args.Event.Distance.Total.Y) > Math.Abs(args.Event.Distance.Total.X) || Math.Abs(args.Event.Distance.Total.X) < 2)
+ {
+ return null;
+ }
}
- }
- if (!IsUserFocused)
- {
- ResetPan();
- }
+ if (!IsUserFocused)
+ {
+ ResetPan();
+ }
- //todo add direction
- //this.IgnoreWrongDirection
+ //todo add direction
+ //this.IgnoreWrongDirection
- IsUserPanning = true;
+ IsUserPanning = true;
- var x = _panningOffset.X + args.Event.Distance.Delta.X / RenderingScale;
- var y = _panningOffset.Y + args.Event.Distance.Delta.Y / RenderingScale;
+ var x = _panningOffset.X + args.Event.Distance.Delta.X / RenderingScale;
+ var y = _panningOffset.Y + args.Event.Distance.Delta.Y / RenderingScale;
- Vector2 velocity;
- float useVelocity = 0;
- if (!IsVertical)
- {
- useVelocity = (float)(args.Event.Distance.Velocity.X / RenderingScale);
- velocity = new(useVelocity, 0);
- }
- else
- {
- useVelocity = (float)(args.Event.Distance.Velocity.Y / RenderingScale);
- velocity = new(0, useVelocity);
- }
+ Vector2 velocity;
+ float useVelocity = 0;
+ if (!IsVertical)
+ {
+ useVelocity = (float)(args.Event.Distance.Velocity.X / RenderingScale);
+ velocity = new(useVelocity, 0);
+ }
+ else
+ {
+ useVelocity = (float)(args.Event.Distance.Velocity.Y / RenderingScale);
+ velocity = new(0, useVelocity);
+ }
- //record velocity
- VelocityAccumulator.CaptureVelocity(velocity);
+ //record velocity
+ VelocityAccumulator.CaptureVelocity(velocity);
- //saving non clamped
- _panningOffset.X = x;
- _panningOffset.Y = y;
+ //saving non clamped
+ _panningOffset.X = x;
+ _panningOffset.Y = y;
- var clamped = ClampOffset((float)x, (float)y, Bounces);
+ var clamped = ClampOffset((float)x, (float)y, Bounces);
- //Debug.WriteLine($"[CAROUSEL] Panning: {_panningOffset:0} / {clamped:0}");
- ApplyPosition(clamped);
+ //Debug.WriteLine($"[CAROUSEL] Panning: {_panningOffset:0} / {clamped:0}");
+ ApplyPosition(clamped);
- consumed = this;
- break;
+ consumed = this;
+ break;
case TouchActionResult.Up:
- //Debug.WriteLine($"[Carousel] {args.Type} {IsUserFocused} {IsUserPanning} {InTransition}");
+ //Debug.WriteLine($"[Carousel] {args.Type} {IsUserFocused} {IsUserPanning} {InTransition}");
- if (IsUserFocused)
- {
-
- if (IsUserPanning || InTransition)
+ if (IsUserFocused)
{
- consumed = this;
- var final = VelocityAccumulator.CalculateFinalVelocity(500);
+ if (IsUserPanning || InTransition)
+ {
+ consumed = this;
- //animate
- CurrentSnap = CurrentPosition;
+ var final = VelocityAccumulator.CalculateFinalVelocity(500);
- ScrollToNearestAnchor(CurrentSnap, final);
- }
+ //animate
+ CurrentSnap = CurrentPosition;
- IsUserPanning = false;
- IsUserFocused = false;
+ ScrollToNearestAnchor(CurrentSnap, final);
+ }
- }
+ IsUserPanning = false;
+ IsUserFocused = false;
+
+ }
- break;
+ break;
}
if (consumed != null || IsUserPanning)
@@ -1456,4 +1459,4 @@ void ResetPan()
}
#endregion
-}
\ No newline at end of file
+}
diff --git a/src/Engine/Controls/Slider/SkiaSlider.cs b/src/Engine/Controls/Slider/SkiaSlider.cs
index 8fcb97ad..3a9dcf2b 100644
--- a/src/Engine/Controls/Slider/SkiaSlider.cs
+++ b/src/Engine/Controls/Slider/SkiaSlider.cs
@@ -38,6 +38,8 @@ protected override void OnLayoutChanged()
public override ISkiaGestureListener ProcessGestures(SkiaGesturesParameters args, GestureEventProcessingInfo apply)
{
+ //Super.Log($"[Touch] SLIDER got {args.Type}");
+
bool passedToChildren = false;
ISkiaGestureListener PassToChildren()
@@ -183,7 +185,7 @@ void ResetPan()
IsUserPanning = true;
- //synch this
+ //synch this
if (touchArea == RangeZone.Start)
lastTouchX = StartThumbX;
else
@@ -205,6 +207,8 @@ void ResetPan()
{
var maybe = lastTouchX + args.Event.Distance.Delta.X / RenderingScale;
SetEndOffsetClamped(maybe);
+
+ //Super.Log($"[Touch] SLIDER zone END {maybe}");
}
RecalculateValues();
diff --git a/src/Engine/Draw/Base/SkiaControl.Maui.cs b/src/Engine/Draw/Base/SkiaControl.Maui.cs
index b792720b..9114b81b 100644
--- a/src/Engine/Draw/Base/SkiaControl.Maui.cs
+++ b/src/Engine/Draw/Base/SkiaControl.Maui.cs
@@ -22,6 +22,10 @@ public partial class SkiaControl : VisualElement,
public static readonly BindableProperty ClearColorProperty = BindableProperty.Create(nameof(ClearColor), typeof(Color), typeof(SkiaControl),
Colors.Transparent,
propertyChanged: NeedDraw);
+
+ private SkiaShadow platformShadow;
+ private SKPath platformClip;
+
public Color ClearColor
{
get { return (Color)GetValue(ClearColorProperty); }
@@ -75,6 +79,16 @@ protected override void OnPropertyChanged([CallerMemberName] string propertyName
Update();
}
else
+ if (propertyName == nameof(Shadow))
+ {
+ UpdatePlatformShadow();
+ }
+ else
+ if (propertyName == nameof(Clip))
+ {
+ Update();
+ }
+ else
if (propertyName.IsEither(
nameof(Padding),
nameof(HorizontalOptions), nameof(VerticalOptions),
@@ -287,9 +301,49 @@ public static SKImageFilter CreateShadow(SkiaShadow shadow, float scale)
}
}
+ protected void UpdatePlatformShadow()
+ {
+ if (this.Shadow != null && Shadow.Brush != null)
+ {
+ PlatformShadow = this.Shadow.FromPlatform();
+ }
+ else
+ {
+ PlatformShadow = null;
+ }
+ }
+
+ protected SkiaShadow PlatformShadow
+ {
+ get => platformShadow;
+ set
+ {
+ if (platformShadow != value)
+ {
+ platformShadow = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ private void GetPlatformClip(SKPath path, SKRect destination, float renderingScale)
+ {
+ if (this.Clip != null)
+ {
+ this.Clip.FromPlatform(path, destination, renderingScale);
+ }
+ }
+
+ protected bool HasPlatformClip()
+ {
+ return Clip != null;
+ }
+
public static float GetDensity()
{
return (float)Super.Screen.Density;
}
+
+
}
}
diff --git a/src/Engine/Draw/Base/SkiaControl.Shared.cs b/src/Engine/Draw/Base/SkiaControl.Shared.cs
index bec80af1..34242037 100644
--- a/src/Engine/Draw/Base/SkiaControl.Shared.cs
+++ b/src/Engine/Draw/Base/SkiaControl.Shared.cs
@@ -1612,6 +1612,8 @@ public bool HasFillGradient
#endregion
+
+
public virtual SKSize GetSizeRequest(float widthConstraint, float heightConstraint, bool insideLayout)
{
widthConstraint *= (float)this.WidthRequestRatio;
@@ -3836,6 +3838,8 @@ protected virtual ScaledSize SetMeasured(float width, float height, bool widthCu
OnMeasured();
+ InvalidatedParent = false;
+
return MeasuredSize;
}
}
@@ -4518,14 +4522,20 @@ public void DrawWithClipAndTransforms(
bool useClipping,
Action draw)
{
- bool isClipping = (WillClipBounds || Clipping != null || ClippedBy != null) && useClipping;
+ bool isClipping = (WillClipBounds || Clipping != null
+ || ClippedBy != null || HasPlatformClip()) && useClipping;
if (isClipping)
{
-
_preparedClipBounds ??= new SKPath();
_preparedClipBounds.Reset();
+
+ if (HasPlatformClip())
+ {
+ GetPlatformClip(_preparedClipBounds, destination, RenderingScale);
+ }
+ else
if (ClippedBy != null)
{
ClippedBy.CreateClip(null, true, _preparedClipBounds);
@@ -4628,7 +4638,7 @@ protected virtual void ApplyTransforms(SkiaDrawingContext ctx, SKRect destinatio
Helper3d.Reset();
Helper3d.RotateXDegrees((float)RotationX);
Helper3d.RotateYDegrees((float)RotationY);
- Helper3d.RotateZDegrees((float)RotationZ);
+ Helper3d.RotateZDegrees(-(float)RotationZ);
Helper3d.Translate(0, 0, (float)TranslationZ);
drawingMatrix = drawingMatrix.PostConcat(Helper3d.Matrix);
@@ -4636,9 +4646,11 @@ protected virtual void ApplyTransforms(SkiaDrawingContext ctx, SKRect destinatio
Helper3d.Save();
Helper3d.RotateXDegrees((float)RotationX);
Helper3d.RotateYDegrees((float)RotationY);
- Helper3d.RotateZDegrees((float)RotationZ);
+ Helper3d.RotateZDegrees(-(float)RotationZ);
Helper3d.TranslateZ((float)TranslationZ);
+
drawingMatrix = drawingMatrix.PostConcat(Helper3d.Matrix);
+
Helper3d.Restore();
#endif
@@ -4774,54 +4786,26 @@ protected void SafeAction(Action action)
Superview.PostponeExecutionAfterDraw(action);
}
- /*
- public async Task ProcessOffscreenCacheRenderingAsync()
- {
-
- await semaphoreOffsecreenProcess.WaitAsync();
- _processingOffscrenRendering = true;
- try
- {
- Action action = _offscreenCacheRenderingQueue.Pop();
- if (!IsDisposed && !IsDisposing && action != null)
- {
- try
- {
- action.Invoke();
+ protected bool NeedRemeasuring;
- RenderObject = RenderObjectPreparing;
- _renderObjectPreparing = null;
- Repaint();
- }
- catch (Exception e)
- {
- Super.Log(e);
- }
- }
+ protected virtual void PaintWithShadows(SkiaDrawingContext ctx, Action render)
+ {
+ if (PlatformShadow != null)
+ {
+ using var paint = new SKPaint() { IsAntialias = true, FilterQuality = SKFilterQuality.Medium };
+ SetupShadow(paint, PlatformShadow, RenderingScale);
+ var saved = ctx.Canvas.SaveLayer(paint);
+ render();
+ ctx.Canvas.RestoreToCount(saved);
}
- finally
+ else
{
- _processingOffscrenRendering = false;
- semaphoreOffsecreenProcess.Release();
-
- if (NeedUpdate || _offscreenCacheRenderingQueue.Count > 0) //someone changed us while rendering inner content
- {
- Update(); //kick
- }
+ render();
}
-
}
- */
-
-
- protected bool NeedRemeasuring;
-
-
-
-
protected virtual void PaintWithEffects(
@@ -4830,9 +4814,12 @@ protected virtual void PaintWithEffects(
if (IsDisposed || IsDisposing)
return;
- void draw(SkiaDrawingContext context)
+ void PaintWithEffectsInternal(SkiaDrawingContext context)
{
- Paint(context, destination, scale, arguments);
+ PaintWithShadows(context, () =>
+ {
+ Paint(context, destination, scale, arguments);
+ });
}
if (!DisableEffects && VisualEffects.Count > 0)
@@ -4870,7 +4857,7 @@ void draw(SkiaDrawingContext context)
{
foreach (var effect in renderers)
{
- var chainedEffectResult = effect.Draw(destination, ctx, draw);
+ var chainedEffectResult = effect.Draw(destination, ctx, PaintWithEffectsInternal);
if (chainedEffectResult.DrawnControl)
hasDrawnControl = true;
}
@@ -4878,14 +4865,14 @@ void draw(SkiaDrawingContext context)
if (!hasDrawnControl)
{
- draw(ctx);
+ PaintWithEffectsInternal(ctx);
}
ctx.Canvas.RestoreToCount(restore);
}
else
{
- draw(ctx);
+ PaintWithEffectsInternal(ctx);
}
}
@@ -5882,14 +5869,14 @@ public bool SetupShadow(SKPaint paint, SkiaShadow shadow, float scale)
Scale = scale,
Shadow = shadow
};
- kill?.Dispose();
+ DisposeObject(kill);
}
var old = paint.ImageFilter;
paint.ImageFilter = LastShadow.Filter;
if (old != paint.ImageFilter)
{
- old?.Dispose();
+ DisposeObject(old);
}
return true;
diff --git a/src/Engine/Draw/Images/SkiaImage.cs b/src/Engine/Draw/Images/SkiaImage.cs
index 6a68cb0b..df80e762 100644
--- a/src/Engine/Draw/Images/SkiaImage.cs
+++ b/src/Engine/Draw/Images/SkiaImage.cs
@@ -69,8 +69,7 @@ public virtual SKImage GetRenderedSource()
return null;
}
- public override bool WillClipBounds => true;
-
+ //public override bool WillClipBounds => true;
public CancellationTokenSource CancelLoading;
diff --git a/src/Engine/Draw/Images/SkiaImageTiles.cs b/src/Engine/Draw/Images/SkiaImageTiles.cs
index b94e60cd..54f2ee9f 100644
--- a/src/Engine/Draw/Images/SkiaImageTiles.cs
+++ b/src/Engine/Draw/Images/SkiaImageTiles.cs
@@ -125,6 +125,11 @@ public override void OnSourceSuccess()
protected virtual SkiaImage CreateTile(double width, double height, LoadedImageSource source)
{
+ if (source != null)
+ {
+ source.ProtectBitmapFromDispose = SkiaImageManager.ReuseBitmaps;
+ }
+
var tile = new SkiaImage()
{
Aspect = this.TileAspect,
@@ -290,4 +295,4 @@ public override void OnDisposing()
Tile = null;
}
-}
\ 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 1adc4fcf..eb26b942 100644
--- a/src/Engine/Draw/Layout/SkiaLayout.ColumnRow.cs
+++ b/src/Engine/Draw/Layout/SkiaLayout.ColumnRow.cs
@@ -306,10 +306,11 @@ public virtual ScaledSize MeasureStack(SKRect rectForChildrenPixels, float scale
var layoutStructure = BuildStackStructure(scale);
bool useOneTemplate =
+ IsTemplated &&
//ItemSizingStrategy == ItemSizingStrategy.MeasureFirstItem &&
RecyclingTemplate == RecyclingTemplate.Enabled;
- if (IsTemplated && useOneTemplate)
+ if (useOneTemplate)
{
template = ChildrenFactory.GetTemplateInstance();
}
@@ -515,7 +516,7 @@ public virtual ScaledSize MeasureStack(SKRect rectForChildrenPixels, float scale
secondPass.Scale);
}
- if (IsTemplated && useOneTemplate)
+ if (useOneTemplate)
{
ChildrenFactory.ReleaseView(template);
}
diff --git a/src/Engine/Draw/Layout/SkiaLayout.Grid.cs b/src/Engine/Draw/Layout/SkiaLayout.Grid.cs
index ed611a13..9cab07f0 100644
--- a/src/Engine/Draw/Layout/SkiaLayout.Grid.cs
+++ b/src/Engine/Draw/Layout/SkiaLayout.Grid.cs
@@ -1,7 +1,7 @@
//Adapted code from the Xamarin.Forms Grid implementation
-using DrawnUi.Maui.Infrastructure.Xaml;
using System.ComponentModel;
+using DrawnUi.Maui.Infrastructure.Xaml;
namespace DrawnUi.Maui.Draw;
@@ -12,6 +12,7 @@ public partial class SkiaLayout
public virtual ScaledSize MeasureGrid(SKRect rectForChildrenPixels, float scale)
{
+ //Trace.WriteLine($"MeasureGrid inside {rectForChildrenPixels}");
var constraints = GetSizeInPoints(rectForChildrenPixels.Size, scale);
@@ -65,6 +66,7 @@ protected virtual int DrawChildrenGrid(SkiaDrawingContext context, SKRect destin
List tree = new();
+ var cellIndex = 0;
foreach (var child in cells)
{
child.OptionalOnBeforeDrawing(); //could set IsVisible or whatever inside
@@ -76,6 +78,8 @@ protected virtual int DrawChildrenGrid(SkiaDrawingContext context, SKRect destin
var cell = GridStructure.GetCellBoundsFor(child, (destination.Left / scale),
(destination.Top / scale));
+ //Trace.WriteLine($"cell {cellIndex++} rect {cell}");
+
//GetCellBoundsFor is in pixels
SKRect cellRect = new((float)Math.Round(cell.Left * scale), (float)Math.Round(cell.Top * scale),
(float)Math.Round(cell.Right * scale), (float)Math.Round(cell.Bottom * scale));
@@ -335,4 +339,4 @@ protected void UpdateRowColumnBindingContexts()
#endregion
-}
\ No newline at end of file
+}
diff --git a/src/Engine/Draw/Scroll/SkiaScroll.cs b/src/Engine/Draw/Scroll/SkiaScroll.cs
index 6752ce39..3d18fdf5 100644
--- a/src/Engine/Draw/Scroll/SkiaScroll.cs
+++ b/src/Engine/Draw/Scroll/SkiaScroll.cs
@@ -1,7 +1,7 @@
-using DrawnUi.Maui.Infrastructure.Helpers;
-using System.Numerics;
+using System.Numerics;
using System.Runtime.CompilerServices;
using System.Windows.Input;
+using DrawnUi.Maui.Infrastructure.Helpers;
namespace DrawnUi.Maui.Draw
@@ -471,14 +471,17 @@ public bool ScrollLocked
protected virtual Vector2 ClampOffsetWithRubberBand(float x, float y)
{
Vector2 clampedElastic = Vector2.Zero;
+ var add = Elastic * RenderingScale;
bool clamped = false;
if (RefreshEnabled)
{
+ add += RefreshDistanceLimit * RenderingScale * 10;
+
if (Orientation == ScrollOrientation.Vertical && y > 0) //pulling down
{
clamped = true;
- float adjusted = (float)(RefreshIndicator.Height * RenderingScale + 1000);
+ float adjusted = (float)(RefreshIndicator.Height * RenderingScale + add);
var customDims = new Vector2(ContentOffsetBounds.Width, adjusted);
clampedElastic = RubberBandUtils.ClampOnTrack(
new Vector2(x, y),
@@ -491,7 +494,7 @@ protected virtual Vector2 ClampOffsetWithRubberBand(float x, float y)
if (Orientation == ScrollOrientation.Horizontal && x > 0)//pulling right
{
clamped = true;
- float adjusted = (float)(RefreshIndicator.Width * RenderingScale + 1000);
+ float adjusted = (float)(RefreshIndicator.Width * RenderingScale + add);
var customDims = new Vector2(adjusted, ContentOffsetBounds.Height);
clampedElastic = RubberBandUtils.ClampOnTrack(
@@ -509,7 +512,8 @@ protected virtual Vector2 ClampOffsetWithRubberBand(float x, float y)
clampedElastic = RubberBandUtils.ClampOnTrack(
new Vector2(x, y),
ContentOffsetBounds,
- (float)RubberEffect
+ (float)RubberEffect,
+ new Vector2(add, add)
);
}
@@ -528,7 +532,7 @@ protected virtual Vector2 ClampOffsetWithRubberBand(float x, float y)
return clampedElastic;
}
-
+ public static int Elastic = 100;
public virtual Vector2 ClampOffset(float x, float y, bool strict = false)
{
@@ -802,242 +806,242 @@ ISkiaGestureListener PassToChildren()
{
case TouchActionResult.Tapped:
case TouchActionResult.LongPressing:
- if (!passedToChildren)
- {
- _panningStartOffsetPts = new(InternalViewportOffset.Units.X, InternalViewportOffset.Units.Y);
- consumed = PassToChildren();
- }
- break;
+ if (!passedToChildren)
+ {
+ _panningStartOffsetPts = new(InternalViewportOffset.Units.X, InternalViewportOffset.Units.Y);
+ consumed = PassToChildren();
+ }
+ break;
case TouchActionResult.Panning when RespondsToGestures:
- bool canPan = !ScrollLocked;
- if (Orientation == ScrollOrientation.Vertical)
- {
- canPan &= Math.Abs(VelocityY) > ScrollVelocityThreshold;
- }
- else if (Orientation == ScrollOrientation.Horizontal)
- {
- canPan &= Math.Abs(VelocityX) > ScrollVelocityThreshold;
- }
- else if (Orientation == ScrollOrientation.Both)
- {
- canPan &= Math.Abs(VelocityX) > ScrollVelocityThreshold || Math.Abs(VelocityY) > ScrollVelocityThreshold;
- }
-
- if (lockHeader && !CanScrollUsingHeader)
- {
- canPan = false;
- }
-
- if (canPan)
- {
- bool checkOverscroll = true;
-
- if (!IsUserFocused)
+ bool canPan = !ScrollLocked;
+ if (Orientation == ScrollOrientation.Vertical)
{
- ResetPan();
- checkOverscroll = false;
+ canPan &= Math.Abs(VelocityY) > ScrollVelocityThreshold;
+ }
+ else if (Orientation == ScrollOrientation.Horizontal)
+ {
+ canPan &= Math.Abs(VelocityX) > ScrollVelocityThreshold;
+ }
+ else if (Orientation == ScrollOrientation.Both)
+ {
+ canPan &= Math.Abs(VelocityX) > ScrollVelocityThreshold || Math.Abs(VelocityY) > ScrollVelocityThreshold;
}
- IsUserPanning = true;
+ if (lockHeader && !CanScrollUsingHeader)
+ {
+ canPan = false;
+ }
- var movedPtsY = (args.Event.Distance.Delta.Y / RenderingScale) * ChangeDIstancePanned;
- var movedPtsX = (args.Event.Distance.Delta.X / RenderingScale) * ChangeDIstancePanned;
+ if (canPan)
+ {
+ bool checkOverscroll = true;
- var interpolatedMoveToX = _panningLastDelta.X + (movedPtsX - _panningLastDelta.X) * 0.9f;
- var interpolatedMoveToY = _panningLastDelta.Y + (movedPtsY - _panningLastDelta.Y) * 0.9f;
+ if (!IsUserFocused)
+ {
+ ResetPan();
+ checkOverscroll = false;
+ }
- _panningLastDelta = new Vector2(interpolatedMoveToX, interpolatedMoveToY);
+ IsUserPanning = true;
- var moveTo = new Vector2(
- (float)Math.Round(_panningCurrentOffsetPts.X + interpolatedMoveToX),
- (float)Math.Round(_panningCurrentOffsetPts.Y + interpolatedMoveToY));
+ var movedPtsY = (args.Event.Distance.Delta.Y / RenderingScale) * ChangeDIstancePanned;
+ var movedPtsX = (args.Event.Distance.Delta.X / RenderingScale) * ChangeDIstancePanned;
- _panningCurrentOffsetPts = moveTo;
+ var interpolatedMoveToX = _panningLastDelta.X + (movedPtsX - _panningLastDelta.X) * 0.9f;
+ var interpolatedMoveToY = _panningLastDelta.Y + (movedPtsY - _panningLastDelta.Y) * 0.9f;
- if (IgnoreWrongDirection && wrongDirection)
- {
- IsUserPanning = false;
- IsUserFocused = false;
- return null;
- }
+ _panningLastDelta = new Vector2(interpolatedMoveToX, interpolatedMoveToY);
- VelocityAccumulator.CaptureVelocity(new(VelocityX, VelocityY));
+ var moveTo = new Vector2(
+ (float)Math.Round(_panningCurrentOffsetPts.X + interpolatedMoveToX),
+ (float)Math.Round(_panningCurrentOffsetPts.Y + interpolatedMoveToY));
- var clamped = ClampOffset(moveTo.X, moveTo.Y);
+ _panningCurrentOffsetPts = moveTo;
- if (!Bounces && checkOverscroll)
- {
- if (!AreEqual(clamped.X, moveTo.X, 1) && !AreEqual(clamped.Y, moveTo.Y, 1))
+ if (IgnoreWrongDirection && wrongDirection)
{
+ IsUserPanning = false;
+ IsUserFocused = false;
return null;
}
- }
- ViewportOffsetX = clamped.X;
- ViewportOffsetY = clamped.Y;
+ VelocityAccumulator.CaptureVelocity(new(VelocityX, VelocityY));
- IsUserPanning = true;
- _lastVelocity = new Vector2(VelocityX, VelocityY);
+ var clamped = ClampOffset(moveTo.X, moveTo.Y);
- consumed = this;
- }
- break;
+ if (!Bounces && checkOverscroll)
+ {
+ if (!AreEqual(clamped.X, moveTo.X, 1) && !AreEqual(clamped.Y, moveTo.Y, 1))
+ {
+ return null;
+ }
+ }
- case TouchActionResult.Up when RespondsToGestures:
- if ((!ChildWasTapped || OverScrolled) && (!ChildWasPanning || IsUserPanning))
- {
- if (apply.alreadyConsumed != null)
- {
- if (CheckNeedToSnap())
- Snap(SystemAnimationTimeSecs);
- return null;
- }
+ ViewportOffsetX = clamped.X;
+ ViewportOffsetY = clamped.Y;
- bool canSwipe = true;
- if (lockHeader && !CanScrollUsingHeader)
- {
- canSwipe = false;
+ IsUserPanning = true;
+ _lastVelocity = new Vector2(VelocityX, VelocityY);
+
+ consumed = this;
}
+ break;
- if (!ScrollLocked && canSwipe)
+ case TouchActionResult.Up when RespondsToGestures:
+ if ((!ChildWasTapped || OverScrolled) && (!ChildWasPanning || IsUserPanning))
{
- var finalVelocity = VelocityAccumulator.CalculateFinalVelocity(this.MaxVelocity);
+ if (apply.alreadyConsumed != null)
+ {
+ if (CheckNeedToSnap())
+ Snap(SystemAnimationTimeSecs);
+ return null;
+ }
- bool fling = false;
- bool swipe = false;
+ bool canSwipe = true;
+ if (lockHeader && !CanScrollUsingHeader)
+ {
+ canSwipe = false;
+ }
- if (!OverScrolled || Orientation == ScrollOrientation.Both)
+ if (!ScrollLocked && canSwipe)
{
- var mainDirection = GetDirectionType(new Vector2(finalVelocity.X, finalVelocity.Y), DirectionType.None, 0.9f);
+ var finalVelocity = VelocityAccumulator.CalculateFinalVelocity(this.MaxVelocity);
- if (Orientation != ScrollOrientation.Both && !IsUserPanning)
+ bool fling = false;
+ bool swipe = false;
+
+ if (!OverScrolled || Orientation == ScrollOrientation.Both)
{
- if (IgnoreWrongDirection)
+ var mainDirection = GetDirectionType(new Vector2(finalVelocity.X, finalVelocity.Y), DirectionType.None, 0.9f);
+
+ if (Orientation != ScrollOrientation.Both && !IsUserPanning)
{
- if (Orientation == ScrollOrientation.Vertical && mainDirection != DirectionType.Vertical)
- {
- return null;
- }
- if (Orientation == ScrollOrientation.Horizontal && mainDirection != DirectionType.Horizontal)
+ if (IgnoreWrongDirection)
{
- return null;
+ if (Orientation == ScrollOrientation.Vertical && mainDirection != DirectionType.Vertical)
+ {
+ return null;
+ }
+ if (Orientation == ScrollOrientation.Horizontal && mainDirection != DirectionType.Horizontal)
+ {
+ return null;
+ }
}
}
+
+ var swipeThreshold = ThesholdSwipeOnUp * RenderingScale;
+ if ((Orientation == ScrollOrientation.Both
+ || (Orientation == ScrollOrientation.Vertical && mainDirection == DirectionType.Vertical)
+ || (Orientation == ScrollOrientation.Horizontal && mainDirection == DirectionType.Horizontal))
+ && (Math.Abs(finalVelocity.X) > swipeThreshold || Math.Abs(finalVelocity.Y) > swipeThreshold))
+ {
+ swipe = true;
+ }
}
- var swipeThreshold = ThesholdSwipeOnUp * RenderingScale;
- if ((Orientation == ScrollOrientation.Both
- || (Orientation == ScrollOrientation.Vertical && mainDirection == DirectionType.Vertical)
- || (Orientation == ScrollOrientation.Horizontal && mainDirection == DirectionType.Horizontal))
- && (Math.Abs(finalVelocity.X) > swipeThreshold || Math.Abs(finalVelocity.Y) > swipeThreshold))
+ if (OverScrolled || swipe)
{
- swipe = true;
- }
- }
+ IsUserPanning = false;
- if (OverScrolled || swipe)
- {
- IsUserPanning = false;
+ bool bounceX = false, bounceY = false;
+ if (OverScrolled)
+ {
+ var contentRect = new SKRect(0, 0, ptsContentWidth, ptsContentHeight);
+ var closestPoint = GetClosestSidePoint(new SKPoint((float)InternalViewportOffset.Units.X, (float)InternalViewportOffset.Units.Y), contentRect, Viewport.Units.Size);
- bool bounceX = false, bounceY = false;
- if (OverScrolled)
- {
- var contentRect = new SKRect(0, 0, ptsContentWidth, ptsContentHeight);
- var closestPoint = GetClosestSidePoint(new SKPoint((float)InternalViewportOffset.Units.X, (float)InternalViewportOffset.Units.Y), contentRect, Viewport.Units.Size);
+ var axis = new Vector2(closestPoint.X, closestPoint.Y);
- var axis = new Vector2(closestPoint.X, closestPoint.Y);
+ var velocityY = finalVelocity.Y * ChangeVelocityScrolled;
+ var velocityX = finalVelocity.X * ChangeVelocityScrolled;
- var velocityY = finalVelocity.Y * ChangeVelocityScrolled;
- var velocityX = finalVelocity.X * ChangeVelocityScrolled;
+ if (Math.Abs(velocityX) > MaxBounceVelocity)
+ {
+ velocityX = Math.Sign(velocityX) * MaxBounceVelocity;
+ }
- if (Math.Abs(velocityX) > MaxBounceVelocity)
- {
- velocityX = Math.Sign(velocityX) * MaxBounceVelocity;
- }
+ if (Math.Abs(velocityY) > MaxBounceVelocity)
+ {
+ velocityY = Math.Sign(velocityY) * MaxBounceVelocity;
+ }
- if (Math.Abs(velocityY) > MaxBounceVelocity)
- {
- velocityY = Math.Sign(velocityY) * MaxBounceVelocity;
- }
+ if (OverscrollDistance.Y != 0)
+ {
+ bounceY = true;
+ BounceY(InternalViewportOffset.Units.Y,
+ axis.Y, velocityY);
+ }
- if (OverscrollDistance.Y != 0)
- {
- bounceY = true;
- BounceY(InternalViewportOffset.Units.Y,
- axis.Y, velocityY);
- }
+ if (OverscrollDistance.X != 0)
+ {
+ bounceX = true;
+ BounceX(InternalViewportOffset.Units.X,
+ axis.X, velocityX);
+ }
- if (OverscrollDistance.X != 0)
- {
- bounceX = true;
- BounceX(InternalViewportOffset.Units.X,
- axis.X, velocityX);
+ fling = true;
}
- fling = true;
- }
-
- if (Orientation != ScrollOrientation.Neither)
- {
- if (!Bounces)
+ if (Orientation != ScrollOrientation.Neither)
{
- if (Orientation == ScrollOrientation.Vertical && !bounceY)
+ if (!Bounces)
{
- if ((AreEqual(InternalViewportOffset.Pixels.Y, ContentOffsetBounds.Bottom, 1) && finalVelocity.Y > 0) ||
- (AreEqual(InternalViewportOffset.Pixels.Y, ContentOffsetBounds.Top, 1) && finalVelocity.Y < 0))
- return null;
+ if (Orientation == ScrollOrientation.Vertical && !bounceY)
+ {
+ if ((AreEqual(InternalViewportOffset.Pixels.Y, ContentOffsetBounds.Bottom, 1) && finalVelocity.Y > 0) ||
+ (AreEqual(InternalViewportOffset.Pixels.Y, ContentOffsetBounds.Top, 1) && finalVelocity.Y < 0))
+ return null;
+ }
+ if (Orientation == ScrollOrientation.Horizontal && !bounceX)
+ {
+ if ((AreEqual(InternalViewportOffset.Pixels.X, ContentOffsetBounds.Right, 1) && finalVelocity.X > 0) ||
+ (AreEqual(InternalViewportOffset.Pixels.X, ContentOffsetBounds.Left, 1) && finalVelocity.X < 0))
+ return null;
+ }
}
- if (Orientation == ScrollOrientation.Horizontal && !bounceX)
+
+ var velocityY = finalVelocity.Y * ChangeVelocityScrolled;
+ var velocityX = finalVelocity.X * ChangeVelocityScrolled;
+
+ if (Math.Abs(velocityX) > _minVelocity && !bounceX)
{
- if ((AreEqual(InternalViewportOffset.Pixels.X, ContentOffsetBounds.Right, 1) && finalVelocity.X > 0) ||
- (AreEqual(InternalViewportOffset.Pixels.X, ContentOffsetBounds.Left, 1) && finalVelocity.X < 0))
- return null;
+ IsUserFocused = false;
+ _vectorAnimatorBounceX.Stop();
+ fling = StartToFlingFrom(_animatorFlingX, ViewportOffsetX, velocityX);
}
- }
- var velocityY = finalVelocity.Y * ChangeVelocityScrolled;
- var velocityX = finalVelocity.X * ChangeVelocityScrolled;
-
- if (Math.Abs(velocityX) > _minVelocity && !bounceX)
- {
- IsUserFocused = false;
- _vectorAnimatorBounceX.Stop();
- fling = StartToFlingFrom(_animatorFlingX, ViewportOffsetX, velocityX);
+ if (Math.Abs(velocityY) > _minVelocity && !bounceY)
+ {
+ IsUserFocused = false;
+ _vectorAnimatorBounceY.Stop();
+ fling = StartToFlingFrom(_animatorFlingY, ViewportOffsetY, velocityY);
+ }
}
- if (Math.Abs(velocityY) > _minVelocity && !bounceY)
+ if (fling)
{
- IsUserFocused = false;
- _vectorAnimatorBounceY.Stop();
- fling = StartToFlingFrom(_animatorFlingY, ViewportOffsetY, velocityY);
+ WasSwiping = true;
+ consumed = this;
+ passedToChildren = true;
}
}
- if (fling)
- {
- WasSwiping = true;
- consumed = this;
- passedToChildren = true;
- }
- }
-
- IsUserFocused = false;
- IsUserPanning = false;
+ IsUserFocused = false;
+ IsUserPanning = false;
- if (!fling)
- {
- if (CheckNeedToSnap())
- Snap(SystemAnimationTimeSecs);
- else
+ if (!fling)
{
- _destination = SKRect.Empty;
+ if (CheckNeedToSnap())
+ Snap(SystemAnimationTimeSecs);
+ else
+ {
+ _destination = SKRect.Empty;
+ }
}
}
+ break;
}
break;
- }
- break;
}
}
@@ -1127,14 +1131,14 @@ void UpdateLoadingLock(Vector2 velocity)
switch (Orientation)
{
case ScrollOrientation.Vertical:
- shouldLock = Math.Abs(velocity.Y) >= VelocityImageLoaderLock;
- break;
+ shouldLock = Math.Abs(velocity.Y) >= VelocityImageLoaderLock;
+ break;
case ScrollOrientation.Horizontal:
- shouldLock = Math.Abs(velocity.X) >= VelocityImageLoaderLock;
- break;
+ shouldLock = Math.Abs(velocity.X) >= VelocityImageLoaderLock;
+ break;
default:
- shouldLock = Math.Abs(velocity.Y) >= VelocityImageLoaderLock || Math.Abs(velocity.X) >= VelocityImageLoaderLock;
- break;
+ shouldLock = Math.Abs(velocity.Y) >= VelocityImageLoaderLock || Math.Abs(velocity.X) >= VelocityImageLoaderLock;
+ break;
}
UpdateLoadingLock(shouldLock);
diff --git a/src/Engine/Draw/SkiaPoint.cs b/src/Engine/Draw/SkiaPoint.cs
new file mode 100644
index 00000000..314c5200
--- /dev/null
+++ b/src/Engine/Draw/SkiaPoint.cs
@@ -0,0 +1,52 @@
+namespace DrawnUi.Maui.Draw
+{
+ public class SkiaPoint : BindableObject
+ {
+ public SkiaPoint()
+ {
+
+ }
+
+ public SkiaPoint(double x, double y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ public static readonly BindableProperty XProperty = BindableProperty.Create(
+ nameof(X),
+ typeof(double),
+ typeof(SkiaPoint),
+ 0.0,
+ propertyChanged: OnPointPropertyChanged);
+
+ public double X
+ {
+ get => (double)GetValue(XProperty);
+ set => SetValue(XProperty, value);
+ }
+
+ public static readonly BindableProperty YProperty = BindableProperty.Create(
+ nameof(Y),
+ typeof(double),
+ typeof(SkiaPoint),
+ 0.0,
+ propertyChanged: OnPointPropertyChanged);
+
+ public double Y
+ {
+ get => (double)GetValue(YProperty);
+ set => SetValue(YProperty, value);
+ }
+
+ private static void OnPointPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SkiaPoint point)
+ {
+ point.ParentShape?.Update();
+ }
+ }
+
+ internal SkiaShape ParentShape { get; set; }
+ }
+}
diff --git a/src/Engine/Draw/SkiaShape.cs b/src/Engine/Draw/SkiaShape.cs
index c07edbf2..4b601489 100644
--- a/src/Engine/Draw/SkiaShape.cs
+++ b/src/Engine/Draw/SkiaShape.cs
@@ -1,5 +1,7 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
+using System.ComponentModel;
+using DrawnUi.Maui.Infrastructure.Xaml;
namespace DrawnUi.Maui.Draw
@@ -138,6 +140,9 @@ public double StrokeWidth
SKStrokeCap.Round,
propertyChanged: NeedDraw);
+ ///
+ /// Affects joint of shapes. Default is KStrokeCap.Round
+ ///
public SKStrokeCap StrokeCap
{
get { return (SKStrokeCap)GetValue(StrokeCapProperty); }
@@ -145,8 +150,6 @@ public SKStrokeCap StrokeCap
}
-
-
public static readonly BindableProperty LayoutChildrenProperty = BindableProperty.Create(
nameof(LayoutChildren),
typeof(LayoutType),
@@ -166,6 +169,9 @@ public LayoutType LayoutChildren
SKBlendMode.SrcOver,
propertyChanged: NeedDraw);
+ ///
+ /// Default is SKBlendMode.SrcOver
+ ///
public SKBlendMode StrokeBlendMode
{
get { return (SKBlendMode)GetValue(StrokeBlendModeProperty); }
@@ -265,8 +271,7 @@ protected void CalculateSizeForStroke(SKRect destination, float scale)
strokeAwareSize =
SKRect.Inflate(strokeAwareSize, -halfStroke, -halfStroke);
- strokeAwareChildrenSize =
- SKRect.Inflate(strokeAwareSize, -halfStroke, -halfStroke);
+ strokeAwareChildrenSize = strokeAwareSize;
}
MeasuredStrokeAwareSize = strokeAwareSize;
@@ -387,10 +392,10 @@ public override SKPath CreateClip(object arguments, bool usePosition, SKPath pat
case ShapeType.Circle:
ShouldClipAntialiased = true;
path.AddCircle(
- (float)Math.Round(strokeAwareChildrenSize.Left + strokeAwareChildrenSize.Width / 2.0f),
- (float)Math.Round(strokeAwareChildrenSize.Top + strokeAwareChildrenSize.Height / 2.0f),
- Math.Min(strokeAwareChildrenSize.Width, strokeAwareChildrenSize.Height) /
- 2.0f);
+ (float)(strokeAwareChildrenSize.Left + strokeAwareChildrenSize.Width / 2.0f),
+ (float)(strokeAwareChildrenSize.Top + strokeAwareChildrenSize.Height / 2.0f),
+ (float)Math.Floor(Math.Min(strokeAwareChildrenSize.Width, strokeAwareChildrenSize.Height) /
+ 2.0f) + 0);
break;
case ShapeType.Ellipse:
@@ -399,6 +404,37 @@ public override SKPath CreateClip(object arguments, bool usePosition, SKPath pat
path.AddOval(strokeAwareChildrenSize);
break;
+ case ShapeType.Polygon:
+ if (Points != null && Points.Count > 0)
+ {
+ path.Reset();
+
+ var scaleX = MeasuredStrokeAwareSize.Width;
+ var scaleY = MeasuredStrokeAwareSize.Height;
+ var offsetX = usePosition ? MeasuredStrokeAwareSize.Left : 0;
+ var offsetY = usePosition ? MeasuredStrokeAwareSize.Top : 0;
+
+ bool first = true;
+ foreach (var skiaPoint in Points)
+ {
+ var point = new SKPoint(
+ (float)Math.Round(offsetX + skiaPoint.X * scaleX),
+ (float)Math.Round(offsetY + skiaPoint.Y * scaleY));
+
+ if (first)
+ {
+ path.MoveTo(point);
+ first = false;
+ }
+ else
+ {
+ path.LineTo(point);
+ }
+ }
+ path.Close();
+ }
+ break;
+
case ShapeType.Rectangle:
default:
if (CornerRadius != default)
@@ -476,6 +512,27 @@ protected virtual void PaintBackground(SkiaDrawingContext ctx,
break;
+ case ShapeType.Polygon:
+
+ if (Points != null && Points.Count > 1)
+ {
+ DrawPathShape.Reset();
+
+ paint.StrokeJoin = MapStrokeCapToStrokeJoin(this.StrokeCap);
+
+ if (SmoothPoints > 0)
+ {
+ AddSmoothPath(DrawPathShape, Points, MeasuredStrokeAwareSize, SmoothPoints, true);
+ }
+ else
+ {
+ AddStraightPath(DrawPathShape, Points, MeasuredStrokeAwareSize, true);
+ }
+
+ ctx.Canvas.DrawPath(DrawPathShape, paint);
+ }
+ break;
+
case ShapeType.Circle:
if (StrokeWidth == 0 || StrokeColor == TransparentColor)
{
@@ -512,6 +569,24 @@ protected virtual void PaintBackground(SkiaDrawingContext ctx,
}
}
+ protected virtual SKStrokeJoin MapStrokeCapToStrokeJoin(SKStrokeCap strokeCap)
+ {
+ switch (strokeCap)
+ {
+ case SKStrokeCap.Round:
+ return SKStrokeJoin.Round;
+ case SKStrokeCap.Square:
+ return SKStrokeJoin.Bevel;
+ case SKStrokeCap.Butt:
+ default:
+ return SKStrokeJoin.Miter;
+ }
+ }
+
+ protected override void PaintWithShadows(SkiaDrawingContext ctx, Action render)
+ {
+ render(); //we will handle shadows by ourselves
+ }
protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float scale, object arguments)
{
@@ -533,7 +608,7 @@ protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float
RenderingPaint ??= new SKPaint()
{
- IsAntialias = true
+ IsAntialias = true,
};
RenderingPaint.IsDither = IsDistorted;
@@ -590,7 +665,8 @@ void PaintStroke(SKPaint paint)
}
paint.Style = SKPaintStyle.Stroke;
- paint.StrokeCap = this.StrokeCap; //todo full stroke object
+ paint.StrokeCap = this.StrokeCap;
+ paint.StrokeJoin = MapStrokeCapToStrokeJoin(this.StrokeCap);
if (this.StrokePath != null && StrokePath.Length > 0)
{
@@ -625,6 +701,22 @@ void PaintStroke(SKPaint paint)
ctx.Canvas.DrawCircle(outRect.MidX, outRect.MidY, minSize / 2.0f, paint);
break;
+ case ShapeType.Line:
+ if (Points != null && Points.Count > 1)
+ {
+ DrawPathShape.Reset();
+ if (SmoothPoints > 0)
+ {
+ AddSmoothPath(DrawPathShape, Points, strokeAwareSize, SmoothPoints, false);
+ }
+ else
+ {
+ AddStraightPath(DrawPathShape, Points, strokeAwareSize, false);
+ }
+ ctx.Canvas.DrawPath(DrawPathShape, paint);
+ }
+ break;
+
case ShapeType.Ellipse:
DrawPathShape.Reset();
DrawPathShape.AddOval(outRect);
@@ -646,35 +738,66 @@ void PaintStroke(SKPaint paint)
ctx.Canvas.DrawPath(DrawPathShape, paint);
break;
+
+ case ShapeType.Polygon:
+ if (Points != null && Points.Count > 1)
+ {
+ DrawPathShape.Reset();
+
+ var path = DrawPathShape;
+
+ if (SmoothPoints > 0)
+ {
+ AddSmoothPath(path, Points, strokeAwareSize, SmoothPoints, true);
+ }
+ else
+ {
+ AddStraightPath(path, Points, strokeAwareSize, true);
+ }
+
+ ctx.Canvas.DrawPath(path, RenderingPaint);
+ }
+ break;
}
}
- void PaintWithShadows(Action render)
+ void PaintWithShadowsInternal(Action render)
{
- if (Shadows != null && Shadows.Count > 0)
+
+ void RenderShadow(SkiaShadow shadow)
{
- for (int index = 0; index < Shadows.Count(); index++)
+ SetupShadow(RenderingPaint, shadow, RenderingScale);
+
+ if (ClipBackgroundColor)
{
- SetupShadow(RenderingPaint, Shadows[index], RenderingScale);
+ ClipContentPath ??= new();
+ ClipContentPath.Reset();
+ CreateClip(arguments, true, ClipContentPath);
- if (ClipBackgroundColor)
- {
- ClipContentPath ??= new();
- ClipContentPath.Reset();
- CreateClip(arguments, true, ClipContentPath);
+ var saved = ctx.Canvas.Save();
- var saved = ctx.Canvas.Save();
+ ClipSmart(ctx.Canvas, ClipContentPath, SKClipOperation.Difference);
+ render();
- ClipSmart(ctx.Canvas, ClipContentPath, SKClipOperation.Difference);
- render();
+ ctx.Canvas.RestoreToCount(saved);
+ }
+ else
+ {
+ render();
+ }
+ }
- ctx.Canvas.RestoreToCount(saved);
- }
- else
- {
- render();
- }
+ if (PlatformShadow != null)
+ {
+ RenderShadow(PlatformShadow);
+ }
+ else
+ if (Shadows != null && Shadows.Count > 0)
+ {
+ for (int index = 0; index < Shadows.Count(); index++)
+ {
+ RenderShadow(Shadows[index]);
}
}
else
@@ -684,7 +807,7 @@ void PaintWithShadows(Action render)
}
//background with shadows pass, no stroke
- PaintWithShadows(() =>
+ PaintWithShadowsInternal(() =>
{
if (SetupBackgroundPaint(RenderingPaint, outRect))
{
@@ -716,8 +839,6 @@ void PaintWithShadows(Action render)
}
-
-
#endregion
#region SHADOWS
@@ -792,7 +913,7 @@ private void OnShadowCollectionChanged(object sender, NotifyCollectionChangedEve
typeof(SkiaShape),
defaultValueCreator: (instance) =>
{
- var created = new ObservableCollection();
+ var created = new SkiaShadowsCollection();
ShadowsPropertyChanged(instance, null, created);
return created;
},
@@ -822,6 +943,371 @@ private static object CoerceShadows(BindableObject bindable, object value)
#endregion
+ #region POINTS
+
+ private static object CoercePoints(BindableObject bindable, object value)
+ {
+ if (value is ReadOnlyCollection readonlyCollection)
+ {
+ return new ReadOnlyCollection(readonlyCollection.ToList());
+ }
+
+ return value;
+ }
+
+
+ public static readonly BindableProperty PointsProperty = BindableProperty.Create(
+ nameof(Points),
+ typeof(IList),
+ typeof(SkiaShape),
+ defaultValueCreator: (instance) =>
+ {
+ var created = new ObservableCollection();
+ PointsPropertyChanged(instance, null, created);
+ return created;
+ },
+ validateValue: (bo, v) => v is IList,
+ propertyChanged: NeedDraw,
+ coerceValue: CoercePoints);
+
+ [TypeConverter(typeof(SkiaPointCollectionConverter))]
+ public IList Points
+ {
+ get => (IList)GetValue(PointsProperty);
+ set => SetValue(PointsProperty, value);
+ }
+
+ public static List PolygonStar
+ {
+ get
+ {
+ return CreateStarPoints(5);
+ }
+ }
+
+ private static void PointsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SkiaShape control)
+ {
+ if (oldValue is INotifyCollectionChanged oldCollection)
+ {
+ oldCollection.CollectionChanged -= control.OnPointsCollectionChanged;
+ }
+
+ if (oldValue is IEnumerable oldPoints)
+ {
+ foreach (var point in oldPoints)
+ {
+ point.ParentShape = null;
+ }
+ }
+
+ if (newValue is IEnumerable newPoints)
+ {
+ foreach (var point in newPoints)
+ {
+ point.ParentShape = control;
+ }
+ }
+
+ if (newValue is INotifyCollectionChanged newCollection)
+ {
+ newCollection.CollectionChanged -= control.OnPointsCollectionChanged;
+ newCollection.CollectionChanged += control.OnPointsCollectionChanged;
+ }
+
+ control.Update();
+ }
+ }
+
+ private void OnPointsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.OldItems != null)
+ {
+ foreach (SkiaPoint oldPoint in e.OldItems)
+ {
+ oldPoint.ParentShape = null;
+ }
+ }
+
+ if (e.NewItems != null)
+ {
+ foreach (SkiaPoint newPoint in e.NewItems)
+ {
+ newPoint.ParentShape = this;
+ }
+ }
+
+ Update();
+ }
+
+
+
+ #endregion
+
+ #region SMOOTH
+
+ public static readonly BindableProperty SmoothPointsProperty = BindableProperty.Create(
+ nameof(SmoothPoints),
+ typeof(float),
+ typeof(SkiaShape),
+ 0f, // Default value is 0 (no smoothing)
+ propertyChanged: NeedDraw,
+ coerceValue: (bindable, value) =>
+ {
+ float val = (float)value;
+ return Math.Max(0f, Math.Min(1f, val)); // Clamp between 0 and 1
+ }
+ );
+
+ ///
+ /// Controls the automatic smoothness between points of Line and Polygon. Ranges from 0.0 (no smoothing) to 1.0.
+ /// Works for sequential points only.
+ ///
+ public float SmoothPoints
+ {
+ get => (float)GetValue(SmoothPointsProperty);
+ set => SetValue(SmoothPointsProperty, value);
+ }
+
+ private void AddSmoothPath(SKPath path, IList points, SKRect rect, float smoothness, bool isClosed)
+ {
+ if (points == null || points.Count < 2)
+ {
+ return;
+ }
+
+ var scaledPoints = points.Select(p => ScalePoint(p, rect)).ToList();
+
+ if (isClosed)
+ {
+ var firstPoint = scaledPoints[0];
+ var closingPoint = new SKPoint(
+ (firstPoint.X + scaledPoints[1].X) / 2,
+ (firstPoint.Y + scaledPoints[1].Y) / 2);
+
+ scaledPoints.RemoveAt(0);
+ scaledPoints.Insert(0, closingPoint);
+ scaledPoints.Add(firstPoint);
+ }
+
+ path.MoveTo(scaledPoints[0]);
+
+ int pointCount = scaledPoints.Count;
+
+ if (isClosed)
+ {
+ for (int i = 0; i < pointCount; i++)
+ {
+ SmoothPoint(path, scaledPoints, i, smoothness, isClosed);
+ }
+ path.Close();
+ }
+ else
+ {
+ for (int i = 0; i < pointCount; i++)
+ {
+ if (i == 0 || i == pointCount - 1)
+ {
+ path.LineTo(scaledPoints[i]);
+ }
+ else
+ {
+ SmoothPoint(path, scaledPoints, i, smoothness, isClosed);
+ }
+ }
+ }
+ }
+
+ private void SmoothPoint(SKPath path, IList scaledPoints, int i, float smoothness, bool isClosed)
+ {
+ int pointCount = scaledPoints.Count;
+
+ var prev = scaledPoints[(i - 1 + pointCount) % pointCount];
+ var current = scaledPoints[i];
+ var next = scaledPoints[(i + 1) % pointCount];
+
+ if (!isClosed)
+ {
+ if (i == 0)
+ {
+ path.LineTo(current);
+ return;
+ }
+ if (i == pointCount - 1)
+ {
+ path.LineTo(current);
+ return;
+ }
+ }
+
+ var v1 = new SKPoint(current.X - prev.X, current.Y - prev.Y);
+ var v2 = new SKPoint(next.X - current.X, next.Y - current.Y);
+
+ float lengthV1 = (float)Math.Sqrt(v1.X * v1.X + v1.Y * v1.Y);
+ float lengthV2 = (float)Math.Sqrt(v2.X * v2.X + v2.Y * v2.Y);
+
+ if (lengthV1 == 0 || lengthV2 == 0)
+ {
+ path.LineTo(current);
+ return;
+ }
+
+ v1 = new SKPoint(v1.X / lengthV1, v1.Y / lengthV1);
+ v2 = new SKPoint(v2.X / lengthV2, v2.Y / lengthV2);
+
+ float smoothingRadius = lengthV1 * smoothness * 0.3f;
+ smoothingRadius = Math.Min(smoothingRadius, lengthV1 * 0.5f);
+ smoothingRadius = Math.Min(smoothingRadius, lengthV2 * 0.5f);
+
+ if (smoothingRadius < 0.001f)
+ {
+ path.LineTo(current); // No significant smoothing is needed
+ return;
+ }
+
+ var p1 = new SKPoint(
+ current.X - v1.X * smoothingRadius,
+ current.Y - v1.Y * smoothingRadius);
+
+ var p2 = new SKPoint(
+ current.X + v2.X * smoothingRadius,
+ current.Y + v2.Y * smoothingRadius);
+
+ path.LineTo(p1);
+
+ // quadratic Bezier curve to smooth angle
+ path.QuadTo(current, p2);
+ }
+
+ private void AddStraightPath(SKPath path, IList points, SKRect rect, bool isClosed)
+ {
+ if (points == null || points.Count < 2)
+ {
+ return;
+ }
+
+ path.MoveTo(ScalePoint(points[0], rect));
+
+ for (int i = 1; i < points.Count; i++)
+ {
+ path.LineTo(ScalePoint(points[i], rect));
+ }
+
+ if (isClosed)
+ {
+ path.Close();
+ }
+ }
+
+ private SKPoint ScalePoint(SkiaPoint point, SKRect rect)
+ {
+ return new SKPoint(
+ (float)Math.Round(rect.Left + point.X * rect.Width),
+ (float)Math.Round(rect.Top + point.Y * rect.Height));
+ }
+
+ #endregion
+
+ public static List CreateStarPoints(int numberOfPoints, double innerRadiusRatio = 0.5)
+ {
+ if (numberOfPoints < 2)
+ throw new ArgumentException("Number of points must be at least 2.", nameof(numberOfPoints));
+ if (innerRadiusRatio <= 0 || innerRadiusRatio >= 1)
+ throw new ArgumentException("Inner radius ratio must be between 0 and 1.", nameof(innerRadiusRatio));
+
+ List points = new List();
+ double angleStep = Math.PI / numberOfPoints;
+ double outerRadius = 1.0;
+ double innerRadius = outerRadius * innerRadiusRatio;
+
+ for (int i = 0; i < numberOfPoints * 2; i++)
+ {
+ double angle = i * angleStep - Math.PI / 2;
+
+ double radius = (i % 2 == 0) ? outerRadius : innerRadius;
+ double x = radius * Math.Cos(angle);
+ double y = radius * Math.Sin(angle);
+
+ points.Add(new SkiaPoint((float)x, (float)y));
+ }
+
+ // Find bounding box
+ var minX = points.Min(p => p.X);
+ var maxX = points.Max(p => p.X);
+ var minY = points.Min(p => p.Y);
+ var maxY = points.Max(p => p.Y);
+
+ var scaleX = 1.0f / (maxX - minX);
+ var scaleY = 1.0f / (maxY - minY);
+
+ var scale = Math.Min(scaleX, scaleY);
+
+ var offsetX = (1.0f - (maxX - minX) * scale) / 2.0f - minX * scale;
+ var offsetY = (1.0f - (maxY - minY) * scale) / 2.0f - minY * scale;
+
+ for (int i = 0; i < points.Count; i++)
+ {
+ var x = points[i].X * scale + offsetX;
+ var y = points[i].Y * scale + offsetY;
+
+ points[i] = new SkiaPoint(x, y);
+ }
+
+ return points;
+ }
+
+ public static List CreateStarPointsCrossed(int numberOfPoints)
+ {
+ if (numberOfPoints < 5 || numberOfPoints % 2 == 0)
+ throw new ArgumentException("Number of points must be an odd number greater than or equal to 5.", nameof(numberOfPoints));
+
+ List points = new List();
+ double angleStep = 2 * Math.PI / numberOfPoints;
+ double radius = 1.0;
+
+ List outerPoints = new List();
+ for (int i = 0; i < numberOfPoints; i++)
+ {
+ double angle = i * angleStep - Math.PI / 2;
+ double x = radius * Math.Cos(angle);
+ double y = radius * Math.Sin(angle);
+ outerPoints.Add(new SkiaPoint((float)x, (float)y));
+ }
+
+ int skip = (numberOfPoints - 1) / 2;
+ for (int i = 0; i < numberOfPoints; i++)
+ {
+ points.Add(outerPoints[(i * skip) % numberOfPoints]);
+ }
+
+ points.Add(points[0]);
+
+ // Find bounding box
+ var minX = points.Min(p => p.X);
+ var maxX = points.Max(p => p.X);
+ var minY = points.Min(p => p.Y);
+ var maxY = points.Max(p => p.Y);
+
+ var scaleX = 1.0f / (maxX - minX);
+ var scaleY = 1.0f / (maxY - minY);
+
+ var scale = Math.Min(scaleX, scaleY);
+
+ var offsetX = (1.0f - (maxX - minX) * scale) / 2.0f - minX * scale;
+ var offsetY = (1.0f - (maxY - minY) * scale) / 2.0f - minY * scale;
+
+ for (int i = 0; i < points.Count; i++)
+ {
+ var x = points[i].X * scale + offsetX;
+ var y = points[i].Y * scale + offsetY;
+
+ points[i] = new SkiaPoint(x, y);
+ }
+
+ return points;
+ }
+
}
}
diff --git a/src/Engine/Draw/Text/SkiaLabel.cs b/src/Engine/Draw/Text/SkiaLabel.cs
index 2ad1dee5..16642254 100644
--- a/src/Engine/Draw/Text/SkiaLabel.cs
+++ b/src/Engine/Draw/Text/SkiaLabel.cs
@@ -313,9 +313,6 @@ public void MergeSpansForLines(
public List Glyphs { get; protected set; } = new();
- //todo
- bool AutoFindFont = false;
-
public override ScaledSize Measure(float widthConstraint, float heightConstraint, float scale)
{
if (IsDisposed || IsDisposing)
@@ -369,20 +366,25 @@ public override ScaledSize Measure(float widthConstraint, float heightConstraint
Glyphs = GetGlyphs(Text, PaintDefault.Typeface);
- if (AutoFindFont)
+ if (AutoFont)
{
if (Glyphs != null && Glyphs.Count > 0)
{
- if (UnicodeNeedsShaping(Glyphs[0].Symbol))
+ var first = Glyphs[0].Symbol;
+ var typeFace = SkiaFontManager.Manager.MatchCharacter(first);
+ if (typeFace != null)
{
- needsShaping = true;
+ //FontDetectedWith = glyph.Symbol;
+ needsShaping = SkiaLabel.UnicodeNeedsShaping(first);
+ _replaceFont = typeFace;
+ ReplaceFont();
}
}
text = Text;
}
else
{
- //replace unprintable symbols
+ //replace unprintable symbols with fallback
if (Glyphs.Count > 0)
{
var textFiltered = "";
@@ -1438,6 +1440,8 @@ protected virtual void UpdateFont()
}
+
+
protected void ReplaceFont()
{
var newFont = _replaceFont;
@@ -2676,6 +2680,21 @@ public int FontWeight
set { SetValue(FontWeightProperty, value); }
}
+ public static readonly BindableProperty AutoFontProperty = BindableProperty.Create(
+ nameof(AutoFont),
+ typeof(bool),
+ typeof(SkiaLabel),
+ false, propertyChanged: NeedUpdateFont);
+
+ ///
+ /// Find and set system font where the first glyph in text is present
+ ///
+ public bool AutoFont
+ {
+ get { return (bool)GetValue(AutoFontProperty); }
+ set { SetValue(AutoFontProperty, value); }
+ }
+
public static readonly BindableProperty TypeFaceProperty = BindableProperty.Create(
nameof(TypeFace),
typeof(SKTypeface),
diff --git a/src/Engine/DrawnUi.Maui.csproj b/src/Engine/DrawnUi.Maui.csproj
index 304e08e8..c503e4ba 100644
--- a/src/Engine/DrawnUi.Maui.csproj
+++ b/src/Engine/DrawnUi.Maui.csproj
@@ -9,7 +9,6 @@
true
-
14.2
14.0
21.0
@@ -62,6 +61,7 @@
+
@@ -92,11 +92,12 @@
+
-
+
@@ -104,38 +105,39 @@
-
-
+
-
+
+
+
+
+
+
+
-
-
+
@@ -163,4 +165,6 @@
+
+
diff --git a/src/Engine/Features/Effects/ChainDropShadowsEffect.cs b/src/Engine/Features/Effects/ChainDropShadowsEffect.cs
index 8a707835..c49e84f4 100644
--- a/src/Engine/Features/Effects/ChainDropShadowsEffect.cs
+++ b/src/Engine/Features/Effects/ChainDropShadowsEffect.cs
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
+using DrawnUi.Maui.Infrastructure.Xaml;
namespace DrawnUi.Maui.Draw;
@@ -14,7 +15,7 @@ public class ChainDropShadowsEffect : BaseChainedEffect
typeof(ChainDropShadowsEffect),
defaultValueCreator: (instance) =>
{
- var created = new ObservableCollection();
+ var created = new SkiaShadowsCollection();
ShadowsPropertyChanged(instance, null, created);
return created;
},
@@ -79,22 +80,22 @@ private void OnShadowCollectionChanged(object sender, NotifyCollectionChangedEve
{
switch (e.Action)
{
- case NotifyCollectionChangedAction.Add:
- foreach (SkiaShadow newSkiaPropertyShadow in e.NewItems)
- {
- newSkiaPropertyShadow.Attach(this);
- }
+ case NotifyCollectionChangedAction.Add:
+ foreach (SkiaShadow newSkiaPropertyShadow in e.NewItems)
+ {
+ newSkiaPropertyShadow.Attach(this);
+ }
- break;
+ break;
- case NotifyCollectionChangedAction.Reset:
- case NotifyCollectionChangedAction.Remove:
- foreach (SkiaShadow oldSkiaPropertyShadow in e.OldItems ?? new SkiaShadow[0])
- {
- oldSkiaPropertyShadow.Dettach();
- }
+ case NotifyCollectionChangedAction.Reset:
+ case NotifyCollectionChangedAction.Remove:
+ foreach (SkiaShadow oldSkiaPropertyShadow in e.OldItems ?? new SkiaShadow[0])
+ {
+ oldSkiaPropertyShadow.Dettach();
+ }
- break;
+ break;
}
Update();
@@ -154,4 +155,4 @@ public override bool NeedApply
return base.NeedApply && Shadows.Count > 0;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Engine/Features/Gestures/GesturesMode.cs b/src/Engine/Features/Gestures/GesturesMode.cs
index bda7b733..9ec77da0 100644
--- a/src/Engine/Features/Gestures/GesturesMode.cs
+++ b/src/Engine/Features/Gestures/GesturesMode.cs
@@ -19,9 +19,4 @@ public enum GesturesMode
/// Lock input for self, useful inside scroll view, panning controls like slider etc
///
Lock,
-
- ///
- /// Tries to let other views consume the touch event if this view doesn't handle it
- ///
- Share,
-}
\ No newline at end of file
+}
diff --git a/src/Engine/Internals/Enums/ShapeType.cs b/src/Engine/Internals/Enums/ShapeType.cs
index af07f339..78aeb64d 100644
--- a/src/Engine/Internals/Enums/ShapeType.cs
+++ b/src/Engine/Internals/Enums/ShapeType.cs
@@ -2,15 +2,41 @@
{
public enum ShapeType
{
+ ///
+ /// Default type for SkiaShape
+ ///
Rectangle,
+
Circle,
+
Ellipse,
+
Arc,
+
///
- /// TODO
+ /// TODO unimplemented yet
///
Squricle,
+ ///
+ ///
+ ///
+ ///
Path,
+
+ ///
+ /// Uses multiple Points
+ ///
+ ///
+ Polygon,
+
+ ///
+ /// Uses multiple Points
+ ///
+ Line,
+
+ ///
+ /// unimplemented
+ ///
Custom
}
-}
\ No newline at end of file
+}
diff --git a/src/Engine/Internals/Extensions/InternalExtensions.Maui.cs b/src/Engine/Internals/Extensions/InternalExtensions.Maui.cs
new file mode 100644
index 00000000..70902160
--- /dev/null
+++ b/src/Engine/Internals/Extensions/InternalExtensions.Maui.cs
@@ -0,0 +1,183 @@
+using Microsoft.Maui.Controls.Shapes;
+
+namespace DrawnUi.Maui.Extensions;
+
+public static partial class InternalExtensions
+{
+
+ #region MAUI CONTEXT
+
+ public static IMauiContext? FindMauiContext(this Element element, bool fallbackToAppMauiContext = false)
+ {
+ if (element is IElement fe && fe.Handler?.MauiContext != null)
+ return fe.Handler.MauiContext;
+
+ foreach (var parent in element.GetParentsPath())
+ {
+ if (parent is IElement parentView && parentView.Handler?.MauiContext != null)
+ return parentView.Handler.MauiContext;
+ }
+
+ return fallbackToAppMauiContext ? Application.Current?.FindMauiContext() : default;
+ }
+
+ public static IEnumerable GetParentsPath(this Element self)
+ {
+ Element current = self;
+
+ while (!IsAppOrNull(current.RealParent))
+ {
+ current = current.RealParent;
+ yield return current;
+ }
+ }
+
+ internal static bool IsAppOrNull(object? element) =>
+ element == null || element is IApplication;
+
+ #endregion
+
+ public static SkiaShadow FromPlatform(this object platform)
+ {
+ if (platform is Shadow shadow)
+ {
+ return new SkiaShadow
+ {
+ Blur = shadow.Radius,
+ Opacity = shadow.Opacity,
+ X = shadow.Offset.X,
+ Y = shadow.Offset.Y,
+ Color = ((SolidColorBrush)shadow.Brush).Color,
+ BindingContext = shadow.BindingContext
+ };
+ }
+ return null;
+ }
+
+ public static void FromPlatform(this Geometry geometry, SKPath path, SKRect destination, float scale)
+ {
+ FromPlatform(geometry, path, scale);
+ path.Offset(destination.Location);
+ }
+
+ public static SKPath FromPlatform(this Geometry geometry, SKPath path, float scale)
+ {
+ if (geometry == null)
+ throw new ArgumentNullException(nameof(geometry));
+
+ path ??= new SKPath();
+
+ if (geometry is EllipseGeometry ellipseGeometry)
+ {
+ return ConvertEllipseGeometry(ellipseGeometry, path, scale);
+ }
+ else if (geometry is LineGeometry lineGeometry)
+ {
+ return ConvertLineGeometry(lineGeometry, path, scale);
+ }
+ else if (geometry is RectangleGeometry rectangleGeometry)
+ {
+ return ConvertRectangleGeometry(rectangleGeometry, path, scale);
+ }
+ else if (geometry is PathGeometry pathGeometry)
+ {
+ return ConvertPathGeometry(pathGeometry, path, scale);
+ }
+
+ throw new NotSupportedException($"Unsupported geometry type: {geometry.GetType()}");
+ }
+
+
+ public static SKRect ToSKRect(this Rect rect, float scale) =>
+ new SKRect(
+ (float)rect.X * scale,
+ (float)rect.Y * scale,
+ (float)(rect.X + rect.Width) * scale,
+ (float)(rect.Y + rect.Height) * scale);
+
+ private static SKPath ConvertEllipseGeometry(EllipseGeometry ellipseGeometry, SKPath path, float scale)
+ {
+
+ var rect = new SKRect(0, 0, (float)(ellipseGeometry.RadiusX * 2 * scale), (float)(ellipseGeometry.RadiusY * 2 * scale));
+ path.AddOval(rect);
+ return path;
+ }
+
+ private static SKPoint ToSKPoint(this Point point, float scale)
+ {
+ return new SKPoint((float)(point.X * scale), (float)(point.Y * scale));
+ }
+
+ private static SKPath ConvertLineGeometry(LineGeometry lineGeometry, SKPath path, float scale)
+ {
+
+ path.MoveTo(lineGeometry.StartPoint.ToSKPoint(scale));
+ path.LineTo(lineGeometry.EndPoint.ToSKPoint(scale));
+ return path;
+ }
+
+ private static SKPath ConvertRectangleGeometry(RectangleGeometry rectangleGeometry, SKPath path, float scale)
+ {
+
+ var rect = rectangleGeometry.Rect.ToSKRect(scale);
+ path.AddRect(rect);
+ return path;
+ }
+
+ private static SKPath ConvertPathGeometry(PathGeometry pathGeometry, SKPath path, float scale)
+ {
+
+
+ foreach (var figure in pathGeometry.Figures)
+ {
+ if (figure.StartPoint != null)
+ path.MoveTo(figure.StartPoint.ToSKPoint(scale));
+
+ foreach (var segment in figure.Segments)
+ {
+ switch (segment)
+ {
+ case LineSegment lineSegment:
+ path.LineTo(lineSegment.Point.ToSKPoint(scale));
+ break;
+
+ case BezierSegment bezierSegment:
+ path.CubicTo(
+ bezierSegment.Point1.ToSKPoint(scale),
+ bezierSegment.Point2.ToSKPoint(scale),
+ bezierSegment.Point3.ToSKPoint(scale));
+ break;
+
+ case PolyLineSegment polyLineSegment:
+ foreach (var point in polyLineSegment.Points)
+ {
+ path.LineTo(point.ToSKPoint(scale));
+ }
+ break;
+
+ case PolyBezierSegment polyBezierSegment:
+ for (int i = 0; i < polyBezierSegment.Points.Count; i += 3)
+ {
+ path.CubicTo(
+ polyBezierSegment.Points[i].ToSKPoint(scale),
+ polyBezierSegment.Points[i + 1].ToSKPoint(scale),
+ polyBezierSegment.Points[i + 2].ToSKPoint(scale));
+ }
+ break;
+
+ default:
+ throw new NotSupportedException($"Unsupported segment type: {segment.GetType()}");
+ }
+ }
+
+ if (figure.IsClosed)
+ {
+ path.Close();
+ }
+ }
+
+ return path;
+ }
+
+}
+
diff --git a/src/Engine/Internals/Extensions/InternalExtensions.cs b/src/Engine/Internals/Extensions/InternalExtensions.cs
index 78709163..fb69fad1 100644
--- a/src/Engine/Internals/Extensions/InternalExtensions.cs
+++ b/src/Engine/Internals/Extensions/InternalExtensions.cs
@@ -2,41 +2,9 @@
namespace DrawnUi.Maui.Extensions;
-public static class InternalExtensions
+public static partial class InternalExtensions
{
- #region MAUI CONTEXT
-
- public static IMauiContext? FindMauiContext(this Element element, bool fallbackToAppMauiContext = false)
- {
- if (element is IElement fe && fe.Handler?.MauiContext != null)
- return fe.Handler.MauiContext;
-
- foreach (var parent in element.GetParentsPath())
- {
- if (parent is IElement parentView && parentView.Handler?.MauiContext != null)
- return parentView.Handler.MauiContext;
- }
-
- return fallbackToAppMauiContext ? Application.Current?.FindMauiContext() : default;
- }
-
- public static IEnumerable GetParentsPath(this Element self)
- {
- Element current = self;
-
- while (!IsAppOrNull(current.RealParent))
- {
- current = current.RealParent;
- yield return current;
- }
- }
-
- internal static bool IsAppOrNull(object? element) =>
- element == null || element is IApplication;
-
- #endregion
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IntersectsWith(this SKRect rect, SKRect with, SKPoint offset)
{
@@ -228,4 +196,4 @@ public static int Clamp(this int self, int min, int max)
return self;
}
-}
\ No newline at end of file
+}
diff --git a/src/Engine/Internals/Xaml/ColumnDefinitionTypeConverter.cs b/src/Engine/Internals/Xaml/ColumnDefinitionTypeConverter.cs
index 11644557..f0055e3f 100644
--- a/src/Engine/Internals/Xaml/ColumnDefinitionTypeConverter.cs
+++ b/src/Engine/Internals/Xaml/ColumnDefinitionTypeConverter.cs
@@ -23,4 +23,4 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c
throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", obj, typeof(ColumnDefinition)));
}
-}
\ No newline at end of file
+}
diff --git a/src/Engine/Internals/Xaml/SkiaPointCollectionConverter.cs b/src/Engine/Internals/Xaml/SkiaPointCollectionConverter.cs
new file mode 100644
index 00000000..0b887074
--- /dev/null
+++ b/src/Engine/Internals/Xaml/SkiaPointCollectionConverter.cs
@@ -0,0 +1,61 @@
+using System.ComponentModel;
+using System.Globalization;
+
+namespace DrawnUi.Maui.Infrastructure.Xaml
+{
+ public class SkiaPointCollectionConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ // We can convert from a string to an IList
+ return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ // Convert the string to an IList
+ if (value is string strValue)
+ {
+ return ParseSkiaPointCollection(strValue);
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+
+ private IList ParseSkiaPointCollection(string str)
+ {
+ var points = new List();
+
+ if (string.IsNullOrWhiteSpace(str))
+ return points;
+
+ // Split the string into individual point representations
+ var pointStrings = str.Split(';');
+
+ foreach (var pointString in pointStrings)
+ {
+ var trimmedPointString = pointString.Trim();
+ if (string.IsNullOrEmpty(trimmedPointString))
+ continue;
+
+ // Split each point into X and Y coordinates, allowing for ',' or ' ' as separators
+ var coordinates = trimmedPointString.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
+
+ if (coordinates.Length != 2)
+ throw new FormatException($"Invalid point format: '{trimmedPointString}'");
+
+ if (double.TryParse(coordinates[0], NumberStyles.Float, CultureInfo.InvariantCulture, out double x) &&
+ double.TryParse(coordinates[1], NumberStyles.Float, CultureInfo.InvariantCulture, out double y))
+ {
+ points.Add(new SkiaPoint(x, y));
+ }
+ else
+ {
+ throw new FormatException($"Invalid coordinate values in point: '{trimmedPointString}'");
+ }
+ }
+
+ return points;
+ }
+ }
+}
diff --git a/src/Engine/Internals/Xaml/SkiaShadowsCollection.cs b/src/Engine/Internals/Xaml/SkiaShadowsCollection.cs
new file mode 100644
index 00000000..08727f86
--- /dev/null
+++ b/src/Engine/Internals/Xaml/SkiaShadowsCollection.cs
@@ -0,0 +1,32 @@
+using System.Collections;
+using System.Collections.ObjectModel;
+
+namespace DrawnUi.Maui.Infrastructure.Xaml
+{
+
+ public class SkiaShadowsCollection : ObservableCollection, IList
+ {
+
+ public int Add(object value)
+ {
+ if (value is SkiaShadow skiaShadow)
+ {
+ base.Add(skiaShadow);
+ }
+ else
+ {
+
+ var fromPlatform = InternalExtensions.FromPlatform(value);
+ if (fromPlatform != null)
+ {
+ base.Add(fromPlatform);
+ }
+ else
+ throw new InvalidOperationException("Invalid item type in Shadows collection");
+ }
+ return Count - 1;
+ }
+
+
+ }
+}
diff --git a/src/Engine/Views/Canvas.cs b/src/Engine/Views/Canvas.cs
index 6b270036..24fd1da2 100644
--- a/src/Engine/Views/Canvas.cs
+++ b/src/Engine/Views/Canvas.cs
@@ -1,5 +1,5 @@
-using Microsoft.Maui.Controls.Internals;
-using System.Runtime.CompilerServices;
+using System.Runtime.CompilerServices;
+using Microsoft.Maui.Controls.Internals;
using Size = Microsoft.Maui.Graphics.Size;
namespace DrawnUi.Maui.Views;
@@ -50,7 +50,6 @@ protected virtual void SetContent(SkiaControl view)
}
}
-
#region LAYOUT & AUTOSIZE
private double _widthConstraint;
@@ -367,9 +366,9 @@ protected virtual void OnGesturesAttachChanged()
else
if (this.Gestures == GesturesMode.Lock)
TouchEffect.SetShareTouch(this, TouchHandlingStyle.Lock);
- else
- if (this.Gestures == GesturesMode.Share)
- TouchEffect.SetShareTouch(this, TouchHandlingStyle.Share);
+ //else
+ //if (this.Gestures == GesturesMode.Share)
+ // TouchEffect.SetShareTouch(this, TouchHandlingStyle.Share);
}
}
@@ -380,14 +379,21 @@ protected virtual void OnGesturesAttachChanged()
bool _isPanning;
- protected SkiaSvg DebugPointer = new()
+
+ protected virtual SkiaSvg CreateDebugPointer()
{
- HeightRequest = 32,
- UseCache = SkiaCacheType.Operations,
- LockRatio = 1,
- //https://freesvg.org/hand-pointer
- SvgString = ""
- };
+ return new()
+ {
+ HeightRequest = 32,
+ UseCache = SkiaCacheType.Operations,
+ LockRatio = 1,
+ //https://freesvg.org/hand-pointer
+ SvgString =
+ ""
+ };
+ }
+
+ protected SkiaSvg DebugPointer { get; set; }
public override void OnDisposing()
{
@@ -485,14 +491,16 @@ protected virtual void ProcessGestures(SkiaGesturesParameters args)
///
public virtual void OnGestureEvent(TouchActionType type, TouchActionEventArgs args1, TouchActionResult touchAction)
{
- //Super.Log($"[Touch] Canvas got {args.Type}");
- /*
+ //Super.Log($"[Touch] Canvas got {args1.Type} {type} => {touchAction}");
+
+#if ANDROID
if (touchAction == TouchActionResult.Panning)
{
//filter micro-gestures
if ((Math.Abs(args1.Distance.Delta.X) < 1 && Math.Abs(args1.Distance.Delta.Y) < 1)
|| (Math.Abs(args1.Distance.Velocity.X / RenderingScale) < 1 && Math.Abs(args1.Distance.Velocity.Y / RenderingScale) < 1))
{
+ //Super.Log($"[Touch] IGNORED");
return;
}
@@ -522,12 +530,19 @@ public virtual void OnGestureEvent(TouchActionType type, TouchActionEventArgs ar
{
_isPanning = false;
}
- */
+#endif
+
var args = SkiaGesturesParameters.Create(touchAction, args1);
if (GesturesDebugColor.Alpha > 0)
{
+
+ if (DebugPointer == null)
+ {
+ DebugPointer = CreateDebugPointer();
+ }
+
if (args.Type == TouchActionResult.Down)
{
_debugIsPressed = true;
@@ -800,4 +815,4 @@ protected override void Draw(SkiaDrawingContext context, SKRect destination, flo
}
#endregion
-}
\ No newline at end of file
+}
diff --git a/src/Engine/Views/DrawnView.cs b/src/Engine/Views/DrawnView.cs
index f3b5cf7e..1284f019 100644
--- a/src/Engine/Views/DrawnView.cs
+++ b/src/Engine/Views/DrawnView.cs
@@ -1327,11 +1327,19 @@ SkiaDrawingContext CreateContext(SKSurface surface)
///
public event EventHandler WillFirstTimeDraw;
+ protected SKRect LastDrawnRect;
+
private bool OnDrawSurface(SKSurface surface, SKRect rect)
{
lock (LockDraw)
{
+ if (LastDrawnRect.Size != rect.Size)
+ {
+ LastDrawnRect = rect;
+ NeedMeasure = true;
+ }
+
if (!OnStartRendering(surface.Canvas))
return UpdateMode == UpdateMode.Constant;
@@ -1747,6 +1755,10 @@ protected virtual void Draw(SkiaDrawingContext context, SKRect destination, floa
destination.Top + (float)Math.Round((Padding.Top) * scale),
destination.Right - (float)Math.Round((Padding.Right) * scale),
destination.Bottom - (float)Math.Round((Padding.Bottom) * scale));
+ if (NeedMeasure)
+ {
+ child.NeedMeasure = true;
+ }
child.Render(context, rectForChild, (float)scale);
}
}
diff --git a/src/samples/Sandbox/App.xaml.cs b/src/samples/Sandbox/App.xaml.cs
index 55a5d222..ded1f258 100644
--- a/src/samples/Sandbox/App.xaml.cs
+++ b/src/samples/Sandbox/App.xaml.cs
@@ -1,9 +1,4 @@
-using AppoMobi.Specials;
-using DrawnUi.Maui;
-using Microsoft.Maui.Platform;
-using Sandbox.Views;
-using System.Globalization;
-using System.Reflection;
+using System.Reflection;
namespace Sandbox
{
@@ -19,7 +14,8 @@ public App()
var mask = "MainPage";
- var xamlResources = this.GetType().Assembly.GetCustomAttributes();
+ var xamlResources = this.GetType().Assembly
+ .GetCustomAttributes();
MainPages = xamlResources
.Where(x => x.Type.Name.Contains(mask)
@@ -66,6 +62,6 @@ public record MainPageVariant()
public string Name { get; set; }
}
-
+
}
diff --git a/src/samples/Sandbox/MainPage.xaml b/src/samples/Sandbox/MainPage.xaml
index 5dc3c476..59546a66 100644
--- a/src/samples/Sandbox/MainPage.xaml
+++ b/src/samples/Sandbox/MainPage.xaml
@@ -8,6 +8,7 @@
xmlns:generic="clr-namespace:System.Collections.Generic;assembly=System.Collections"
xmlns:sandbox="clr-namespace:Sandbox"
xmlns:views="clr-namespace:Sandbox.Views"
+ x:Name="ThisPage"
x:DataType="sandbox:MainPageViewModel"
BackgroundColor="#000000">
@@ -55,6 +56,7 @@
Source="car.png"
Tag="Car"
UseCache="ImageDoubleBuffered">
+
@@ -135,6 +137,7 @@
x:Name="Buttons"
HorizontalOptions="Center"
IsVisible="True"
+ ItemsSource="{Binding Source={x:Reference ThisPage}, Path=ButtonsList}"
Spacing="0"
Split="0"
Tag="Buttons"
diff --git a/src/samples/Sandbox/MainPage.xaml.cs b/src/samples/Sandbox/MainPage.xaml.cs
index 3bb09b98..77e97ab4 100644
--- a/src/samples/Sandbox/MainPage.xaml.cs
+++ b/src/samples/Sandbox/MainPage.xaml.cs
@@ -7,16 +7,17 @@ public partial class MainPage : BasePage
{
int count = 0;
+ public List ButtonsList { get; }
+
public MainPage()
{
try
{
+ ButtonsList = App.MainPages;
+
InitializeComponent();
BindingContext = new MainPageViewModel();
-
- Buttons.ItemsSource = App.MainPages;
-
}
catch (Exception e)
{
@@ -57,4 +58,4 @@ private void TappedSelectPage(object sender, SkiaGesturesParameters skiaGestures
-}
\ No newline at end of file
+}
diff --git a/src/samples/Sandbox/MainPageCode.cs b/src/samples/Sandbox/MainPageCode.cs
index 03cd91d7..6b750522 100644
--- a/src/samples/Sandbox/MainPageCode.cs
+++ b/src/samples/Sandbox/MainPageCode.cs
@@ -138,7 +138,7 @@ void Build()
CornerRadius = 16,
RotationX = 16,
RotationY = 20,
- RotationZ = 6,
+ RotationZ = -6,
TranslationZ = 0,
Tag="Shape",
WidthRequest = 150,
diff --git a/src/samples/Sandbox/MainPageCode2.cs b/src/samples/Sandbox/MainPageCode2.cs
deleted file mode 100644
index c8de8448..00000000
--- a/src/samples/Sandbox/MainPageCode2.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-using Sandbox.Views;
-using Canvas = DrawnUi.Maui.Views.Canvas;
-
-namespace Sandbox
-{
-
-
- public class MainPageCode2 : BasePage, IDisposable
- {
- Canvas Canvas;
-
- public void Dispose()
- {
- this.Content = null;
- Canvas?.Dispose();
- }
-
- public MainPageCode2()
- {
-#if DEBUG
- HotReloadService.UpdateApplicationEvent += ReloadUI;
-#endif
- Build();
- }
-
- private int _reloads;
-
- internal class CustomShape : SkiaShape
- {
- public CustomShape()
- {
- Type = ShapeType.Circle;
- }
-
- public override ISkiaGestureListener ProcessGestures(SkiaGesturesParameters args, GestureEventProcessingInfo apply)
- {
- if (args.Type == AppoMobi.Maui.Gestures.TouchActionResult.Tapped)
- {
- this.BackgroundColor = Colors.Blue;
- return this;
- }
-
- return base.ProcessGestures(args, apply);
- }
- }
-
- void Build()
- {
- Canvas?.Dispose();
-
- Canvas = new Canvas()
- {
- Gestures = GesturesMode.Enabled,
- HardwareAcceleration = HardwareAccelerationMode.Enabled,
-
- VerticalOptions = LayoutOptions.Start,
- HorizontalOptions = LayoutOptions.Fill,
- WidthRequest = 300,
- HeightRequest = 300,
- BackgroundColor = Colors.Gray,
-
- Content = new SkiaLayout()
- {
- VerticalOptions = LayoutOptions.Fill,
- HorizontalOptions = LayoutOptions.Fill,
- BackgroundColor = Colors.Bisque,
- Children = new List()
- {
- new SkiaLayout()
- {
- HeightRequest = 200,
- BackgroundColor = Colors.Green,
- Children = new List()
- {
- new SkiaLayout()
- {
- ColumnSpacing = 0,
- BackgroundColor = Colors.Green,
- Margin=new Thickness(0),
- Type = LayoutType.Grid,
- RowDefinitions = new RowDefinitionCollection()
- {
- new RowDefinition(new GridLength(1,GridUnitType.Star)),
- new RowDefinition(new GridLength(100,GridUnitType.Absolute)),
- new RowDefinition(new GridLength(1,GridUnitType.Star)),
- },
- Children = new List()
- {
- new CustomShape()
- {
- Tag="1",
- HeightRequest = 50,
- WidthRequest = 50,
- HorizontalOptions = LayoutOptions.Center,
- BackgroundColor = Colors.Red,
- }.WithRow(0),
- new SkiaShape()
- {
- Tag="2",
- HorizontalOptions = LayoutOptions.Fill,
- BackgroundColor = Colors.Yellow,
- }.WithRow(1),
- new CustomShape()
- {
- Tag="3",
- HeightRequest = 50,
- WidthRequest = 50,
- HorizontalOptions = LayoutOptions.Center,
- BackgroundColor = Colors.Red,
- }.WithRow(2),
- }
- },
-
-
- }
- }
-
- }
- }
-
-
- };
-
- _reloads++;
-
- this.Content = Canvas;
- }
-
- private void ReloadUI(Type[] obj)
- {
- MainThread.BeginInvokeOnMainThread(() =>
- {
- Build();
- });
- }
-
- }
-}
diff --git a/src/samples/Sandbox/MainPageDevShape.cs b/src/samples/Sandbox/MainPageDevShape.cs
new file mode 100644
index 00000000..3e0ef971
--- /dev/null
+++ b/src/samples/Sandbox/MainPageDevShape.cs
@@ -0,0 +1,140 @@
+using Sandbox.Views;
+using Canvas = DrawnUi.Maui.Views.Canvas;
+
+namespace Sandbox
+{
+
+
+ public class MainPageDevShape : BasePage, IDisposable
+ {
+ Canvas Canvas;
+
+ public void Dispose()
+ {
+ this.Content = null;
+ Canvas?.Dispose();
+ }
+
+ public MainPageDevShape()
+ {
+#if DEBUG
+ HotReloadService.UpdateApplicationEvent += ReloadUI;
+#endif
+ Build();
+ }
+
+ private int _reloads;
+
+
+ void Build()
+ {
+ Canvas?.Dispose();
+
+ Canvas = new Canvas()
+ {
+ Gestures = GesturesMode.Enabled,
+ HardwareAcceleration = HardwareAccelerationMode.Enabled,
+
+ VerticalOptions = LayoutOptions.Start,
+ HorizontalOptions = LayoutOptions.Fill,
+ WidthRequest = 300,
+ HeightRequest = 300,
+ BackgroundColor = Colors.White,
+
+ Content = new SkiaLayout()
+ {
+ VerticalOptions = LayoutOptions.Fill,
+ HorizontalOptions = LayoutOptions.Fill,
+ Children = new List()
+ {
+ new SkiaShape()
+ {
+ SmoothPoints = 0.9f,
+ VerticalOptions = LayoutOptions.Center,
+ HorizontalOptions = LayoutOptions.Center,
+ WidthRequest = 150,
+ HeightRequest = 150,
+ Type = ShapeType.Polygon,
+ StrokeColor = Colors.Black,
+ StrokeWidth = 3,
+ StrokeBlendMode = SKBlendMode.SrcIn,
+ StrokeCap = SKStrokeCap.Round,
+ Rotation = 0,
+ BackgroundColor = Colors.Yellow,
+ //Points = SkiaShape.CreateStarPoints(5, 0.5),
+ Shadows = new List()
+ {
+
+ },
+ Points = new List()
+ {
+ new (0.1f, 0.1f),
+ new (0.9f, 0.1f),
+ new (0.8f, 0.9f),
+ new (0.1f, 0.9f),
+ },
+ },
+ new SkiaShape()
+ {
+ IsVisible = false,
+ Type = ShapeType.Line,
+ StrokeColor = Colors.Red,
+ StrokeWidth = 2,
+ HorizontalOptions = LayoutOptions.Fill,
+ VerticalOptions = LayoutOptions.Fill,
+ Points = new List()
+ {
+ new (0.2f, 0.8f),
+ new (0.8f, 0.2f),
+ new (1.0f, 0.2f),
+ },
+ },
+ new SkiaShape()
+ {
+ IsVisible = false,
+ SmoothPoints = 0.3f,
+ Type = ShapeType.Line,
+ StrokeColor = Colors.Green,
+ StrokeWidth = 2,
+ HorizontalOptions = LayoutOptions.Fill,
+ VerticalOptions = LayoutOptions.Fill,
+ Points = new List()
+ {
+ new (0.2f, 0.8f),
+ new (0.8f, 0.2f),
+ new (1.0f, 0.2f),
+ },
+ },
+ new SkiaImage()
+ {
+ Source="car.png",
+ WidthRequest = 100,
+ HeightRequest = 100,
+ VerticalOptions = LayoutOptions.End,
+ Margin = 10,
+ Shadow = new Shadow()
+ {
+ Radius = 3,
+ Brush= Colors.Black,
+ Offset = new (2,2)
+ }
+ }
+ }
+ }
+ };
+
+ _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
deleted file mode 100644
index afc5ca8f..00000000
--- a/src/samples/Sandbox/MainPageRepro.xaml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/samples/Sandbox/MainPageTestImage.cs b/src/samples/Sandbox/MainPageTestImage.cs
new file mode 100644
index 00000000..4e95c605
--- /dev/null
+++ b/src/samples/Sandbox/MainPageTestImage.cs
@@ -0,0 +1,98 @@
+using Sandbox.Views;
+using Canvas = DrawnUi.Maui.Views.Canvas;
+
+namespace Sandbox
+{
+
+
+ public class MainPageTestImage : BasePage, IDisposable
+ {
+ Canvas Canvas;
+
+ public void Dispose()
+ {
+ this.Content = null;
+ Canvas?.Dispose();
+ }
+
+
+ public MainPageTestImage()
+ {
+#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()
+ {
+ VerticalOptions = LayoutOptions.Center,
+ HorizontalOptions = LayoutOptions.Center,
+ WidthRequest = 150,
+ HeightRequest = 150,
+ Type = ShapeType.Polygon,
+ StrokeColor = Colors.Black,
+ StrokeWidth = 3,
+ BackgroundColor = Colors.Red,
+ Points = new List()
+ {
+ new (0.1f, 0.1f),
+ new (0.9f, 0.1f),
+ new (0.1f, 0.5f),
+ },
+ Content = new SkiaImage()
+ {
+ Source = "cap.png",
+ Aspect = TransformAspect.AspectFit,
+ VerticalOptions = LayoutOptions.Center,
+ HorizontalOptions = LayoutOptions.Center
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ };
+
+ _reloads++;
+
+ this.Content = Canvas;
+ }
+
+ private void ReloadUI(Type[] obj)
+ {
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ Build();
+ });
+ }
+
+ }
+}
diff --git a/src/samples/Sandbox/MauiProgram.cs b/src/samples/Sandbox/MauiProgram.cs
index 2e13cf17..9ab9c4f2 100644
--- a/src/samples/Sandbox/MauiProgram.cs
+++ b/src/samples/Sandbox/MauiProgram.cs
@@ -1,7 +1,7 @@
global using DrawnUi.Maui.Draw;
global using SkiaSharp;
using Microsoft.Extensions.Logging;
-
+
namespace Sandbox
{
public static class MauiProgram
@@ -12,6 +12,14 @@ public static class MauiProgram
public static string Testing = "1\r\n2\r\n3";
+ public static List PolygonStar
+ {
+ get
+ {
+ return SkiaShape.CreateStarPoints(5);
+ }
+ }
+
public static MauiApp CreateMauiApp()
{
//SkiaImageManager.CacheLongevitySecs = 10;
@@ -26,6 +34,7 @@ public static MauiApp CreateMauiApp()
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ fonts.AddFont("OpenSans-Semibold.ttf", "FontTextTitle");
fonts.AddFont("OpenSans-Regular.ttf", "FontText");
fonts.AddFont("NotoColorEmoji-Regular.ttf", "FontEmoji");
@@ -36,7 +45,7 @@ public static MauiApp CreateMauiApp()
fonts.AddFont("Orbitron-ExtraBold.ttf", "FontGameExtraBold"); //800
});
-
+
builder.UseDrawnUi(new()
{
UseDesktopKeyboard = true, //will not work with maui shell on apple!!
@@ -62,4 +71,4 @@ public static MauiApp CreateMauiApp()
public static string ShadersFolder = "Shaders";
}
-}
\ No newline at end of file
+}
diff --git a/src/samples/Sandbox/Sandbox.csproj b/src/samples/Sandbox/Sandbox.csproj
index c8c2f709..e936bd60 100644
--- a/src/samples/Sandbox/Sandbox.csproj
+++ b/src/samples/Sandbox/Sandbox.csproj
@@ -213,8 +213,8 @@
-
- MainPageRepro.xaml
+
+ MainPageShapes.xaml
@@ -370,7 +370,7 @@
-
+
MSBuild:Compile
diff --git a/src/samples/Sandbox/TestPage.xaml b/src/samples/Sandbox/TestPage.xaml
index 7d7f5033..825cfeb4 100644
--- a/src/samples/Sandbox/TestPage.xaml
+++ b/src/samples/Sandbox/TestPage.xaml
@@ -27,9 +27,8 @@
HorizontalOptions="Fill"
Tag="Wrapper"
VerticalOptions="Fill">
-
+
+ UseCache="None"
+ VerticalOptions="Center">
-
+
diff --git a/src/samples/Sandbox/TestPage2.xaml b/src/samples/Sandbox/TestPage2.xaml
index 6a480eb8..b43cb54f 100644
--- a/src/samples/Sandbox/TestPage2.xaml
+++ b/src/samples/Sandbox/TestPage2.xaml
@@ -11,20 +11,77 @@
x:DataType="sandbox:MainPageViewModel"
BackgroundColor="#000000">
+
+
-
-
+
+
-
-
-
-
+
-
+
+
+
+
+
+
+ White
+ Yellow
+ Orange
+ Red
+ DarkRed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/samples/Sandbox/Views/MainDrawnCells.xaml b/src/samples/Sandbox/Views/MainDrawnCells.xaml
new file mode 100644
index 00000000..aec4d5fe
--- /dev/null
+++ b/src/samples/Sandbox/Views/MainDrawnCells.xaml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/samples/Sandbox/Views/MainDrawnCells.xaml.cs b/src/samples/Sandbox/Views/MainDrawnCells.xaml.cs
new file mode 100644
index 00000000..6deec270
--- /dev/null
+++ b/src/samples/Sandbox/Views/MainDrawnCells.xaml.cs
@@ -0,0 +1,69 @@
+namespace Sandbox.Views
+{
+ public partial class MainDrawnCells
+ {
+ private double _Position;
+ private readonly MockChat2ViewModel _vm;
+
+ public double Position
+ {
+ get
+ {
+ return _Position;
+ }
+ set
+ {
+ if (_Position != value)
+ {
+ _Position = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(ShowPosition));
+ }
+ }
+ }
+
+ public string ShowPosition
+ {
+ get
+ {
+ return $"{Position:0.0}";
+ }
+ }
+
+ public MainDrawnCells()
+ {
+ try
+ {
+ InitializeComponent();
+
+ BindingContext = _vm = new MockChat2ViewModel();
+
+ }
+ catch (Exception e)
+ {
+ Super.DisplayException(this, e);
+ Console.WriteLine(e);
+ }
+ }
+
+
+ bool once;
+
+ protected override void OnAppearing()
+ {
+ base.OnAppearing();
+
+ if (!once)
+ {
+ once = true;
+ _vm.LoadData();
+ }
+
+ }
+
+ private void OnScrolled(object sender, ScaledPoint e)
+ {
+
+ }
+ }
+}
diff --git a/src/samples/Sandbox/Views/MainPageBackdrop.xaml b/src/samples/Sandbox/Views/MainPageBackdrop.xaml
index 13829b48..7d2775c3 100644
--- a/src/samples/Sandbox/Views/MainPageBackdrop.xaml
+++ b/src/samples/Sandbox/Views/MainPageBackdrop.xaml
@@ -125,10 +125,10 @@
@@ -150,6 +150,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #777777
+ Gray
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pin
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/samples/Sandbox/MainPageRepro.xaml.cs b/src/samples/Sandbox/Views/MainPageShapes.xaml.cs
similarity index 96%
rename from src/samples/Sandbox/MainPageRepro.xaml.cs
rename to src/samples/Sandbox/Views/MainPageShapes.xaml.cs
index bcd0b444..b9ce1e32 100644
--- a/src/samples/Sandbox/MainPageRepro.xaml.cs
+++ b/src/samples/Sandbox/Views/MainPageShapes.xaml.cs
@@ -3,11 +3,11 @@
namespace MauiNet8;
-public partial class MainPageRepro : BasePage
+public partial class MainPageShapes : BasePage
{
int count = 0;
- public MainPageRepro()
+ public MainPageShapes()
{
try
{