diff --git a/README.md b/README.md
index 82b0c868..bb23f4d3 100644
--- a/README.md
+++ b/README.md
@@ -16,14 +16,11 @@ https://github.com/taublast/DrawnUi.Maui/assets/25801194/3b360229-ce3b-4d33-a85b
## What's new
-* `SkiaScroll` fixed scrolling vertically + horizontally at the same time (`Orientation="Both"`).
-* `SkiaShell` Navigated and Navigating events now report popups too.
-* `SkiaMediaImage` a subclassed `SkiaImage` for displaying any kind of images (image/animated gif/more..)
-* `SkiaGif` a dedicated lightweight GIF-player with playback control properties. See Sandbox project.
-* Fixed gestures inside `ImageCacheComposite` cache.
-* Fixed bug `SkiaShell` navigation gets locked when spamming popups.
-* Layout optimizations.
-* Nuget 1.2.3.4
+* Breaking ``ISkiaCell`` changed, check out demo app FestCellWithBanner new usage.
+* Android loop changed along with its OpenGL renderer.
+* Gestures fix: will not trigger Tapped after raised Longpressing.
+* Other fixes.
+* Nuget 1.2.3.6
## Demo Apps
@@ -595,6 +592,15 @@ It will render a mask over its children when hovered, think of it as an inverted
## Previously
+* `SkiaScroll` fixed scrolling vertically + horizontally at the same time (`Orientation="Both"`).
+* `SkiaShell` Navigated and Navigating events now report popups too.
+* `SkiaMediaImage` a subclassed `SkiaImage` for displaying any kind of images (image/animated gif/more..)
+* `SkiaGif` a dedicated lightweight GIF-player with playback control properties. See Sandbox project.
+* Fixed gestures inside `ImageCacheComposite` cache.
+* Fixed bug `SkiaShell` navigation gets locked when spamming popups.
+* Layout optimizations.
+* Nuget 1.2.3.4
+*
* `SkiaLayout` Column/Row uses 2 layout passes when needed, can now use full alignment options inside.
* Critical fixes for Release builds.
diff --git a/src/Addons/DrawnUi.Maui.Camera/DrawnUi.Maui.Camera.csproj b/src/Addons/DrawnUi.Maui.Camera/DrawnUi.Maui.Camera.csproj
index 0638ac6c..a8af1595 100644
--- a/src/Addons/DrawnUi.Maui.Camera/DrawnUi.Maui.Camera.csproj
+++ b/src/Addons/DrawnUi.Maui.Camera/DrawnUi.Maui.Camera.csproj
@@ -43,7 +43,7 @@
-
+
\ No newline at end of file
diff --git a/src/Addons/DrawnUi.Maui.Camera/README.md b/src/Addons/DrawnUi.Maui.Camera/README.md
new file mode 100644
index 00000000..4fa92bad
--- /dev/null
+++ b/src/Addons/DrawnUi.Maui.Camera/README.md
@@ -0,0 +1,4 @@
+
+Only Android is actually implemented.
+
+When i have spare time, hopefully soon..
\ 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 47bf2d87..00fbff4e 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 20305346..7596db34 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 2a4ec53d..f10b34eb 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 56f6f9c3..2626989b 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 1eab784b..502bc9c5 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.3.4
+ 1.2.3.6
diff --git a/src/Engine/Controls/Button/SkiaButton.cs b/src/Engine/Controls/Button/SkiaButton.cs
index 2ebf9a1e..97af8a62 100644
--- a/src/Engine/Controls/Button/SkiaButton.cs
+++ b/src/Engine/Controls/Button/SkiaButton.cs
@@ -78,7 +78,7 @@ protected override void CreateDefaultContent()
/// Clip effects with rounded rect of the frame inside
///
///
- public override SKPath CreateClip(object arguments, bool usePosition)
+ public override SKPath CreateClip(object arguments, bool usePosition, SKPath path = null)
{
if (MainFrame != null)
{
diff --git a/src/Engine/Controls/Layouts/SkiaDecoratedGrid.cs b/src/Engine/Controls/Layouts/SkiaDecoratedGrid.cs
index 33df3d21..b396d36b 100644
--- a/src/Engine/Controls/Layouts/SkiaDecoratedGrid.cs
+++ b/src/Engine/Controls/Layouts/SkiaDecoratedGrid.cs
@@ -181,20 +181,28 @@ protected override void OnLayoutChanged()
base.OnLayoutChanged();
UpdateLines();
+ }
+
+ protected override void PostArrange(SKRect destination, float widthRequest, float heightRequest, float scale)
+ {
+ base.PostArrange(destination, widthRequest, heightRequest, scale);
+ if (ContainerLines == null)
+ {
+ CreateLines();
+ }
}
protected override void Draw(SkiaDrawingContext context, SKRect destination, float scale)
{
base.Draw(context, destination, scale);
- //if (ContainerLines == null)
- //{
- // CreateLines();
- //}
+
if (ContainerLines != null)
+ {
ContainerLines.Render(context, GetDrawingRectForChildren(Destination, scale), scale);
+ }
FinalizeDrawingWithRenderObject(context, scale);
}
diff --git a/src/Engine/Controls/Pickers/ScrollPickerWheel.cs b/src/Engine/Controls/Pickers/ScrollPickerWheel.cs
index e5d3d2c9..f32342d9 100644
--- a/src/Engine/Controls/Pickers/ScrollPickerWheel.cs
+++ b/src/Engine/Controls/Pickers/ScrollPickerWheel.cs
@@ -176,7 +176,7 @@ protected override bool DrawChild(SkiaDrawingContext context, SKRect dest, ISkia
var ret = base.DrawChild(context, dest, child, scale);
- context.Canvas.Restore();
+ context.Canvas.RestoreToCount(saved);
return true;
}
diff --git a/src/Engine/Controls/PlayFrames/AnimatedFramesRenderer.cs b/src/Engine/Controls/PlayFrames/AnimatedFramesRenderer.cs
index 725323a2..32128a97 100644
--- a/src/Engine/Controls/PlayFrames/AnimatedFramesRenderer.cs
+++ b/src/Engine/Controls/PlayFrames/AnimatedFramesRenderer.cs
@@ -127,10 +127,15 @@ public void InitializeAnimator()
OnAnimatorInitializing();
- if (AutoPlay && CheckCanStartAnimator())
+ if (_delayedPlay || AutoPlay && CheckCanStartAnimator())
+ {
+ _delayedPlay = false;
Start();
+ }
}
+ bool _delayedPlay;
+
protected virtual void OnAnimatorInitializing()
{ }
@@ -161,6 +166,13 @@ public void Seek(double frame)
public virtual void Start(int delayMs = 0)
{
+
+ if (Animator == null)
+ {
+ _delayedPlay = true;
+ return;
+ }
+
if (Animator.IsRunning)
{
Animator.Stop();
diff --git a/src/Engine/Controls/Shapes/SkiaHoverMask.cs b/src/Engine/Controls/Shapes/SkiaHoverMask.cs
index 726381ac..f9720c66 100644
--- a/src/Engine/Controls/Shapes/SkiaHoverMask.cs
+++ b/src/Engine/Controls/Shapes/SkiaHoverMask.cs
@@ -26,7 +26,7 @@ protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float
using var clipContent = CreateClip(arguments, true);
clipInsideParent.AddPath(clipContent);
- ctx.Canvas.ClipPath(clipInsideParent, SKClipOperation.Difference, true);
+ ClipSmart(ctx.Canvas, clipInsideParent, SKClipOperation.Difference);
//paint this taking viewport dimensions
ctx.Canvas.DrawRect(Parent.DrawingRect, paint);
@@ -35,7 +35,7 @@ protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float
//todo add stroke property?
- ctx.Canvas.Restore();
+ ctx.Canvas.RestoreToCount(saved);
}
}
diff --git a/src/Engine/Draw/Layout/SkiaLayout.ViewsAdapter.cs b/src/Engine/Draw/Layout/SkiaLayout.ViewsAdapter.cs
index 7118b522..4cc40c78 100644
--- a/src/Engine/Draw/Layout/SkiaLayout.ViewsAdapter.cs
+++ b/src/Engine/Draw/Layout/SkiaLayout.ViewsAdapter.cs
@@ -86,10 +86,10 @@ public void MarkViewAsHidden(int index)
{
if (_dicoCellsInUse.TryGetValue(index, out SkiaControl hiddenView))
{
- if (hiddenView is ISkiaCell notify)
- {
- notify.OnDisappeared();
- }
+ //if (hiddenView is ISkiaCell notify)
+ //{
+ // notify.OnDisappeared();
+ //}
_dicoCellsInUse.Remove(index);
ReleaseView(hiddenView);
@@ -248,10 +248,10 @@ public SkiaControl GetChildAt(int index, SkiaControl template = null)
//Debug.WriteLine($"[InUse] {_dicoCellsInUse.Keys.Select(k => k.ToString()).Aggregate((current, next) => $"{current},{next}")}");
- if (view is ISkiaCell notify)
- {
- notify.OnAppearing();
- }
+ //if (view is ISkiaCell notify)
+ //{
+ // notify.OnAppearing();
+ //}
}
return view;
diff --git a/src/Engine/Draw/Layout/SkiaLayout.Wrap.cs b/src/Engine/Draw/Layout/SkiaLayout.Wrap.cs
index 54badbf9..aa21ffd3 100644
--- a/src/Engine/Draw/Layout/SkiaLayout.Wrap.cs
+++ b/src/Engine/Draw/Layout/SkiaLayout.Wrap.cs
@@ -46,12 +46,12 @@ protected virtual int DrawStack(
//PASS 1 - VISIBILITY
//we need this pass before drawing to recycle views that became hidden
- var viewsTotal = 0;
+ var currentIndex = -1;
foreach (var cell in structure.GetChildrenAsSpans())
{
- viewsTotal++;
- viewsTotal++;
- if (cell.Destination == SKRect.Empty)
+ currentIndex++;
+
+ if (cell.Destination == SKRect.Empty || cell.Measured.Pixels.IsEmpty)
{
cell.IsVisible = false;
}
diff --git a/src/Engine/Draw/Layout/SkiaLayout.cs b/src/Engine/Draw/Layout/SkiaLayout.cs
index a15767e7..ef6a5e09 100644
--- a/src/Engine/Draw/Layout/SkiaLayout.cs
+++ b/src/Engine/Draw/Layout/SkiaLayout.cs
@@ -980,6 +980,10 @@ protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float
{
SetupRenderingWithComposition(ctx, destination);
}
+ else
+ {
+ DirtyChildren.Clear();
+ }
base.Paint(ctx, destination, scale, arguments);
@@ -993,16 +997,10 @@ protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float
drawnChildrenCount = DrawViews(ctx, rectForChildren, scale);
}
else
- //grid
if (Type == LayoutType.Grid) //todo add optimization for OptimizeRenderingViewport
{
drawnChildrenCount = DrawChildrenGrid(ctx, rectForChildren, scale);
}
- //else
- //if (Type == LayoutType.Row || Type == LayoutType.Column)
- //{
- // drawnChildrenCount = DrawChildrenStack(ctx, rectForChildren, scale);
- //}
else
//stacklayout
if (IsStack)
@@ -1288,13 +1286,6 @@ private static void ItemsSourcePropertyChanged(BindableObject bindable, object o
}
- //if (newvalue is IList newList)
- //{
- // foreach (var context in newList)
- // {
- // //todo
- // }
- //}
if (newvalue is INotifyCollectionChanged newCollection)
{
@@ -1302,9 +1293,7 @@ private static void ItemsSourcePropertyChanged(BindableObject bindable, object o
newCollection.CollectionChanged += skiaControl.ItemsSourceCollectionChanged;
}
- skiaControl.PostponeInvalidation(nameof(OnItemSourceChanged), skiaControl.OnItemSourceChanged);
-
- //skiaControl.OnItemSourceChanged();
+ skiaControl.OnItemSourceChanged();
}
private static void NeedUpdateItemsSource(BindableObject bindable, object oldvalue, object newvalue)
@@ -1313,8 +1302,8 @@ private static void NeedUpdateItemsSource(BindableObject bindable, object oldval
skiaControl.PostponeInvalidation(nameof(UpdateItemsSource), skiaControl.UpdateItemsSource);
- //skiaControl.OnItemSourceChanged();
- //skiaControl.Invalidate();
+ skiaControl.Update();
+
}
void UpdateItemsSource()
@@ -1326,8 +1315,8 @@ void UpdateItemsSource()
public override void OnItemTemplateChanged()
{
- PostponeInvalidation(nameof(OnItemSourceChanged), OnItemSourceChanged);
- //OnItemSourceChanged();
+ //PostponeInvalidation(nameof(OnItemSourceChanged), OnItemSourceChanged);
+ OnItemSourceChanged();
}
public bool ApplyNewItemsSource { get; set; }
@@ -1516,9 +1505,8 @@ protected virtual void ItemsSourceCollectionChanged(object sender, NotifyCollect
GetTemplatesPoolLimit(),
GetTemplatesPoolPrefill());
- // Invalidate();
-
- // return;
+ Invalidate();
+ return;
}
break;
diff --git a/src/Engine/Draw/Scroll/SkiaScroll.Scrolling.cs b/src/Engine/Draw/Scroll/SkiaScroll.Scrolling.cs
index f75f4f37..be81b330 100644
--- a/src/Engine/Draw/Scroll/SkiaScroll.Scrolling.cs
+++ b/src/Engine/Draw/Scroll/SkiaScroll.Scrolling.cs
@@ -486,6 +486,7 @@ void BounceX(float offsetFrom, float offsetTo, float velocity)
if (displacement != 0)
{
var spring = new Spring((float)(1 * (1 + RubberDamping)), 200, (float)(0.5f * (1 + RubberDamping)));
+ _animatorFlingX.Stop();
_vectorAnimatorBounceX.Initialize(offsetTo, displacement, velocity, spring);
_vectorAnimatorBounceX.Start();
}
@@ -505,6 +506,7 @@ void BounceY(float offsetFrom, float offsetTo, float velocity)
if (displacement != 0)
{
+ _animatorFlingY.Stop();
var spring = new Spring((float)(1 * (1 + RubberDamping)), 200, (float)(0.5f * (1 + RubberDamping)));
_vectorAnimatorBounceY.Initialize(offsetTo, displacement, velocity, spring);
_vectorAnimatorBounceY.Start();
diff --git a/src/Engine/Draw/Scroll/SkiaScroll.cs b/src/Engine/Draw/Scroll/SkiaScroll.cs
index c1b8e50f..2001e2a2 100644
--- a/src/Engine/Draw/Scroll/SkiaScroll.cs
+++ b/src/Engine/Draw/Scroll/SkiaScroll.cs
@@ -914,12 +914,14 @@ ISkiaGestureListener PassToChildren()
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);
}
}
@@ -2615,17 +2617,20 @@ protected override void Draw(SkiaDrawingContext context, SKRect destination,
var posX = (float)Math.Round(ViewportOffsetX * _zoomedScale);
var posY = (float)Math.Round(ViewportOffsetY * _zoomedScale);
+ //var posX = (float)(ViewportOffsetX * _zoomedScale);
+ //var posY = (float)(ViewportOffsetY * _zoomedScale);
+
var needReposition =
_updatedViewportForPixY != posY
|| _updatedViewportForPixX != posX
- || _destination != Destination;
+ || _destination != destination;
//reposition viewport (scroll)
if (needReposition)
{
_updatedViewportForPixX = posX;
_updatedViewportForPixY = posY;
- _destination = Destination;
+ _destination = destination;
PositionViewport(DrawingRect, new(posX, posY), _zoomedScale, (float)scale);
diff --git a/src/Engine/Draw/Scroll/SkiaScrollLooped.cs b/src/Engine/Draw/Scroll/SkiaScrollLooped.cs
index df3fe0d9..d137b916 100644
--- a/src/Engine/Draw/Scroll/SkiaScrollLooped.cs
+++ b/src/Engine/Draw/Scroll/SkiaScrollLooped.cs
@@ -368,7 +368,7 @@ protected override void OnDrawn(SkiaDrawingContext context,
DrawViews(context, childRect, zoomedScale, debug);
- context.Canvas.Restore();
+ context.Canvas.RestoreToCount(count);
}
else
{
diff --git a/src/Engine/Draw/SkiaControl.cs b/src/Engine/Draw/SkiaControl.cs
index 453cd52b..0a1ba078 100644
--- a/src/Engine/Draw/SkiaControl.cs
+++ b/src/Engine/Draw/SkiaControl.cs
@@ -32,8 +32,11 @@ namespace DrawnUi.Maui.Draw
[DebuggerDisplay("{DebugString}")]
[ContentProperty("Children")]
public partial class SkiaControl : VisualElement,
- IHasAfterEffects, ISkiaControl,
- IVisualTreeElement, IReloadHandler, IHotReloadableView
+ IHasAfterEffects,
+ ISkiaControl,
+ IVisualTreeElement,
+ IReloadHandler,
+ IHotReloadableView
{
public SkiaControl()
{
@@ -1479,6 +1482,21 @@ private void Init()
CalculateSizeRequest();
}
+ public static readonly BindableProperty ClipFromProperty = BindableProperty.Create(
+ nameof(ClipFrom),
+ typeof(SkiaControl),
+ typeof(SkiaControl),
+ default(SkiaControl),
+ propertyChanged: OnControlClipFromChanged);
+
+ ///
+ /// Use clipping area from another control
+ ///
+ public new SkiaControl ClipFrom
+ {
+ get { return (SkiaControl)GetValue(ClipFromProperty); }
+ set { SetValue(ClipFromProperty, value); }
+ }
public static readonly BindableProperty ParentProperty = BindableProperty.Create(
nameof(Parent),
@@ -1538,6 +1556,14 @@ protected virtual void OnParentVisibilityChanged(bool newvalue)
Superview?.SetViewTreeVisibilityByParent(this, newvalue);
+ if (!newvalue)
+ {
+ if (this.UsingCacheType == SkiaCacheType.GPU)
+ {
+ RenderObject = null;
+ }
+ }
+
if (!IsVisible)
{
//though shell not pass
@@ -1565,6 +1591,10 @@ public virtual void OnVisibilityChanged(bool newvalue)
{
if (!newvalue)
{
+ if (this.UsingCacheType == SkiaCacheType.GPU)
+ {
+ RenderObject = null;
+ }
//DestroyRenderingObject();
}
// need to this to:
@@ -1591,6 +1621,7 @@ public virtual void OnVisibilityChanged(bool newvalue)
///
public virtual void OnDisposing()
{
+ ClippedBy = null;
Disposing?.Invoke(this, null);
Superview?.UnregisterGestureListener(this as ISkiaGestureListener);
Superview?.UnregisterAllAnimatorsByParent(this);
@@ -2256,6 +2287,13 @@ public double TransformPivotPointY
}
+ private static void OnControlClipFromChanged(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ if (bindable is SkiaControl control)
+ {
+ control.ClippedBy = newvalue as SkiaControl;
+ }
+ }
private static void OnControlParentChanged(BindableObject bindable, object oldvalue, object newvalue)
{
@@ -2801,6 +2839,7 @@ protected virtual void OnSizeChanged()
public Action Clipping { get; set; }
+ public SkiaControl ClippedBy { get; set; }
///
@@ -4332,9 +4371,17 @@ public virtual void OnSuperviewShouldRenderChanged(bool state)
{
Update();
}
- foreach (var view in Views) //will crash? why adapter nor used??
+
+ try
{
- view.OnSuperviewShouldRenderChanged(state);
+ foreach (var view in Views.ToList())
+ {
+ view.OnSuperviewShouldRenderChanged(state);
+ }
+ }
+ catch (Exception e)
+ {
+ Super.Log(e);
}
}
@@ -4787,7 +4834,7 @@ public CachedObject RenderObject
if (_renderObject != null) //if we already have something in actual cache then
{
if (UsingCacheType == SkiaCacheType.ImageDoubleBuffered
- || UsingCacheType == SkiaCacheType.Image //to just reuse same surface
+ //|| UsingCacheType == SkiaCacheType.Image //to just reuse same surface
|| UsingCacheType == SkiaCacheType.ImageComposite)
{
RenderObjectPrevious = _renderObject; //send it to back for special cases
@@ -4860,14 +4907,21 @@ public void DrawWithClipAndTransforms(
bool useClipping,
Action draw)
{
- bool isClipping = (WillClipBounds || Clipping != null) && useClipping;
+ bool isClipping = (WillClipBounds || Clipping != null || ClippedBy != null) && useClipping;
if (isClipping)
{
_preparedClipBounds ??= new SKPath();
_preparedClipBounds.Reset();
- _preparedClipBounds.AddRect(destination);
- Clipping?.Invoke(_preparedClipBounds, destination);
+ if (ClippedBy != null)
+ {
+ ClippedBy.CreateClip(null, true, _preparedClipBounds);
+ }
+ else
+ {
+ _preparedClipBounds.AddRect(destination);
+ Clipping?.Invoke(_preparedClipBounds, destination);
+ }
}
bool applyOpacity = useOpacity && Opacity < 1;
@@ -4883,14 +4937,16 @@ public void DrawWithClipAndTransforms(
_paintWithOpacity.Color = SKColors.White.WithAlpha((byte)(0xFF * Opacity));
+ var restore = 0;
+
if (applyOpacity || CustomizeLayerPaint != null)
{
CustomizeLayerPaint?.Invoke(_paintWithOpacity, destination);
- ctx.Canvas.SaveLayer(_paintWithOpacity);
+ restore = ctx.Canvas.SaveLayer(_paintWithOpacity);
}
else
{
- ctx.Canvas.Save();
+ restore = ctx.Canvas.Save();
}
if (needTransform)
@@ -4900,11 +4956,12 @@ public void DrawWithClipAndTransforms(
if (isClipping)
{
- ctx.Canvas.ClipPath(_preparedClipBounds, SKClipOperation.Intersect, true);
+ ClipSmart(ctx.Canvas, _preparedClipBounds);
}
draw(ctx);
- ctx.Canvas.Restore();
+
+ ctx.Canvas.RestoreToCount(restore);
}
else
{
@@ -4975,7 +5032,63 @@ protected virtual void ApplyTransforms(SkiaDrawingContext ctx, SKRect destinatio
ctx.Canvas.SetMatrix(drawingMatrix);
}
+ public static bool IsSimpleRectangle(SKPath path)
+ {
+ if (path == null)
+ return false;
+
+ if (path.VerbCount != 5)
+ return false;
+
+ var iterator = path.CreateRawIterator();
+ var points = new SKPoint[4];
+ int lineToCount = 0;
+ bool moveToFound = false;
+
+ SKPathVerb verb;
+ while ((verb = iterator.Next(points)) != SKPathVerb.Done)
+ {
+ switch (verb)
+ {
+ case SKPathVerb.Move:
+ if (moveToFound)
+ return false; // Multiple MoveTo commands
+ moveToFound = true;
+ break;
+ case SKPathVerb.Line:
+ if (lineToCount < 4)
+ {
+ lineToCount++;
+ }
+ else
+ {
+ return false; // More than 4 LineTo commands
+ }
+ break;
+
+ case SKPathVerb.Close:
+ return lineToCount == 4; // Ensure we have exactly 4 LineTo commands before Close
+
+ default:
+ return false; // Any other command invalidates the rectangle check
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Use antialiasing if path is not rectangle
+ ///
+ ///
+ ///
+ ///
+ public static void ClipSmart(SKCanvas canvas, SKPath path, SKClipOperation operation = SKClipOperation.Intersect)
+ {
+ bool isRectangle = IsSimpleRectangle(path);
+ canvas.ClipPath(path, operation, !isRectangle); // Disable anti-aliasing if it's a rectangle
+ }
public virtual bool NeedMeasure
@@ -5259,7 +5372,7 @@ public async Task ProcessOffscreenCacheRenderingAsync()
}
}
- if (NeedUpdate) //someone changed us while rendering inner content
+ if (NeedUpdate || RenderObjectNeedsUpdate) //someone changed us while rendering inner content
{
Update(); //kick
}
@@ -5399,7 +5512,7 @@ protected virtual CachedObject CreateRenderingObject(
grContext = accelerated.GRContext;
//hardware accelerated
surface = SKSurface.Create(accelerated.GRContext,
- true,
+ false,
cacheSurfaceInfo);
}
}
@@ -5571,7 +5684,8 @@ protected void CreateRenderingObjectAndPaint(
oldObject = RenderObject;
}
else
- if (usingCacheType == SkiaCacheType.Image || usingCacheType == SkiaCacheType.ImageComposite)
+ if (usingCacheType == SkiaCacheType.Image
+ || usingCacheType == SkiaCacheType.ImageComposite)
{
oldObject = RenderObjectPrevious;
}
@@ -5638,6 +5752,9 @@ protected virtual void Paint(SkiaDrawingContext ctx, SKRect destination, float s
}
private bool _wasDrawn;
+ ///
+ /// Signals if this control was drawn on canvas one time at least, it will be set by Paint method.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public bool WasDrawn
{
@@ -5665,9 +5782,10 @@ public bool WasDrawn
///
///
///
- public virtual SKPath CreateClip(object arguments, bool usePosition)
+ public virtual SKPath CreateClip(object arguments, bool usePosition, SKPath path = null)
{
- var path = new SKPath();
+ path ??= new SKPath();
+
if (usePosition)
{
path.AddRect(DrawingRect);
@@ -5680,6 +5798,7 @@ public virtual SKPath CreateClip(object arguments, bool usePosition)
}
private bool _RenderObjectPreviousNeedsUpdate;
+ [EditorBrowsable(EditorBrowsableState.Never)]
public bool RenderObjectPreviousNeedsUpdate
{
get
@@ -5699,6 +5818,7 @@ public bool RenderObjectPreviousNeedsUpdate
///
/// Should delete RenderObject when starting new frame rendering
///
+ [EditorBrowsable(EditorBrowsableState.Never)]
public bool RenderObjectNeedsUpdate
{
get
@@ -5734,6 +5854,7 @@ public double UseTranslationX
}
}
+ [EditorBrowsable(EditorBrowsableState.Never)]
public bool HasTransform
{
get
@@ -5748,6 +5869,7 @@ public bool HasTransform
}
}
+ [EditorBrowsable(EditorBrowsableState.Never)]
public bool IsDistorted
{
get
@@ -5844,6 +5966,7 @@ protected virtual int RenderViewsList(IEnumerable skiaControls,
return count;
}
+ [EditorBrowsable(EditorBrowsableState.Never)]
public virtual bool UsesRenderingTree
{
get
@@ -5877,9 +6000,9 @@ public record SkiaControlWithRect(SkiaControl Control,
public bool Invalidated { get; set; } = true;
///
- /// For internal use
+ /// For internal use, set by Update method
///
-
+ [EditorBrowsable(EditorBrowsableState.Never)]
public bool NeedUpdate
{
get
@@ -5901,10 +6024,11 @@ public bool NeedUpdate
-
-
DrawnView _superview;
-
+ ///
+ /// Our canvas
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
public DrawnView Superview
{
get
@@ -5932,6 +6056,10 @@ public DrawnView Superview
}
}
+ ///
+ /// For virtualization
+ ///
+ ///
public virtual ScaledRect GetOnScreenVisibleArea()
{
if (this.UsingCacheType != SkiaCacheType.None)
@@ -5979,6 +6107,7 @@ public virtual void InvalidateViewport()
///
//protected SkiaControl DirtyChild { get; set; }
+
protected readonly ControlsTracker DirtyChildren = new();
//protected readonly ConcurrentBag DirtyChildren = new();
@@ -5995,6 +6124,7 @@ public virtual void UpdateByChild(SkiaControl control)
///
/// Used to check whether to apply IsClippedToBounds property
///
+ [EditorBrowsable(EditorBrowsableState.Never)]
public virtual bool WillClipBounds
{
get
@@ -6004,6 +6134,7 @@ public virtual bool WillClipBounds
}
}
+ [EditorBrowsable(EditorBrowsableState.Never)]
public virtual bool WillClipEffects
{
get
@@ -6030,6 +6161,9 @@ protected virtual void UpdateInternal()
Parent?.UpdateByChild(this);
}
+ ///
+ /// Main method to invalidate cache and invoke rendering
+ ///
public virtual void Update()
{
InvalidateCache();
@@ -6039,6 +6173,9 @@ public virtual void Update()
Updated?.Invoke(this, null);
}
+ ///
+ /// Triggered by Update method
+ ///
public event EventHandler Updated;
public static MemoryStream StreamFromString(string value)
@@ -6056,7 +6193,10 @@ public static double PixelsToDeviceUnits(double units)
return units / GetDensity();
}
-
+ ///
+ /// For internal use
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
public SKRect Destination { get; protected set; }
protected SKPaint PaintSystem { get; set; }
@@ -6153,11 +6293,11 @@ protected void ActionWithClipping(SKRect viewport, SKCanvas canvas, Action draw)
var saved = canvas.Save();
- canvas.ClipPath(clip, SKClipOperation.Intersect, true);
+ ClipSmart(canvas, clip);
draw();
- canvas.Restore();
+ canvas.RestoreToCount(saved);
}
@@ -6184,7 +6324,9 @@ public virtual void Invalidate()
InvalidateParent();
}
-
+ ///
+ /// Summing up Margins and AddMargin.. properties
+ ///
public virtual void CalculateMargins()
{
//use Margin property as starting point
@@ -6356,6 +6498,10 @@ protected static void NeedInvalidateViewport(BindableObject bindable, object old
}
}
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ ///
+ /// Is using ItemTemplate or no
+ ///
public virtual bool IsTemplated
{
get
diff --git a/src/Engine/Draw/SkiaShape.cs b/src/Engine/Draw/SkiaShape.cs
index 15771852..8693977c 100644
--- a/src/Engine/Draw/SkiaShape.cs
+++ b/src/Engine/Draw/SkiaShape.cs
@@ -360,6 +360,8 @@ protected void CalculateSizeForStroke(SKRect destination, float scale)
protected SKRoundRect DrawRoundedRect { get; set; }
+ SKPath ClipContentPath { get; set; } = new();
+
public override void OnDisposing()
{
@@ -371,6 +373,7 @@ public override void OnDisposing()
DrawPathAligned?.Dispose();
DrawRoundedRect?.Dispose();
DrawPathShape?.Dispose();
+ ClipContentPath?.Dispose();
base.OnDisposing();
}
@@ -384,8 +387,10 @@ public override object CreatePaintArguments()
};
}
- public override SKPath CreateClip(object arguments, bool usePosition)
+ public override SKPath CreateClip(object arguments, bool usePosition, SKPath path = null)
{
+ path ??= new SKPath();
+
var strokeAwareSize = MeasuredStrokeAwareSize;
var strokeAwareChildrenSize = MeasuredStrokeAwareChildrenSize;
@@ -403,8 +408,6 @@ public override SKPath CreateClip(object arguments, bool usePosition)
strokeAwareChildrenSize.Width + offsetToZero.X, strokeAwareChildrenSize.Height + offsetToZero.Y);
}
- var path = new SKPath();
-
switch (Type)
{
case ShapeType.Path:
@@ -685,15 +688,16 @@ void PaintWithShadows(Action render)
if (ClipBackgroundColor)
{
- using var clip = new SKPath();
- using var clipContent = CreateClip(arguments, true);
- clip.AddPath(clipContent);
+ ClipContentPath ??= new();
+ ClipContentPath.Reset();
+ CreateClip(arguments, true, ClipContentPath);
+
var saved = ctx.Canvas.Save();
- ctx.Canvas.ClipPath(clip, SKClipOperation.Difference, true);
+ ClipSmart(ctx.Canvas, ClipContentPath, SKClipOperation.Difference);
render();
- ctx.Canvas.Restore();
+ ctx.Canvas.RestoreToCount(saved);
}
else
{
@@ -728,17 +732,20 @@ void PaintWithShadows(Action render)
});
//draw children views clipped with shape
- using var clip = new SKPath();
- using var clipContent = CreateClip(arguments, true);
- clip.AddPath(clipContent);
+ ClipContentPath ??= new();
+ ClipContentPath.Reset();
+
+ CreateClip(arguments, true, ClipContentPath);
+
var saved = ctx.Canvas.Save();
- ctx.Canvas.ClipPath(clip, SKClipOperation.Intersect, true);
+
+ ClipSmart(ctx.Canvas, ClipContentPath);
var rectForChildren = ContractPixelsRect(strokeAwareChildrenSize, scale, Padding);
DrawViews(ctx, rectForChildren, scale);
- ctx.Canvas.Restore();
+ ctx.Canvas.RestoreToCount(saved);
//last pass for stroke over background or children
if (willStroke)
diff --git a/src/Engine/Draw/Svg/SkiaSvg.cs b/src/Engine/Draw/Svg/SkiaSvg.cs
index fa613241..5c925c76 100644
--- a/src/Engine/Draw/Svg/SkiaSvg.cs
+++ b/src/Engine/Draw/Svg/SkiaSvg.cs
@@ -914,11 +914,11 @@ protected void DrawPicture(SKCanvas canvas, SKPicture picture, SKRect dest,
var saved = canvas.Save();
- canvas.ClipPath(path, SKClipOperation.Intersect, true);
+ ClipSmart(canvas, path);
canvas.DrawPicture(picture, display.Left, display.Top, paint);
- canvas.Restore();
+ canvas.RestoreToCount(saved);
}
}
else
@@ -1147,11 +1147,11 @@ protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float
}
var saved = ctx.Canvas.Save();
- ctx.Canvas.ClipPath(clipPath, SKClipOperation.Intersect, true);
+ ClipSmart(ctx.Canvas, clipPath);
ctx.Canvas.DrawPicture(Svg.Picture, ref matrix, paint);
- ctx.Canvas.Restore();
+ ctx.Canvas.RestoreToCount(saved);
clipPath.Dispose();
}
diff --git a/src/Engine/Draw/Text/SkiaLabel.cs b/src/Engine/Draw/Text/SkiaLabel.cs
index 7cf3af42..96799008 100644
--- a/src/Engine/Draw/Text/SkiaLabel.cs
+++ b/src/Engine/Draw/Text/SkiaLabel.cs
@@ -481,6 +481,7 @@ public override ScaledSize Measure(float widthConstraint, float heightConstraint
if (PaintDefault.Typeface == null)
{
+ PaintDefault.Typeface = SKTypeface.Default;
UpdateFont();
return MeasuredSize;
}
@@ -498,6 +499,10 @@ public override ScaledSize Measure(float widthConstraint, float heightConstraint
UpdateFontMetrics(PaintDefault);
var usePaint = PaintDefault;
+ if (PaintDefault.Typeface == null)
+ {
+ PaintDefault.Typeface = SKTypeface.Default;
+ }
if (Spans.Count == 0)
{
@@ -769,11 +774,18 @@ public override void OnDisposing()
private void DisposePaint(ref SKPaint paint)
{
- if (paint != null)
+ try
+ {
+ if (paint != null)
+ {
+ paint.Typeface = SKTypeface.Default; // Preserve cached font from disposing
+ paint.Dispose();
+ paint = null;
+ }
+ }
+ catch (Exception e)
{
- paint.Typeface = SKTypeface.Default; // Preserve cached font from disposing
- paint.Dispose();
- paint = null;
+ Console.WriteLine(e);
}
}
@@ -1506,6 +1518,10 @@ protected void ReplaceFont()
_replaceFont = null;
OnFontUpdated();
}
+ if (TypeFace == null)
+ {
+ TypeFace = SKTypeface.Default;
+ }
}
protected float _fontUnderline;
diff --git a/src/Engine/Draw/Text/TextSpan.cs b/src/Engine/Draw/Text/TextSpan.cs
index 788e64d5..7327fe9c 100644
--- a/src/Engine/Draw/Text/TextSpan.cs
+++ b/src/Engine/Draw/Text/TextSpan.cs
@@ -182,9 +182,11 @@ public SKTypeface TypeFace
get => _typeFace;
set
{
- if (Equals(value, _typeFace)) return;
- _typeFace = value;
- OnPropertyChanged();
+ if (_typeFace != value)
+ {
+ _typeFace = value;
+ OnPropertyChanged();
+ }
}
}
@@ -334,13 +336,21 @@ public virtual void Dispose()
{
Parent = null;
- if (Paint != null)
+ try
{
- Paint.Typeface = SKTypeface.Default; //do not dipose typeface that could be cached and reused
- Paint.Dispose();
- Paint = null;
+ if (Paint != null)
+ {
+ Paint.Typeface = SKTypeface.Default; //do not dipose typeface that could be cached and reused
+ Paint.Dispose();
+ Paint = null;
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
}
+
CommandTapped = null;
Tapped = null;
}
@@ -436,7 +446,7 @@ public TextSpan()
Paint = new()
{
IsAntialias = true,
- Typeface = TypeFace
+ Typeface = _typeFace
};
}
diff --git a/src/Engine/DrawnUi.Maui.csproj b/src/Engine/DrawnUi.Maui.csproj
index eaf410da..7ed105dd 100644
--- a/src/Engine/DrawnUi.Maui.csproj
+++ b/src/Engine/DrawnUi.Maui.csproj
@@ -27,6 +27,7 @@
maui drawnui skia skiasharp draw ui
Nick Kovalsky aka AppoMobi
(c) AppoMobi, 2023 - present day
+ MIT
icon128.png
https://github.com/taublast/DrawnUi.Maui
@@ -87,7 +88,7 @@
-
+
@@ -120,6 +121,7 @@
+
diff --git a/src/Engine/DrawnUi.Maui.sln b/src/Engine/DrawnUi.Maui.sln
deleted file mode 100644
index fb7b6541..00000000
--- a/src/Engine/DrawnUi.Maui.sln
+++ /dev/null
@@ -1,25 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.9.34728.123
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DrawnUi.Maui", "DrawnUi.Maui.csproj", "{21A0DB3A-226E-48CA-ACE1-D82EC08D6C1A}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {21A0DB3A-226E-48CA-ACE1-D82EC08D6C1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {21A0DB3A-226E-48CA-ACE1-D82EC08D6C1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {21A0DB3A-226E-48CA-ACE1-D82EC08D6C1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {21A0DB3A-226E-48CA-ACE1-D82EC08D6C1A}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {DD752EBC-E624-4C69-8009-CF480E4CACD8}
- EndGlobalSection
-EndGlobal
diff --git a/src/Engine/Features/Animations/Animators/EdgeGlowAnimator.cs b/src/Engine/Features/Animations/Animators/EdgeGlowAnimator.cs
index f3802025..75c6693e 100644
--- a/src/Engine/Features/Animations/Animators/EdgeGlowAnimator.cs
+++ b/src/Engine/Features/Animations/Animators/EdgeGlowAnimator.cs
@@ -56,12 +56,13 @@ void Draw()
clipContent.Offset((float)(control.TranslationX * scale), (float)(control.TranslationY * scale));
clipInsideParent.AddPath(clipContent);
- context.Canvas.Save();
- context.Canvas.ClipPath(clipInsideParent, SKClipOperation.Intersect, true);
+ var count = context.Canvas.Save();
+
+ SkiaControl.ClipSmart(context.Canvas, clipInsideParent);
Draw();
- context.Canvas.Restore();
+ context.Canvas.RestoreToCount(count);
}
}
else
diff --git a/src/Engine/Features/Animations/Animators/RenderingAnimator.cs b/src/Engine/Features/Animations/Animators/RenderingAnimator.cs
index 038dbaaf..dd1cdbb8 100644
--- a/src/Engine/Features/Animations/Animators/RenderingAnimator.cs
+++ b/src/Engine/Features/Animations/Animators/RenderingAnimator.cs
@@ -58,10 +58,12 @@ void Render()
{
ApplyControlClipping(control, clipInsideParent, selfDrawingLocation);
- context.Canvas.Save();
- context.Canvas.ClipPath(clipInsideParent, SKClipOperation.Intersect, true);
+ var count = context.Canvas.Save();
+
+ SkiaControl.ClipSmart(context.Canvas, clipInsideParent);
draw();
- context.Canvas.Restore();
+
+ context.Canvas.RestoreToCount(count);
}
}
else
@@ -101,5 +103,6 @@ protected static void ApplyControlClipping(IDrawnBase control, SKPath clipInside
clipContent.Offset((float)(control.TranslationX * control.RenderingScale), (float)(control.TranslationY * control.RenderingScale));
clipInsideParent.AddPath(clipContent);
}
+ clipContent.Dispose();
}
}
\ No newline at end of file
diff --git a/src/Engine/Features/Animations/Animators/ShimmerAnimator.cs b/src/Engine/Features/Animations/Animators/ShimmerAnimator.cs
index 59026cbe..2a1149b6 100644
--- a/src/Engine/Features/Animations/Animators/ShimmerAnimator.cs
+++ b/src/Engine/Features/Animations/Animators/ShimmerAnimator.cs
@@ -92,7 +92,7 @@ protected override bool OnRendering(IDrawnBase control, SkiaDrawingContext conte
canvas.DrawRect(rect, Paint);
- canvas.Restore();
+ canvas.RestoreToCount(saved);
}
});
diff --git a/src/Engine/Features/Animations/Animators/SkiaValueAnimator.cs b/src/Engine/Features/Animations/Animators/SkiaValueAnimator.cs
index 9b419006..eb894605 100644
--- a/src/Engine/Features/Animations/Animators/SkiaValueAnimator.cs
+++ b/src/Engine/Features/Animations/Animators/SkiaValueAnimator.cs
@@ -23,7 +23,6 @@ public override void Stop()
this.tcs?.TrySetCanceled();
}
cancellationTokenRegistration.Dispose();
-
}
public virtual async Task RunAsync(Action initialize, CancellationToken cancellationToken = default)
@@ -34,16 +33,16 @@ public virtual async Task RunAsync(Action initialize, CancellationToken cancella
}
this.tcs = new TaskCompletionSource();
-
this.cancellationTokenRegistration = cancellationToken.Register(() =>
{
- if (this.IsRunning)
- {
- this.Stop();
- }
+ //if (this.IsRunning)
+ //{
+ // this.Stop();
+ //}
});
initialize?.Invoke();
+
Start();
await this.tcs.Task;
diff --git a/src/Engine/Features/Effects/ChainDropShadowsEffect.cs b/src/Engine/Features/Effects/ChainDropShadowsEffect.cs
index 72151676..8a707835 100644
--- a/src/Engine/Features/Effects/ChainDropShadowsEffect.cs
+++ b/src/Engine/Features/Effects/ChainDropShadowsEffect.cs
@@ -134,11 +134,11 @@ public override ChainEffectResult Draw(SKRect destination, SkiaDrawingContext ct
(float)shadow.Blur, (float)shadow.Blur,
shadow.Color.ToSKColor());
- ctx.Canvas.SaveLayer(Paint);
+ var saved = ctx.Canvas.SaveLayer(Paint);
drawControl(ctx);
- ctx.Canvas.Restore();
+ ctx.Canvas.RestoreToCount(saved);
}
return ChainEffectResult.Create(false);
diff --git a/src/Engine/Features/Effects/IStateEffect.cs b/src/Engine/Features/Effects/IStateEffect.cs
index 3770e487..1d4ce0e4 100644
--- a/src/Engine/Features/Effects/IStateEffect.cs
+++ b/src/Engine/Features/Effects/IStateEffect.cs
@@ -3,7 +3,7 @@
public interface IStateEffect : ISkiaEffect
{
///
- /// Will be invoked before actually painting but after gestures processing and other internal calculations
+ /// Will be invoked before actually painting but after gestures processing and other internal calculations. By SkiaControl.OnBeforeDrawing method.
///
void UpdateState();
}
\ No newline at end of file
diff --git a/src/Engine/Features/Effects/SkiaShaderEffect.cs b/src/Engine/Features/Effects/SkiaShaderEffect.cs
index 070ae5d9..11e675f6 100644
--- a/src/Engine/Features/Effects/SkiaShaderEffect.cs
+++ b/src/Engine/Features/Effects/SkiaShaderEffect.cs
@@ -1,10 +1,6 @@
-namespace DrawnUi.Maui.Draw;
-
-public class ShaderAnimatedEffect : SkiaShaderEffect
-{
-
-}
+using System.ComponentModel;
+namespace DrawnUi.Maui.Draw;
public class StateEffect : SkiaEffect, IStateEffect
@@ -80,7 +76,12 @@ public virtual void Render(SkiaDrawingContext ctx, SKRect destination)
{
if (_paintWithShader == null)
{
- _paintWithShader = new SKPaint();
+ _paintWithShader = new SKPaint()
+ {
+ //todo check how if this affect anything after upcoming skiasharp3 fix
+ //FilterQuality = SKFilterQuality.High,
+ //IsDither = true
+ };
}
SKImage source = Parent.RenderObject.Image;
@@ -90,6 +91,23 @@ public virtual void Render(SkiaDrawingContext ctx, SKRect destination)
ctx.Canvas.DrawRect(destination, _paintWithShader);
}
+ protected SKShader PrimaryTexture;
+ private SKImage _lastSource;
+
+ protected virtual SKShader CompilePrimaryTexture(SKImage source)
+ {
+ //if (_lastSource == null && source != null) //snapshot changed
+ if (source != _lastSource && source != null) //snapshot changed
+ {
+ _lastSource = source;
+ var dispose = PrimaryTexture;
+ PrimaryTexture = source.ToShader(SKShaderTileMode.Repeat, SKShaderTileMode.Repeat);
+ if (dispose != PrimaryTexture)
+ dispose?.Dispose();
+ }
+ return PrimaryTexture;
+ }
+
public virtual SKShader CreateShader(SkiaDrawingContext ctx, SKRect destination, SKImage source)
{
//we need to
@@ -98,9 +116,10 @@ public virtual SKShader CreateShader(SkiaDrawingContext ctx, SKRect destination,
//step 3: prepare uniforms to pass, including those textures
//step 4: with all above create an SKShader to use in SKPaint
- if (CompiledShader == null)
+ if (CompiledShader == null || _hasNewShader)
{
CompileShader();
+ _hasNewShader = false;
}
if (NeedApply)
@@ -110,9 +129,8 @@ public virtual SKShader CreateShader(SkiaDrawingContext ctx, SKRect destination,
source = CreateSnapshot(ctx, destination);
}
- //if textures didn't change.. use previous?
var killTextures = TexturesUniforms;
- TexturesUniforms = CreateTexturesUniforms(ctx, destination, source);
+ TexturesUniforms = CreateTexturesUniforms(ctx, destination, CompilePrimaryTexture(source));
#if SKIA3
if (Parent != null && killTextures != null)
@@ -160,12 +178,10 @@ protected virtual SKRuntimeEffectUniforms CreateUniforms(SKRect destination)
return uniforms;
}
- protected virtual SKRuntimeEffectChildren CreateTexturesUniforms(SkiaDrawingContext ctx, SKRect destination, SKImage snapshot)
+ protected virtual SKRuntimeEffectChildren CreateTexturesUniforms(SkiaDrawingContext ctx, SKRect destination, SKShader texture1)
{
- if (snapshot != null)
+ if (texture1 != null)
{
- var texture1 = snapshot
- .ToShader();
//.ToShader(SKShaderTileMode.Repeat, SKShaderTileMode.Repeat);
return new SKRuntimeEffectChildren(CompiledShader)
{
@@ -187,16 +203,32 @@ protected virtual void CompileShader()
CompiledShader = SkSl.Compile(shaderCode);
}
- public static readonly BindableProperty ShaderFilenameProperty = BindableProperty.Create(nameof(ShaderSource),
+ protected virtual void ApplyShaderSource()
+ {
+ _hasNewShader = true;
+ Update();
+ }
+
+ private bool _hasNewShader;
+
+ public static readonly BindableProperty ShaderSourceProperty = BindableProperty.Create(nameof(ShaderSource),
typeof(string),
typeof(SkiaShaderEffect),
- string.Empty, propertyChanged: NeedUpdate);
- public string ShaderSource
+ string.Empty, propertyChanged: NeedChangeSource);
+
+ private static void NeedChangeSource(BindableObject bindable, object oldvalue, object newvalue)
{
- get { return (string)GetValue(ShaderFilenameProperty); }
- set { SetValue(ShaderFilenameProperty, value); }
+ if (bindable is SkiaShaderEffect control)
+ {
+ control.ApplyShaderSource();
+ }
}
+ public string ShaderSource
+ {
+ get { return (string)GetValue(ShaderSourceProperty); }
+ set { SetValue(ShaderSourceProperty, value); }
+ }
public override void Update()
@@ -210,6 +242,8 @@ public override void Update()
else
kill?.Dispose();
+ //Parent?.Repaint();
+
base.Update();
}
@@ -217,6 +251,7 @@ protected override void OnDisposing()
{
Shader?.Dispose();
Shader = null;
+ _lastSource = null;
CompiledShader?.Dispose();
#if SKIA3
@@ -224,6 +259,8 @@ protected override void OnDisposing()
#endif
_paintWithShader?.Dispose();
+ PrimaryTexture?.Dispose();
+
base.OnDisposing();
}
diff --git a/src/Engine/Features/Fonts/SkiaFontManager.cs b/src/Engine/Features/Fonts/SkiaFontManager.cs
index a6e0c443..37534b19 100644
--- a/src/Engine/Features/Fonts/SkiaFontManager.cs
+++ b/src/Engine/Features/Fonts/SkiaFontManager.cs
@@ -90,7 +90,14 @@ public async Task GetFont(string fontFamily, int fontWeight)
return SKTypeface.Default;
}
var alias = GetRegisteredAlias(fontFamily, fontWeight);
- return await GetFont(alias);
+ var font = await GetFont(alias);
+
+ //safety check to avoid any chance of crash split_config.arm64_v8a.apk!libSkiaSharp.so (sk_font_set_typeface+60)
+ if (font == null)
+ {
+ return SKTypeface.Default;
+ }
+ return font;
}
///
diff --git a/src/Engine/Features/Gestures/AddGestures.cs b/src/Engine/Features/Gestures/AddGestures.cs
index dacb67a0..4c64a485 100644
--- a/src/Engine/Features/Gestures/AddGestures.cs
+++ b/src/Engine/Features/Gestures/AddGestures.cs
@@ -68,6 +68,20 @@ public override ISkiaGestureListener ProcessGestures(SkiaGesturesParameters args
if (_parent == null || !_parent.CanDraw)
return null;
+
+ if (args.Type == TouchActionResult.LongPressing)
+ {
+ var command = GetCommandLongPressing(_parent);
+ if (command != null)
+ {
+ var parameter = GetCommandLongPressingParameter(_parent);
+ if (parameter == null)
+ parameter = _parent.BindingContext;
+ command?.Execute(parameter);
+ return this;
+ }
+ }
+ else
if (args.Type == TouchActionResult.Tapped)
{
var anim = GetAnimationTapped(_parent);
@@ -111,19 +125,6 @@ public override ISkiaGestureListener ProcessGestures(SkiaGesturesParameters args
}
}
else
- if (args.Type == TouchActionResult.LongPressing)
- {
- var command = GetCommandLongPressing(_parent);
- if (command != null)
- {
- var parameter = GetCommandLongPressingParameter(_parent);
- if (parameter == null)
- parameter = _parent.BindingContext;
- command?.Execute(parameter);
- return this;
- }
- }
- else
if (args.Type == TouchActionResult.Panning)
{
if (GetLockPanning(_parent))
diff --git a/src/Engine/Internals/Extensions/DependencyExtensions.cs b/src/Engine/Internals/Extensions/DependencyExtensions.cs
index 5aa13580..98c733d3 100644
--- a/src/Engine/Internals/Extensions/DependencyExtensions.cs
+++ b/src/Engine/Internals/Extensions/DependencyExtensions.cs
@@ -9,6 +9,7 @@
using Microsoft.Maui.Platform;
using Polly;
using Polly.Timeout;
+using SkiaSharp.Views.Maui.Controls.Compatibility;
using SkiaSharp.Views.Maui.Controls.Hosting;
using System.Net;
using System.Net.Http.Headers;
@@ -57,6 +58,11 @@ public static MauiAppBuilder UseDrawnUi(this MauiAppBuilder builder, UiSettings
builder.ConfigureMauiHandlers(handlers =>
{
#if ANDROID
+
+#if !SKIA3
+ handlers.AddHandler(typeof(SkiaViewAccelerated), typeof(FixedSKGLViewRenderer));
+#endif
+
handlers.AddHandler(typeof(DrawnUiBasePage), typeof(DrawnUiBasePageHandler));
handlers.AddHandler(typeof(MauiEntry), typeof(MauiEntryHandler));
handlers.AddHandler(typeof(MauiEditor), typeof(MauiEditorHandler));
diff --git a/src/Engine/Internals/Interfaces/IDrawnBase.cs b/src/Engine/Internals/Interfaces/IDrawnBase.cs
index b759ef31..63d3e235 100644
--- a/src/Engine/Internals/Interfaces/IDrawnBase.cs
+++ b/src/Engine/Internals/Interfaces/IDrawnBase.cs
@@ -40,7 +40,7 @@ public interface IDrawnBase : IDisposable, ICanBeUpdatedWithContext
/// 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 CreateClip(object arguments, bool usePosition, SKPath path = null);
bool RegisterAnimator(ISkiaAnimator animator);
diff --git a/src/Engine/Internals/Interfaces/ISelectableOption.cs b/src/Engine/Internals/Interfaces/ISelectableOption.cs
new file mode 100644
index 00000000..163c8411
--- /dev/null
+++ b/src/Engine/Internals/Interfaces/ISelectableOption.cs
@@ -0,0 +1,6 @@
+namespace DrawnUi.Maui.Draw;
+
+public interface ISelectableOption : IHasTitleWithId, ICanBeSelected
+{
+ public bool IsReadOnly { get; }
+}
\ No newline at end of file
diff --git a/src/Engine/Internals/Interfaces/ISkiaCell.cs b/src/Engine/Internals/Interfaces/ISkiaCell.cs
index a2d653b5..4633bc83 100644
--- a/src/Engine/Internals/Interfaces/ISkiaCell.cs
+++ b/src/Engine/Internals/Interfaces/ISkiaCell.cs
@@ -1,29 +1,6 @@
namespace DrawnUi.Maui.Draw;
-public interface ISkiaCell : IVisibilityAware
+public interface ISkiaCell
{
- // Cell went on screen inside parent viewport. This will be called after binding context was set
- //OnAppearing
-
- // Cell went offscreen from parent viewport
- // OnDisappeared
-
public void OnScrolled();
}
-
-public interface IVisibilityAware
-{
- ///
- /// This can sometimes be omitted,
- ///
- void OnAppearing();
-
- ///
- /// This event can sometimes be called without prior OnAppearing
- ///
- void OnAppeared();
-
- void OnDisappeared();
-
- void OnDisappearing();
-}
\ No newline at end of file
diff --git a/src/Engine/Internals/Interfaces/IVisibilityAware.cs b/src/Engine/Internals/Interfaces/IVisibilityAware.cs
new file mode 100644
index 00000000..fa533710
--- /dev/null
+++ b/src/Engine/Internals/Interfaces/IVisibilityAware.cs
@@ -0,0 +1,18 @@
+namespace DrawnUi.Maui.Draw;
+
+public interface IVisibilityAware
+{
+ ///
+ /// This can sometimes be omitted,
+ ///
+ void OnAppearing();
+
+ ///
+ /// This event can sometimes be called without prior OnAppearing
+ ///
+ void OnAppeared();
+
+ void OnDisappeared();
+
+ void OnDisappearing();
+}
\ No newline at end of file
diff --git a/src/Engine/Internals/Models/SelectableAction.cs b/src/Engine/Internals/Models/SelectableAction.cs
new file mode 100644
index 00000000..15dfb0cc
--- /dev/null
+++ b/src/Engine/Internals/Models/SelectableAction.cs
@@ -0,0 +1,28 @@
+namespace DrawnUi.Maui.Internals;
+
+public class SelectableAction : TitleWithStringId, ISelectableOption
+{
+ public SelectableAction()
+ {
+ Id = Guid.NewGuid().ToString();
+ }
+ public SelectableAction(string title, Action action)
+ {
+ Id = Guid.NewGuid().ToString();
+ Title = title;
+ Action = action;
+ }
+
+ public SelectableAction(string id, string title, Action action)
+ {
+ Id = id;
+ Title = title;
+ Action = action;
+ }
+
+
+ public Action Action { get; set; }
+ public bool Selected { get; set; }
+
+ public bool IsReadOnly { get; } = false;
+}
\ No newline at end of file
diff --git a/src/Engine/Internals/Models/TitleWithStringId.cs b/src/Engine/Internals/Models/TitleWithStringId.cs
new file mode 100644
index 00000000..2c81b3c1
--- /dev/null
+++ b/src/Engine/Internals/Models/TitleWithStringId.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DrawnUi.Maui.Internals
+{
+
+
+ public class TitleWithStringId
+ {
+ public string Id { get; set; }
+ public string Title { get; set; }
+ }
+}
diff --git a/src/Engine/Platforms/Android/DrawnView.Android.cs b/src/Engine/Platforms/Android/DrawnView.Android.cs
index 860a3079..2e445ac7 100644
--- a/src/Engine/Platforms/Android/DrawnView.Android.cs
+++ b/src/Engine/Platforms/Android/DrawnView.Android.cs
@@ -82,7 +82,7 @@ public void CheckElementVisibility(VisualElement element)
protected virtual void DisposePlatform()
{
- Super.ChoreographerCallback -= OnChoreographer;
+ Super.OnFrame -= OnChoreographer;
}
object lockFrame = new();
@@ -113,8 +113,8 @@ private void OnChoreographer(object sender, EventArgs e)
public virtual void SetupRenderingLoop()
{
- Super.ChoreographerCallback -= OnChoreographer;
- Super.ChoreographerCallback += OnChoreographer;
+ Super.OnFrame -= OnChoreographer;
+ Super.OnFrame += OnChoreographer;
}
protected virtual void PlatformHardwareAccelerationChanged()
diff --git a/src/Engine/Platforms/Android/Files.Android.cs b/src/Engine/Platforms/Android/Files.Android.cs
index fb5436b4..33036691 100644
--- a/src/Engine/Platforms/Android/Files.Android.cs
+++ b/src/Engine/Platforms/Android/Files.Android.cs
@@ -1,4 +1,5 @@
-using Android.Media;
+using Android.Content.Res;
+using Android.Media;
namespace DrawnUi.Maui.Infrastructure
{
@@ -21,6 +22,16 @@ public static void RefreshSystem(FileDescriptor file)
// .AbsolutePath;
//}
+ ///
+ /// tries to get all resources from assets folder Resources/Raw/{subfolder}
+ ///
+ ///
+ public static List ListAssets(string subfolder)
+ {
+ AssetManager assets = Platform.AppContext.Assets;
+ return assets.List(subfolder).ToList();
+ }
+
public static void Share(string message, IEnumerable fullFilenames)
{
MainThread.BeginInvokeOnMainThread(async () =>
diff --git a/src/Engine/Platforms/Android/Legacy/Class1.cs b/src/Engine/Platforms/Android/Legacy/Class1.cs
new file mode 100644
index 00000000..5f282702
--- /dev/null
+++ b/src/Engine/Platforms/Android/Legacy/Class1.cs
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Engine/Platforms/Android/Legacy/FixedSKGLViewRenderer.cs b/src/Engine/Platforms/Android/Legacy/FixedSKGLViewRenderer.cs
new file mode 100644
index 00000000..a0b24099
--- /dev/null
+++ b/src/Engine/Platforms/Android/Legacy/FixedSKGLViewRenderer.cs
@@ -0,0 +1,197 @@
+using Android.Content;
+using Android.Opengl;
+using Microsoft.Maui.Controls.Compatibility.Platform.Android;
+using Microsoft.Maui.Controls.Platform;
+using SkiaSharp;
+using SkiaSharp.Views.Maui.Controls.Compatibility;
+using SkiaSharp.Views.Maui.Platform;
+using System;
+using System;
+using System.ComponentModel;
+using SKFormsView = DrawnUi.Maui.Views.SkiaViewAccelerated;
+using SKNativePaintGLSurfaceEventArgs = SkiaSharp.Views.Android.SKPaintGLSurfaceEventArgs;
+using SKNativeView = DrawnUi.Maui.SkiaGLTexture;
+
+//[assembly: ExportRenderer(typeof(SKFormsView), typeof(FixedSKGLViewRenderer))]
+// =>
+// handlers.AddHandler(typeof(SkiaViewAccelerated), typeof(FixedSKGLViewRenderer));
+namespace DrawnUi.Maui;
+
+public class FixedSKGLViewRenderer : FixedSKGLViewRendererBase
+{
+ public FixedSKGLViewRenderer(Context context)
+ : base(context)
+ {
+ }
+
+ protected override void SetupRenderLoop(bool oneShot)
+ {
+ if (oneShot)
+ {
+ Control.RequestRender();
+ }
+
+ Control.RenderMode = Element.HasRenderLoop
+ ? Rendermode.Continuously
+ : Rendermode.WhenDirty;
+ }
+
+ protected override SKNativeView CreateNativeControl()
+ {
+ var view = GetType() == typeof(SKGLViewRenderer)
+ ? new SKNativeView(Context)
+ : base.CreateNativeControl();
+
+ // Force the opacity to false for consistency with the other platforms
+ view.SetOpaque(false);
+
+ return view;
+ }
+}
+
+public abstract class FixedSKGLViewRendererBase : Microsoft.Maui.Controls.Handlers.Compatibility.ViewRenderer
+ where TFormsView : SKFormsView
+ where TNativeView : SKNativeView
+{
+
+
+ protected FixedSKGLViewRendererBase(Context context)
+ : base(context)
+ {
+ Initialize();
+ }
+
+ private void Initialize()
+ {
+
+ }
+
+ private bool measured;
+
+ public GRContext GRContext => Control.GRContext;
+
+ public static object lockUpdate = new();
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ if (e.OldElement != null)
+ {
+ var oldController = (ISKGLViewController)e.OldElement;
+
+ // unsubscribe from events
+ oldController.SurfaceInvalidated -= OnSurfaceInvalidated;
+ oldController.GetCanvasSize -= OnGetCanvasSize;
+ oldController.GetGRContext -= OnGetGRContext;
+ }
+
+ if (e.NewElement != null)
+ {
+ var newController = (ISKGLViewController)e.NewElement;
+
+ // create the native view
+ if (Control == null)
+ {
+ var view = CreateNativeControl();
+ view.PaintSurface += OnPaintSurface;
+ SetNativeControl(view);
+ }
+
+ // subscribe to events from the user
+ newController.SurfaceInvalidated += OnSurfaceInvalidated;
+ newController.GetCanvasSize += OnGetCanvasSize;
+ newController.GetGRContext += OnGetGRContext;
+
+ // start the rendering
+ SetupRenderLoop(false);
+ }
+
+ base.OnElementChanged(e);
+ }
+
+
+ protected override TNativeView CreateNativeControl()
+ {
+ return (TNativeView)Activator.CreateInstance(typeof(TNativeView), new[] { Context });
+ }
+
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ // refresh the render loop
+ if (e.PropertyName == SKFormsView.HasRenderLoopProperty.PropertyName)
+ {
+ SetupRenderLoop(false);
+ }
+ else if (e.PropertyName == SKFormsView.EnableTouchEventsProperty.PropertyName)
+ {
+
+ }
+
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ // detach all events before disposing
+ var controller = (ISKGLViewController)Element;
+ if (controller != null)
+ {
+ controller.SurfaceInvalidated -= OnSurfaceInvalidated;
+ controller.GetCanvasSize -= OnGetCanvasSize;
+ controller.GetGRContext -= OnGetGRContext;
+ }
+
+ var control = Control;
+ if (control != null)
+ {
+ control.PaintSurface -= OnPaintSurface;
+ }
+
+
+
+ base.Dispose(disposing);
+ }
+
+ protected abstract void SetupRenderLoop(bool oneShot);
+
+ private SKPoint GetScaledCoord(double x, double y)
+ {
+ return new SKPoint((float)x, (float)y);
+ }
+
+
+ // the user asked to repaint
+ private void OnSurfaceInvalidated(object sender, EventArgs eventArgs)
+ {
+ // if we aren't in a loop, then refresh once
+ if (!Element.HasRenderLoop)
+ {
+ SetupRenderLoop(true);
+ }
+ }
+
+ // the user asked for the size
+ private void OnGetCanvasSize(object sender, GetPropertyValueEventArgs e)
+ {
+ e.Value = Control?.CanvasSize ?? SKSize.Empty;
+ }
+
+ // the user asked for the current GRContext
+ private void OnGetGRContext(object sender, GetPropertyValueEventArgs e)
+ {
+ e.Value = Control?.GRContext;
+ }
+
+ private void OnPaintSurface(object sender, SKNativePaintGLSurfaceEventArgs e)
+ {
+ lock (lockUpdate)
+ {
+ var controller = Element as ISKGLViewController;
+
+ // the control is being repainted, let the user know
+ controller?.OnPaintSurface(new SKPaintGLSurfaceEventArgs(e.Surface, e.BackendRenderTarget));
+ }
+ }
+}
+
diff --git a/src/Engine/Platforms/Android/Legacy/SkiaGLTexture.cs b/src/Engine/Platforms/Android/Legacy/SkiaGLTexture.cs
new file mode 100644
index 00000000..fc2e385e
--- /dev/null
+++ b/src/Engine/Platforms/Android/Legacy/SkiaGLTexture.cs
@@ -0,0 +1,61 @@
+using Android.Content;
+using Android.Util;
+using System.ComponentModel;
+using SKPaintGLSurfaceEventArgs = SkiaSharp.Views.Android.SKPaintGLSurfaceEventArgs;
+
+namespace DrawnUi.Maui
+{
+ public partial class SkiaGLTexture : SkiaGLTextureView
+ {
+ private SkiaGLTextureRenderer renderer;
+
+ public SkiaGLTexture(Context context)
+ : base(context)
+ {
+ Initialize();
+ }
+
+ public SkiaGLTexture(Context context, IAttributeSet attrs)
+ : base(context, attrs)
+ {
+ Initialize();
+ }
+
+ private void Initialize()
+ {
+ SetEGLContextClientVersion(2);
+ SetEGLConfigChooser(8, 8, 8, 8, 0, 8);
+
+ renderer = new InternalRenderer(this);
+ SetRenderer(renderer);
+ }
+
+ public SKSize CanvasSize => renderer.CanvasSize;
+
+ public GRContext GRContext => renderer.GRContext;
+
+ public event EventHandler PaintSurface;
+
+ protected virtual void OnPaintSurface(SKPaintGLSurfaceEventArgs e)
+ {
+ PaintSurface?.Invoke(this, e);
+ }
+
+
+ private class InternalRenderer : SkiaGLTextureRenderer
+ {
+ private readonly SkiaGLTexture textureView;
+
+ public InternalRenderer(SkiaGLTexture textureView)
+ {
+ this.textureView = textureView;
+ }
+
+ protected override void OnPaintSurface(SKPaintGLSurfaceEventArgs e)
+ {
+ textureView.OnPaintSurface(e);
+ }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Engine/Platforms/Android/Legacy/SkiaGLTextureRenderer.cs b/src/Engine/Platforms/Android/Legacy/SkiaGLTextureRenderer.cs
new file mode 100644
index 00000000..c0ea5452
--- /dev/null
+++ b/src/Engine/Platforms/Android/Legacy/SkiaGLTextureRenderer.cs
@@ -0,0 +1,141 @@
+using Android.Opengl;
+using System.ComponentModel;
+using SKPaintGLSurfaceEventArgs = SkiaSharp.Views.Android.SKPaintGLSurfaceEventArgs;
+
+namespace DrawnUi.Maui;
+
+public abstract partial class SkiaGLTextureRenderer : Java.Lang.Object, SkiaGLTextureView.IRenderer
+{
+ private const SKColorType colorType = SKColorType.Rgba8888;
+ private const GRSurfaceOrigin surfaceOrigin = GRSurfaceOrigin.BottomLeft;
+
+ private GRContext context;
+ private GRGlFramebufferInfo glInfo;
+ private GRBackendRenderTarget renderTarget;
+ private SKSurface surface;
+ private SKCanvas canvas;
+
+ private SKSizeI lastSize;
+ private SKSizeI newSize;
+
+ public SKSize CanvasSize => lastSize;
+
+ public GRContext GRContext => context;
+
+
+ protected virtual void OnPaintSurface(SKPaintGLSurfaceEventArgs e)
+ {
+ }
+
+
+ public void OnDrawFrame()
+ {
+
+ GLES10.GlClear(GLES10.GlColorBufferBit | GLES10.GlDepthBufferBit | GLES10.GlStencilBufferBit);
+
+ // create the contexts if not done already
+ if (context == null)
+ {
+ var glInterface = GRGlInterface.Create();
+ context = GRContext.CreateGl(glInterface);
+ }
+
+ // manage the drawing surface
+ if (renderTarget == null || lastSize != newSize || !renderTarget.IsValid)
+ {
+ // create or update the dimensions
+ lastSize = newSize;
+
+ // read the info from the buffer
+ var buffer = new int[3];
+ GLES20.GlGetIntegerv(GLES20.GlFramebufferBinding, buffer, 0);
+ GLES20.GlGetIntegerv(GLES20.GlStencilBits, buffer, 1);
+ GLES20.GlGetIntegerv(GLES20.GlSamples, buffer, 2);
+ var samples = buffer[2];
+ var maxSamples = context.GetMaxSurfaceSampleCount(colorType);
+ if (samples > maxSamples)
+ samples = maxSamples;
+ glInfo = new GRGlFramebufferInfo((uint)buffer[0], colorType.ToGlSizedFormat());
+
+ // destroy the old surface
+ surface?.Dispose();
+ surface = null;
+ canvas = null;
+
+ // re-create the render target
+ renderTarget?.Dispose();
+ renderTarget = new GRBackendRenderTarget(newSize.Width, newSize.Height, samples, buffer[1], glInfo);
+ }
+
+ // create the surface
+ if (surface == null)
+ {
+ surface = SKSurface.Create(context, renderTarget, surfaceOrigin, colorType);
+ }
+
+ if (surface != null)
+ {
+ canvas = surface.Canvas;
+ }
+
+ if (canvas != null)
+ {
+ var restore = canvas.Save();
+
+ var e = new SKPaintGLSurfaceEventArgs(surface, renderTarget, surfaceOrigin, colorType);
+ OnPaintSurface(e);
+
+ canvas.RestoreToCount(restore);
+
+ canvas.Flush();
+ context.Flush();
+ }
+
+ }
+
+
+
+ public void OnSurfaceChanged(int width, int height)
+ {
+ GLES20.GlViewport(0, 0, width, height);
+
+ // get the new surface size
+ newSize = new SKSizeI(width, height);
+ }
+
+
+
+ public void OnSurfaceCreated(EGLConfig config)
+ {
+ // Create the context and resources
+ if (context != null)
+ {
+ FreeContext();
+ }
+
+ }
+
+ public void OnSurfaceDestroyed()
+ {
+ FreeContext();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ FreeContext();
+ }
+ base.Dispose(disposing);
+ }
+
+ private void FreeContext()
+ {
+ surface?.Dispose();
+ surface = null;
+ renderTarget?.Dispose();
+ renderTarget = null;
+ context?.Dispose();
+ context = null;
+ }
+}
\ No newline at end of file
diff --git a/src/Engine/Platforms/Android/Legacy/SkiaGLTextureView.cs b/src/Engine/Platforms/Android/Legacy/SkiaGLTextureView.cs
new file mode 100644
index 00000000..e156180f
--- /dev/null
+++ b/src/Engine/Platforms/Android/Legacy/SkiaGLTextureView.cs
@@ -0,0 +1,1401 @@
+using Android.App;
+using Android.Content;
+using Android.Content.PM;
+using Android.Graphics;
+using Android.Opengl;
+using Android.Opengl;
+using Android.Runtime;
+using Android.Util;
+using Android.Views;
+using Javax.Microedition.Khronos.Egl;
+using Application = Android.App.Application;
+using EGLConfig = Android.Opengl.EGLConfig;
+using EGLContext = Android.Opengl.EGLContext;
+using EGLDisplay = Android.Opengl.EGLDisplay;
+using EGLSurface = Android.Opengl.EGLSurface;
+using View = Android.Views.View;
+
+namespace DrawnUi.Maui;
+
+public class SkiaGLTextureView : TextureView, TextureView.ISurfaceTextureListener, View.IOnLayoutChangeListener
+{
+ public static bool EnableLogging = false;
+
+ private WeakReference thisWeakRef;
+ private GLThread glThread;
+ private IRenderer renderer;
+ private bool detachedFromWindow;
+ private IEGLConfigChooser eglConfigChooser;
+ private IEGLContextFactory eglContextFactory;
+ private IEGLWindowSurfaceFactory eglWindowSurfaceFactory;
+ private int eglContextClientVersion;
+
+ public SkiaGLTextureView(Context context)
+ : base(context)
+ {
+ Initialize();
+ }
+
+ public SkiaGLTextureView(Context context, IAttributeSet attrs)
+ : base(context, attrs)
+ {
+ Initialize();
+ }
+
+ private void Initialize()
+ {
+ thisWeakRef = new WeakReference(this);
+
+ SurfaceTextureListener = this;
+ AddOnLayoutChangeListener(this);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (glThread != null)
+ {
+ // GLThread may still be running if this view was never attached to a window.
+ glThread.RequestExitAndWait();
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ public bool PreserveEGLContextOnPause { get; set; }
+
+
+ public void SetRenderer(IRenderer renderer)
+ {
+ CheckRenderThreadState();
+ if (eglConfigChooser == null)
+ {
+ eglConfigChooser = new SimpleEGLConfigChooser(this, true);
+ }
+ if (eglContextFactory == null)
+ {
+ eglContextFactory = new DefaultContextFactory(this);
+ }
+ if (eglWindowSurfaceFactory == null)
+ {
+ eglWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
+ }
+ this.renderer = renderer;
+ glThread = new GLThread(thisWeakRef);
+ glThread.Start();
+ }
+
+ public void SetEGLContextFactory(IEGLContextFactory factory)
+ {
+ CheckRenderThreadState();
+ eglContextFactory = factory;
+ }
+
+ public void SetEGLWindowSurfaceFactory(IEGLWindowSurfaceFactory factory)
+ {
+ CheckRenderThreadState();
+ eglWindowSurfaceFactory = factory;
+ }
+
+ public void SetEGLConfigChooser(IEGLConfigChooser configChooser)
+ {
+ CheckRenderThreadState();
+ eglConfigChooser = configChooser;
+ }
+
+ public void SetEGLConfigChooser(bool needDepth)
+ {
+ SetEGLConfigChooser(new SimpleEGLConfigChooser(this, needDepth));
+ }
+
+ public void SetEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)
+ {
+ SetEGLConfigChooser(new ComponentSizeChooser(this, redSize, greenSize, blueSize, alphaSize, depthSize, stencilSize));
+ }
+
+ public void SetEGLContextClientVersion(int version)
+ {
+ CheckRenderThreadState();
+ eglContextClientVersion = version;
+ }
+
+ public Rendermode RenderMode
+ {
+ get { return glThread.GetRenderMode(); }
+ set { glThread.SetRenderMode(value); }
+ }
+
+ public void RequestRender()
+ {
+ glThread.RequestRender();
+ }
+
+ public void OnSurfaceTextureUpdated(SurfaceTexture surface)
+ {
+ //glThread.RequestRender();
+ }
+
+ public void OnSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)
+ {
+ glThread.OnSurfaceCreated();
+ glThread.RequestRender();
+ }
+
+ public bool OnSurfaceTextureDestroyed(SurfaceTexture surface)
+ {
+ // Surface will be destroyed when we return
+ glThread.OnSurfaceDestroyed();
+ return true;
+ }
+
+ public void OnSurfaceTextureSizeChanged(SurfaceTexture surface, int w, int h)
+ {
+ glThread.OnWindowResize(w, h);
+ }
+
+ public void OnPause()
+ {
+ glThread.OnPause();
+ }
+
+ public void OnResume()
+ {
+ glThread.OnResume();
+ }
+
+ public void QueueEvent(Action r)
+ {
+ QueueEvent(new Java.Lang.Runnable(r));
+ }
+
+ public void QueueEvent(Java.Lang.IRunnable r)
+ {
+ glThread.QueueEvent(r);
+ }
+
+ protected override void OnAttachedToWindow()
+ {
+ base.OnAttachedToWindow();
+
+ LogDebug($" OnAttachedToWindow");
+
+ if (detachedFromWindow && (renderer != null))
+ {
+ var renderMode = Rendermode.Continuously;
+ if (glThread != null)
+ {
+ renderMode = glThread.GetRenderMode();
+ glThread.RequestExitAndWait();
+ }
+ glThread = new GLThread(thisWeakRef);
+ if (renderMode != Rendermode.Continuously)
+ {
+ glThread.SetRenderMode(renderMode);
+ }
+ glThread.Start();
+ }
+ detachedFromWindow = false;
+ }
+
+ protected override void OnDetachedFromWindow()
+ {
+ LogDebug($" OnDetachedFromWindow reattach={detachedFromWindow}");
+
+ if (glThread != null)
+ {
+ glThread.RequestExitAndWait();
+ }
+ detachedFromWindow = true;
+ base.OnDetachedFromWindow();
+ }
+
+ private void CheckRenderThreadState()
+ {
+ if (glThread != null)
+ {
+ throw new Exception("setRenderer has already been called for this instance.");
+ }
+ }
+
+ public void OnLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)
+ {
+ OnSurfaceTextureSizeChanged(SurfaceTexture, right - left, bottom - top);
+ }
+
+ [Conditional("DEBUG")]
+ private static void LogDebug(string message)
+ {
+ if (EnableLogging)
+ {
+ Log.Debug("SkiaGLTextureView", message);
+ }
+ }
+
+ [Conditional("DEBUG")]
+ private static void LogError(string message)
+ {
+ Log.Error("SkiaGLTextureView", message);
+ }
+
+
+
+ public interface IEGLConfigChooser
+ {
+ EGLConfig ChooseConfig(EGLDisplay display);
+ }
+
+ public interface IEGLContextFactory
+ {
+ EGLContext CreateContext(EGLDisplay display, EGLConfig eglConfig);
+
+ void DestroyContext(EGLDisplay display, EGLContext context);
+ }
+
+ public interface IEGLWindowSurfaceFactory
+ {
+ EGLSurface CreateWindowSurface(EGLDisplay display, EGLConfig config, Java.Lang.Object nativeWindow);
+
+ void DestroySurface(EGLDisplay display, EGLSurface surface);
+ }
+
+ public interface IRenderer
+ {
+ void OnDrawFrame();
+
+ void OnSurfaceChanged(int width, int height);
+
+ void OnSurfaceCreated(EGLConfig config);
+
+ void OnSurfaceDestroyed();
+ }
+
+ private class DefaultContextFactory : IEGLContextFactory
+ {
+ private SkiaGLTextureView textureView;
+
+ public DefaultContextFactory(SkiaGLTextureView textureView)
+ {
+ this.textureView = textureView;
+ }
+
+ public EGLContext CreateContext(EGLDisplay display, EGLConfig config)
+ {
+ var attribList = new int[] {
+ EglHelper.EGL_CONTEXT_CLIENT_VERSION, textureView.eglContextClientVersion,
+ EGL14.EglNone
+ };
+
+ return EGL14.EglCreateContext(
+ display, config,
+ EGL14.EglNoContext,
+ textureView.eglContextClientVersion != 0 ? attribList : null
+ , 0);
+ }
+
+ public void DestroyContext(EGLDisplay display, EGLContext context)
+ {
+ LogDebug($"[DefaultContextFactory] DestroyContext tid={Thread.CurrentThread.ManagedThreadId} display={display} context={context}");
+
+ if (!EGL14.EglDestroyContext(display, context))
+ {
+ var error = EGL14.EglGetError();
+ LogError($"[DefaultContextFactory] eglDestroyContext failed: {error}");
+ throw new Exception($"eglDestroyContext failed: {error}");
+ }
+ }
+ }
+
+ private class DefaultWindowSurfaceFactory : IEGLWindowSurfaceFactory
+ {
+ public EGLSurface CreateWindowSurface(EGLDisplay display, EGLConfig config, Java.Lang.Object nativeWindow)
+ {
+ EGLSurface result = null;
+ try
+ {
+ int[] attribList = {
+ EGL14.EglNone
+ };
+ result = EGL14.EglCreateWindowSurface(display, config, nativeWindow, attribList, 0);
+ }
+ catch (Exception ex)
+ {
+ // This exception indicates that the surface flinger surface
+ // is not valid. This can happen if the surface flinger surface has
+ // been torn down, but the application has not yet been
+ // notified via SurfaceHolder.Callback.surfaceDestroyed.
+ // In theory the application should be notified first,
+ // but in practice sometimes it is not. See b/4588890
+ LogError($"[DefaultWindowSurfaceFactory] eglCreateWindowSurface failed: {ex}");
+ }
+ return result;
+ }
+
+ public void DestroySurface(EGLDisplay display, EGLSurface surface)
+ {
+ EGL14.EglDestroySurface(display, surface);
+ }
+ }
+
+ private abstract class BaseConfigChooser : IEGLConfigChooser
+ {
+ private SkiaGLTextureView textureView;
+ private int[] configSpec;
+
+ public BaseConfigChooser(SkiaGLTextureView textureView, int[] configSpec)
+ {
+ this.textureView = textureView;
+ this.configSpec = FilterConfigSpec(configSpec);
+ }
+
+ public EGLConfig ChooseConfig(EGLDisplay display)
+ {
+ int[] configAttribs = {
+ EGL14.EglRedSize, 8,
+ EGL14.EglGreenSize, 8,
+ EGL14.EglBlueSize, 8,
+ EGL14.EglAlphaSize, 8,
+ EGL14.EglDepthSize, 16,
+ EGL14.EglSurfaceType, EGL14.EglWindowBit,
+ EGL14.EglRenderableType, EGL14.EglOpenglEs2Bit,
+ EGL14.EglNone
+ };
+
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] numConfigs = new int[1];
+ if (!EGL14.EglChooseConfig(display, configAttribs, 0, configs, 0, configs.Length, numConfigs, 0))
+ {
+ throw new Exception("eglChooseConfig failed");
+ }
+
+ if (numConfigs[0] <= 0)
+ {
+ throw new Exception("No configs match configSpec");
+ }
+
+ return configs[0];
+ }
+
+ public abstract EGLConfig ChooseConfig(EGLDisplay display, EGLConfig[] configs);
+
+ private int[] FilterConfigSpec(int[] spec)
+ {
+ if (textureView.eglContextClientVersion != 2)
+ {
+ return spec;
+ }
+
+ // We know none of the subclasses define EGL_RENDERABLE_TYPE.
+ // And we know the configSpec is well formed.
+ var len = spec.Length;
+ var newConfigSpec = new int[len + 2];
+ Array.Copy(spec, 0, newConfigSpec, 0, len - 1);
+ newConfigSpec[len - 1] = EGL14.EglRenderableType;
+ newConfigSpec[len] = EglHelper.EGL_OPENGL_ES2_BIT;
+ newConfigSpec[len + 1] = EGL14.EglNone;
+ return newConfigSpec;
+ }
+ }
+
+ private class ComponentSizeChooser : BaseConfigChooser
+ {
+ private int[] value;
+ private int redSize;
+ private int greenSize;
+ private int blueSize;
+ private int alphaSize;
+ private int depthSize;
+ private int stencilSize;
+
+ public ComponentSizeChooser(SkiaGLTextureView textureView, int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)
+ : base(textureView, new int[] {
+ EGL14.EglRedSize, redSize,
+ EGL14.EglGreenSize, greenSize,
+ EGL14.EglBlueSize, blueSize,
+ EGL14.EglAlphaSize, alphaSize,
+ EGL14.EglDepthSize, depthSize,
+ EGL14.EglStencilSize, stencilSize,
+ EGL14.EglNone
+ })
+ {
+ value = new int[1];
+ this.redSize = redSize;
+ this.greenSize = greenSize;
+ this.blueSize = blueSize;
+ this.alphaSize = alphaSize;
+ this.depthSize = depthSize;
+ this.stencilSize = stencilSize;
+ }
+
+ public override EGLConfig ChooseConfig(EGLDisplay display, EGLConfig[] configs)
+ {
+ foreach (var config in configs)
+ {
+ var d = FindConfigAttrib(display, config, EGL14.EglDepthSize, 0);
+ var s = FindConfigAttrib(display, config, EGL14.EglStencilSize, 0);
+ if ((d >= depthSize) && (s >= stencilSize))
+ {
+ var r = FindConfigAttrib(display, config, EGL14.EglRedSize, 0);
+ var g = FindConfigAttrib(display, config, EGL14.EglGreenSize, 0);
+ var b = FindConfigAttrib(display, config, EGL14.EglBlueSize, 0);
+ var a = FindConfigAttrib(display, config, EGL14.EglAlphaSize, 0);
+ if ((r == redSize) && (g == greenSize) && (b == blueSize) && (a == alphaSize))
+ {
+ return config;
+ }
+ }
+ }
+ return null;
+ }
+
+ private int FindConfigAttrib(EGLDisplay display, EGLConfig config, int attribute, int defaultValue)
+ {
+ int[] value = new int[1];
+ if (EGL14.EglGetConfigAttrib(display, config, attribute, value, 0))
+ {
+ return value[0];
+ }
+ return defaultValue;
+ }
+ }
+
+ private class SimpleEGLConfigChooser : ComponentSizeChooser
+ {
+ public SimpleEGLConfigChooser(SkiaGLTextureView textureView, bool withDepthBuffer)
+ : base(textureView, 8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0)
+ {
+ }
+ }
+
+
+ private class GLThread
+ {
+ private Thread thread;
+ private volatile GLThreadManager threadManager;
+ private EglHelper eglHelper;
+ private WeakReference textureViewWeakRef;
+
+ // Once the thread is started, all accesses to the following member
+ // variables are protected by the sGLThreadManager monitor
+ private volatile bool shouldExit;
+ public volatile bool exited;
+ private volatile bool requestPaused;
+ private volatile bool paused;
+ private volatile bool hasSurface;
+ private volatile bool surfaceIsBad;
+ private volatile bool waitingForSurface;
+ private volatile bool haveEglContext;
+ private volatile bool haveEglSurface;
+ private volatile bool finishedCreatingEglSurface;
+ private volatile bool shouldReleaseEglContext;
+ private volatile int width;
+ private volatile int height;
+ private Rendermode renderMode;
+ private Queue eventQueue = new Queue();
+ private volatile bool surfaceSizeChanged = true;
+ private volatile bool requestRender;
+ private volatile bool renderComplete;
+ // End of member variables protected by the sGLThreadManager monitor.
+
+ public GLThread(WeakReference glTextureViewWeakRef)
+ {
+ threadManager = new GLThreadManager();
+
+ width = 0;
+ height = 0;
+ requestRender = true;
+ renderMode = Rendermode.Continuously;
+ textureViewWeakRef = glTextureViewWeakRef;
+ thread = new Thread(new ThreadStart(Run));
+ }
+
+ public int Id => thread.ManagedThreadId;
+
+ public void Start()
+ {
+ thread.Start();
+ }
+
+ public void Run()
+ {
+ thread.Name = "GLThread " + thread.ManagedThreadId;
+
+ LogDebug($"[GLThread {Id}] Starting '{thread.Name}'");
+
+ try
+ {
+ GuardedRun();
+ }
+ finally
+ {
+ threadManager.ThreadExiting(this);
+ }
+ }
+
+ private void StopEglSurfaceLocked()
+ {
+ if (haveEglSurface)
+ {
+ haveEglSurface = false;
+ eglHelper.DestroySurface();
+ }
+ }
+
+ private void StopEglContextLocked()
+ {
+ if (haveEglContext)
+ {
+ eglHelper.Finish();
+ haveEglContext = false;
+ threadManager.ReleaseEglContextLocked(this);
+ }
+ }
+
+ private void GuardedRun()
+ {
+ eglHelper = new EglHelper(textureViewWeakRef);
+ haveEglContext = false;
+ haveEglSurface = false;
+ try
+ {
+ var createEglContext = false;
+ var createEglSurface = false;
+ var createGlInterface = false;
+ var lostEglContext = false;
+ var sizeChanged = false;
+ var wantRenderNotification = false;
+ var doRenderNotification = false;
+ var askedToReleaseEglContext = false;
+ var w = 0;
+ var h = 0;
+ Java.Lang.IRunnable ev = null;
+
+ while (true)
+ {
+ lock (threadManager)
+ {
+ while (true)
+ {
+ if (shouldExit)
+ {
+ return;
+ }
+
+ if (eventQueue.Count > 0)
+ {
+ ev = eventQueue.Dequeue();
+ break;
+ }
+
+ // Update the pause state.
+ var pausing = false;
+ if (paused != requestPaused)
+ {
+ pausing = requestPaused;
+ paused = requestPaused;
+ Monitor.PulseAll(threadManager);
+
+ LogDebug($"[GLThread {Id}] paused is now {paused}");
+ }
+
+ // Do we need to give up the EGL context?
+ if (shouldReleaseEglContext)
+ {
+ LogDebug($"[GLThread {Id}] Releasing EGL context because asked to");
+
+ StopEglSurfaceLocked();
+ StopEglContextLocked();
+ shouldReleaseEglContext = false;
+ askedToReleaseEglContext = true;
+ }
+
+ // Have we lost the EGL context?
+ if (lostEglContext)
+ {
+ StopEglSurfaceLocked();
+ StopEglContextLocked();
+ lostEglContext = false;
+ }
+
+ // When pausing, release the EGL surface:
+ if (pausing && haveEglSurface)
+ {
+ LogDebug($"[GLThread {Id}] Releasing EGL surface because paused");
+
+ StopEglSurfaceLocked();
+ }
+
+ // When pausing, optionally release the EGL Context:
+ if (pausing && haveEglContext)
+ {
+ textureViewWeakRef.TryGetTarget(out SkiaGLTextureView view);
+ var preserveEglContextOnPause = view == null ? false : view.PreserveEGLContextOnPause;
+ if (!preserveEglContextOnPause || threadManager.ShouldReleaseEGLContextWhenPausing())
+ {
+ StopEglContextLocked();
+
+ LogDebug($"[GLThread {Id}] Releasing EGL context because paused");
+ }
+ }
+
+ // When pausing, optionally terminate EGL:
+ if (pausing)
+ {
+ if (threadManager.ShouldTerminateEGLWhenPausing())
+ {
+ eglHelper.Finish();
+
+ LogDebug($"[GLThread {Id}] Terminating EGL because paused");
+ }
+ }
+
+ // Have we lost the TextureView surface?
+ if ((!hasSurface) && (!waitingForSurface))
+ {
+ LogDebug($"[GLThread {Id}] Noticed TextureView surface lost");
+
+ if (haveEglSurface)
+ {
+ StopEglSurfaceLocked();
+ }
+ waitingForSurface = true;
+ surfaceIsBad = false;
+ Monitor.PulseAll(threadManager);
+ }
+
+ // Have we acquired the surface view surface?
+ if (hasSurface && waitingForSurface)
+ {
+ LogDebug($"[GLThread {Id}] Noticed TextureView surface acquired");
+
+ waitingForSurface = false;
+ Monitor.PulseAll(threadManager);
+ }
+
+ if (doRenderNotification)
+ {
+ LogDebug($"[GLThread {Id}] Sending render notification");
+
+ wantRenderNotification = false;
+ doRenderNotification = false;
+ renderComplete = true;
+ Monitor.PulseAll(threadManager);
+ }
+
+ // Ready to draw?
+ if (IsReadyToDraw())
+ // https://stackoverflow.com/questions/67513816/xamarin-android-jni-error-accessed-deleted-global-0x000000
+ // crashing somewhere down here
+ {
+ // If we don't have an EGL context, try to acquire one.
+ if (!haveEglContext)
+ {
+ if (askedToReleaseEglContext)
+ {
+ askedToReleaseEglContext = false;
+ }
+ else
+ if (threadManager.TryAcquireEglContextLocked(this))
+ {
+ try
+ {
+ eglHelper.Start();
+ }
+ catch (Exception)
+ {
+ threadManager.ReleaseEglContextLocked(this);
+ throw;
+ }
+ haveEglContext = true;
+ createEglContext = true;
+
+ Monitor.PulseAll(threadManager);
+ }
+ }
+
+ if (haveEglContext && !haveEglSurface)
+ {
+ haveEglSurface = true;
+ createEglSurface = true;
+ createGlInterface = true;
+ sizeChanged = true;
+ }
+
+ if (haveEglSurface)
+ {
+ if (surfaceSizeChanged)
+ {
+ sizeChanged = true;
+ w = width;
+ h = height;
+ wantRenderNotification = true;
+
+ LogDebug($"[GLThread {Id}] Noticing that we want render notification");
+
+ // Destroy and recreate the EGL surface.
+ createEglSurface = true;
+ surfaceSizeChanged = false;
+ }
+ requestRender = false;
+ Monitor.PulseAll(threadManager);
+ break;
+ }
+ }
+
+ LogDebug($"[GLThread {Id}] Waiting mHaveEglContext={haveEglContext} mHaveEglSurface={haveEglSurface} mFinishedCreatingEglSurface={finishedCreatingEglSurface} paused={paused} hasSurface={hasSurface} surfaceIsBad={surfaceIsBad} mWaitingForSurface={waitingForSurface} mWidth={width} mHeight={height} mRequestRender={requestRender} mRenderMode={renderMode}");
+
+ // By design, this is the only place in a GLThread thread where we Wait().
+ Monitor.Wait(threadManager);
+ }
+ } // end of lock(sGLThreadManager)
+
+ if (ev != null)
+ {
+ ev.Run();
+ ev = null;
+ continue;
+ }
+
+ if (createEglSurface)
+ {
+ LogDebug($"[GLThread {Id}] EGL create surface");
+
+ if (eglHelper.CreateSurface())
+ {
+ lock (threadManager)
+ {
+ finishedCreatingEglSurface = true;
+ Monitor.PulseAll(threadManager);
+ }
+ }
+ else
+ {
+ lock (threadManager)
+ {
+ finishedCreatingEglSurface = true;
+ surfaceIsBad = true;
+ Monitor.PulseAll(threadManager);
+ }
+ continue;
+ }
+ createEglSurface = false;
+ }
+
+ if (createGlInterface)
+ {
+ threadManager.CheckGLDriver();
+ createGlInterface = false;
+ }
+
+ if (createEglContext)
+ {
+ LogDebug($"[GLThread {Id}] OnSurfaceCreated");
+
+ if (textureViewWeakRef.TryGetTarget(out SkiaGLTextureView view))
+ {
+ view.renderer.OnSurfaceCreated(eglHelper.EglConfig);
+ }
+ createEglContext = false;
+ }
+
+ if (sizeChanged)
+ {
+ LogDebug($"[GLThread {Id}] OnSurfaceChanged({w}, {h})");
+
+ if (textureViewWeakRef.TryGetTarget(out SkiaGLTextureView view))
+ {
+ view.renderer.OnSurfaceChanged(w, h);
+ }
+ sizeChanged = false;
+ }
+
+ {
+ LogDebug($"[GLThread {Id}] OnDrawFrame");
+
+ if (textureViewWeakRef.TryGetTarget(out SkiaGLTextureView view))
+ {
+ view.renderer.OnDrawFrame();
+ }
+ }
+
+ var swapError = eglHelper.Swap();
+ switch (swapError)
+ {
+ case EGL14.EglSuccess:
+ break;
+ case EGL11.EglContextLost:
+ LogDebug($"[GLThread {Id}] EGL context lost");
+ lostEglContext = true;
+ break;
+ default:
+ // Other errors typically mean that the current surface is bad,
+ // probably because the TextureView surface has been destroyed,
+ // but we haven't been notified yet.
+ LogError($"[GLThread {Id}] eglSwapBuffers failed: {swapError}");
+
+ lock (threadManager)
+ {
+ surfaceIsBad = true;
+ Monitor.PulseAll(threadManager);
+ }
+ break;
+ }
+
+ if (wantRenderNotification)
+ {
+ doRenderNotification = true;
+ }
+ }
+ }
+ finally
+ {
+ lock (threadManager)
+ {
+ StopEglSurfaceLocked();
+ StopEglContextLocked();
+ }
+ }
+ }
+
+ public bool IsAbleToDraw()
+ {
+ return haveEglContext && haveEglSurface && IsReadyToDraw();
+ }
+
+ private bool IsReadyToDraw()
+ {
+ return (!paused) && hasSurface && (!surfaceIsBad) && (width > 0) && (height > 0) && (requestRender || (renderMode == Rendermode.Continuously));
+ }
+
+ public void SetRenderMode(Rendermode mode)
+ {
+ lock (threadManager)
+ {
+ renderMode = mode;
+ Monitor.PulseAll(threadManager);
+ }
+ }
+
+ public Rendermode GetRenderMode()
+ {
+ lock (threadManager)
+ {
+ return renderMode;
+ }
+ }
+
+ public void RequestRender()
+ {
+ lock (threadManager)
+ {
+ requestRender = true;
+ Monitor.PulseAll(threadManager);
+ }
+ }
+
+ public void OnSurfaceCreated()
+ {
+ lock (threadManager)
+ {
+ LogDebug($"[GLThread {Id}] OnSurfaceCreated");
+
+ hasSurface = true;
+ finishedCreatingEglSurface = false;
+ Monitor.PulseAll(threadManager);
+ while (waitingForSurface && !finishedCreatingEglSurface && !exited)
+ {
+ try
+ {
+ Monitor.Wait(threadManager);
+ }
+ catch (Exception)
+ {
+ Thread.CurrentThread.Interrupt();
+ }
+ }
+ }
+ }
+
+ public void OnSurfaceDestroyed()
+ {
+ lock (threadManager)
+ {
+ LogDebug($"[GLThread {Id}] OnSurfaceDestroyed");
+
+ hasSurface = false;
+ Monitor.PulseAll(threadManager);
+ while ((!waitingForSurface) && (!exited))
+ {
+ try
+ {
+ Monitor.Wait(threadManager);
+ }
+ catch (Exception)
+ {
+ Thread.CurrentThread.Interrupt();
+ }
+ }
+ }
+ }
+
+ public void OnPause()
+ {
+ lock (threadManager)
+ {
+ LogDebug($"[GLThread {Id}] OnPause");
+
+ requestPaused = true;
+ Monitor.PulseAll(threadManager);
+
+ while ((!exited) && (!paused))
+ {
+ LogDebug($"[GLThread {Id}] OnPause: Waiting for paused==True");
+
+ try
+ {
+ Monitor.Wait(threadManager);
+ }
+ catch (Exception)
+ {
+ Thread.CurrentThread.Interrupt();
+ }
+ }
+ }
+ }
+
+ public void OnResume()
+ {
+ lock (threadManager)
+ {
+ LogDebug($"[GLThread {Id}] OnResume");
+
+ requestPaused = false;
+ requestRender = true;
+ renderComplete = false;
+ Monitor.PulseAll(threadManager);
+ while ((!exited) && paused && (!renderComplete))
+ {
+ LogDebug($"[GLThread {Id}] OnResume: Waiting for paused==False");
+
+ try
+ {
+ Monitor.Wait(threadManager);
+ }
+ catch (Exception)
+ {
+ Thread.CurrentThread.Interrupt();
+ }
+ }
+ }
+ }
+
+ public void OnWindowResize(int w, int h)
+ {
+ lock (threadManager)
+ {
+ width = w;
+ height = h;
+ surfaceSizeChanged = true;
+ requestRender = true;
+ renderComplete = false;
+ Monitor.PulseAll(threadManager);
+
+ // Wait for thread to react to resize and render a frame
+ while (!exited && !paused && !renderComplete && IsAbleToDraw())
+ {
+ LogDebug($"[GLThread {Id}] OnWindowResize: Waiting for render complete");
+
+ try
+ {
+ Monitor.Wait(threadManager);
+ }
+ catch (Exception)
+ {
+ Thread.CurrentThread.Interrupt();
+ }
+ }
+ }
+ }
+
+ public void RequestExitAndWait()
+ {
+ // don't call this from GLThread thread or it is a guaranteed deadlock!
+ lock (threadManager)
+ {
+ shouldExit = true;
+ Monitor.PulseAll(threadManager);
+ while (!exited)
+ {
+ try
+ {
+ Monitor.Wait(threadManager);
+ }
+ catch (Exception)
+ {
+ Thread.CurrentThread.Interrupt();
+ }
+ }
+ }
+ }
+
+ public void RequestReleaseEglContextLocked()
+ {
+ shouldReleaseEglContext = true;
+ Monitor.PulseAll(threadManager);
+ }
+
+ public void QueueEvent(Java.Lang.IRunnable r)
+ {
+ if (r == null)
+ {
+ throw new ArgumentNullException(nameof(r));
+ }
+
+ lock (threadManager)
+ {
+ eventQueue.Enqueue(r);
+ Monitor.PulseAll(threadManager);
+ }
+ }
+ }
+
+ private class LogWriter : Java.IO.Writer
+ {
+ private Java.Lang.StringBuilder builder = new Java.Lang.StringBuilder();
+
+ public override void Close()
+ {
+ FlushBuilder();
+ }
+
+ public override void Flush()
+ {
+ FlushBuilder();
+ }
+
+ public override void Write(char[] buf, int offset, int count)
+ {
+ for (var i = 0; i < count; i++)
+ {
+ var c = buf[offset + i];
+ if (c == '\n')
+ {
+ FlushBuilder();
+ }
+ else
+ {
+ builder.Append(c);
+ }
+ }
+ }
+
+ private void FlushBuilder()
+ {
+ if (builder.Length() > 0)
+ {
+ LogDebug($"[LogWriter] {builder.ToString()}");
+ builder.Delete(0, builder.Length());
+ }
+ }
+ }
+
+ private class GLThreadManager
+ {
+ private bool glesVersionCheckComplete;
+ private int glesVersion;
+ private bool glesDriverCheckComplete;
+ private bool multipleGLESContextsAllowed;
+ private bool limitedGLESContexts;
+ private GLThread eglOwner;
+
+ public void ThreadExiting(GLThread thread)
+ {
+ lock (this)
+ {
+ LogDebug($"[GLThreadManager] ThreadExiting: tid = '{eglOwner?.Id}'");
+
+ thread.exited = true;
+ if (eglOwner == thread)
+ {
+ eglOwner = null;
+ }
+ Monitor.PulseAll(this);
+ }
+ }
+
+ public bool TryAcquireEglContextLocked(GLThread thread)
+ {
+ if (eglOwner == thread || eglOwner == null)
+ {
+ eglOwner = thread;
+ Monitor.PulseAll(this);
+ return true;
+ }
+ CheckGLESVersion();
+ if (multipleGLESContextsAllowed)
+ {
+ return true;
+ }
+ // Notify the owning thread that it should release the context.
+ if (eglOwner != null)
+ {
+ eglOwner.RequestReleaseEglContextLocked();
+ }
+ return false;
+ }
+
+ public void ReleaseEglContextLocked(GLThread thread)
+ {
+ if (eglOwner == thread)
+ {
+ eglOwner = null;
+ }
+ Monitor.PulseAll(this);
+ }
+
+ public bool ShouldReleaseEGLContextWhenPausing()
+ {
+ lock (this)
+ {
+ // Release the EGL context when pausing even if
+ // the hardware supports multiple EGL contexts.
+ // Otherwise the device could run out of EGL contexts.
+ return limitedGLESContexts;
+ }
+ }
+
+ public bool ShouldTerminateEGLWhenPausing()
+ {
+ lock (this)
+ {
+ CheckGLESVersion();
+ return !multipleGLESContextsAllowed;
+ }
+ }
+
+ public void CheckGLDriver()
+ {
+ lock (this)
+ {
+ if (!glesDriverCheckComplete)
+ {
+ CheckGLESVersion();
+ var renderer = GLES10.GlGetString(GLES10.GlRenderer);
+ if (glesVersion < EglHelper.GLES_20)
+ {
+ multipleGLESContextsAllowed = !renderer.StartsWith(EglHelper.MSM7K_RENDERER_PREFIX);
+ Monitor.PulseAll(this);
+ }
+ limitedGLESContexts = !multipleGLESContextsAllowed;
+
+ LogDebug($"[GLThreadManager] CheckGLDriver: renderer = '{renderer}' multipleContextsAllowed = '{multipleGLESContextsAllowed}' mLimitedGLESContexts = '{limitedGLESContexts}'");
+
+ glesDriverCheckComplete = true;
+ }
+ }
+ }
+
+ private void CheckGLESVersion()
+ {
+ // This check was required for some pre-Android-3.0 hardware. Android 3.0 provides
+ // support for hardware-accelerated views, therefore multiple EGL contexts are
+ // supported on all Android 3.0+ EGL drivers.
+ if (!glesVersionCheckComplete)
+ {
+ // SystemProperties.getInt("ro.opengles.version", ConfigurationInfo.GL_ES_VERSION_UNDEFINED)
+ var activityManager = ActivityManager.FromContext(Application.Context);
+ var configInfo = activityManager.DeviceConfigurationInfo;
+ if (configInfo.ReqGlEsVersion != ConfigurationInfo.GlEsVersionUndefined)
+ {
+ glesVersion = configInfo.ReqGlEsVersion;
+ }
+ else
+ {
+ glesVersion = 1 << 16; // Lack of property means OpenGL ES version 1
+ }
+ if (glesVersion >= EglHelper.GLES_20)
+ {
+ multipleGLESContextsAllowed = true;
+ }
+ glesVersionCheckComplete = true;
+ }
+ }
+ }
+
+
+ private class EglHelper
+ {
+ public const int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+ public const int EGL_OPENGL_ES2_BIT = 4;
+ public const string MSM7K_RENDERER_PREFIX = "Q3Dimension MSM7500 ";
+ public const int GLES_20 = 0x20000;
+
+ private WeakReference textureViewWeakRef;
+ private EGLDisplay eglDisplay;
+ private EGLSurface eglSurface;
+ private EGLContext eglContext;
+ private EGLConfig eglConfig;
+
+ public EglHelper(WeakReference glTextureViewWeakRef)
+ {
+ textureViewWeakRef = glTextureViewWeakRef;
+ }
+
+ public EGLConfig EglConfig => eglConfig;
+
+ private int CurrentThreadId => Thread.CurrentThread.ManagedThreadId;
+
+ public void Start()
+ {
+ LogDebug($"[GLThread {CurrentThreadId}][EglHelper] Start");
+
+
+ // Get to the default display.
+ eglDisplay = EGL14.EglGetDisplay(EGL14.EglDefaultDisplay);
+
+ if (eglDisplay == EGL14.EglNoDisplay)
+ {
+ throw new Exception("eglGetDisplay failed");
+ }
+
+ // We can now initialize EGL for that display
+ var versionMaj = new int[2];
+ var versionMin = new int[2];
+ if (!EGL14.EglInitialize(eglDisplay, versionMaj, 0, versionMin, 0))
+ {
+ throw new Exception("eglInitialize failed");
+ }
+ if (!textureViewWeakRef.TryGetTarget(out SkiaGLTextureView view))
+ {
+ eglConfig = null;
+ eglContext = null;
+ }
+ else
+ {
+ eglConfig = view.eglConfigChooser.ChooseConfig(eglDisplay);
+ // Create an EGL context. We want to do this as rarely as we can, because an
+ // EGL context is a somewhat heavy object.
+ eglContext = view.eglContextFactory.CreateContext(eglDisplay, eglConfig);
+ }
+ if (eglContext == null || eglContext == EGL14.EglNoContext)
+ {
+ eglContext = null;
+
+ var error = EGL14.EglGetError();
+ LogError($"[GLThread {CurrentThreadId}][EglHelper] createContext failed: {error}");
+ throw new Exception($"createContext failed: {error}");
+ }
+
+ LogDebug($"[GLThread {CurrentThreadId}][EglHelper] createContext {eglContext}");
+
+ eglSurface = null;
+ }
+
+ public bool CreateSurface()
+ {
+ LogDebug($"[GLThread {CurrentThreadId}][EglHelper] CreateSurface");
+
+ if (eglDisplay == null)
+ {
+ throw new Exception("eglDisplay not initialized");
+ }
+ if (eglConfig == null)
+ {
+ throw new Exception("mEglConfig not initialized");
+ }
+ // The window size has changed, so we need to create a new surface.
+ DestroySurfaceImpl();
+
+ // Create an EGL surface we can render into.
+ if (textureViewWeakRef.TryGetTarget(out SkiaGLTextureView view))
+ {
+ eglSurface = view.eglWindowSurfaceFactory.CreateWindowSurface(eglDisplay, eglConfig, view.SurfaceTexture);
+ }
+ else
+ {
+ eglSurface = null;
+ }
+ if (eglSurface == null || eglSurface == EGL14.EglNoSurface)
+ {
+ var error = EGL14.EglGetError();
+ if (error == EGL14.EglBadNativeWindow)
+ {
+ LogError($"[GLThread {CurrentThreadId}][EglHelper] createWindowSurface returned EGL_BAD_NATIVE_WINDOW");
+ }
+ return false;
+ }
+ // Before we can issue IGL commands, we need to make sure the context is
+ // current and bound to a surface.
+ if (!EGL14.EglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext))
+ {
+ // Could not make the context current, probably because the underlying
+ // TextureView surface has been destroyed.
+ LogError($"[GLThread {CurrentThreadId}][EglHelper] eglMakeCurrent failed: {EGL14.EglGetError()}");
+ return false;
+ }
+
+ return true;
+ }
+
+
+ public int Swap()
+ {
+ //GLES20.GlFlush();
+
+ //VSync ON
+ EGL14.EglSwapInterval(eglDisplay, 1);
+
+ if (!EGL14.EglSwapBuffers(eglDisplay, eglSurface))
+ {
+ return EGL14.EglGetError();
+ }
+ return EGL14.EglSuccess;
+ }
+
+ public void DestroySurface()
+ {
+ LogDebug($"[GLThread {CurrentThreadId}][EglHelper] DestroySurface");
+
+ if (textureViewWeakRef.TryGetTarget(out SkiaGLTextureView view))
+ {
+ view.renderer.OnSurfaceDestroyed();
+ }
+ DestroySurfaceImpl();
+ }
+
+ private void DestroySurfaceImpl()
+ {
+ if (eglSurface != null && eglSurface != EGL14.EglNoSurface)
+ {
+ EGL14.EglMakeCurrent(eglDisplay, EGL14.EglNoSurface, EGL14.EglNoSurface, EGL14.EglNoContext);
+ if (textureViewWeakRef.TryGetTarget(out SkiaGLTextureView view))
+ {
+ view.eglWindowSurfaceFactory.DestroySurface(eglDisplay, eglSurface);
+ }
+ eglSurface = null;
+ }
+ }
+
+ public void Finish()
+ {
+ LogDebug($"[GLThread {CurrentThreadId}][EglHelper] Finish");
+
+ if (eglContext != null)
+ {
+ if (textureViewWeakRef.TryGetTarget(out SkiaGLTextureView view))
+ {
+ view.eglContextFactory.DestroyContext(eglDisplay, eglContext);
+ }
+ eglContext = null;
+ }
+ if (eglDisplay != null)
+ {
+ EGL14.EglTerminate(eglDisplay);
+ eglDisplay = null;
+ }
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/Engine/Platforms/Android/Super.Android.cs b/src/Engine/Platforms/Android/Super.Android.cs
index 894b3980..78db147f 100644
--- a/src/Engine/Platforms/Android/Super.Android.cs
+++ b/src/Engine/Platforms/Android/Super.Android.cs
@@ -14,9 +14,12 @@ public partial class Super
public static Android.App.Activity MainActivity { get; set; }
private static FrameCallback _frameCallback;
+
static bool _loopStarting = false;
static bool _loopStarted = false;
- public static event EventHandler ChoreographerCallback;
+
+ public static event EventHandler OnFrame;
+ static Looper Looper { get; set; }
public static void Init(Android.App.Activity activity)
{
@@ -48,36 +51,61 @@ public static void Init(Android.App.Activity activity)
VisualDiagnostics.VisualTreeChanged += OnVisualTreeChanged;
+ bool isRendering = false;
+ object lockFrane = new();
+
+ /*
Tasks.StartDelayed(TimeSpan.FromMilliseconds(250), async () =>
{
_frameCallback = new FrameCallback((nanos) =>
{
- ChoreographerCallback?.Invoke(null, null);
+ if (isRendering)
+ return;
+ isRendering = true;
+ OnFrame?.Invoke(null, null);
Choreographer.Instance.PostFrameCallback(_frameCallback);
+ isRendering = false;
});
while (!_loopStarted)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
- if (_loopStarting)
- return;
- _loopStarting = true;
-
- if (MainThread.IsMainThread) //Choreographer is available
+ lock (lockFrane)
{
- if (!_loopStarted)
+ if (_loopStarting)
+ return;
+
+ _loopStarting = true;
+
+ if (MainThread.IsMainThread) // Choreographer is available
{
- _loopStarted = true;
- Choreographer.Instance.PostFrameCallback(_frameCallback);
+ if (!_loopStarted)
+ {
+ _loopStarted = true;
+ Choreographer.Instance.PostFrameCallback(_frameCallback);
+ }
}
+
+ _loopStarting = false;
}
- _loopStarting = false;
});
+
+ if (_loopStarted)
+ break;
+
await Task.Delay(100);
}
+
+ });
+ */
+
+ Looper = new(() =>
+ {
+ OnFrame?.Invoke(null, null);
});
+ Looper.StartOnMainThread(120);
}
diff --git a/src/Engine/Platforms/MacCatalyst/Files.Mac.cs b/src/Engine/Platforms/MacCatalyst/Files.Mac.cs
index d0c6bc10..fa342a74 100644
--- a/src/Engine/Platforms/MacCatalyst/Files.Mac.cs
+++ b/src/Engine/Platforms/MacCatalyst/Files.Mac.cs
@@ -1,4 +1,6 @@
-namespace DrawnUi.Maui.Infrastructure
+using Foundation;
+
+namespace DrawnUi.Maui.Infrastructure
{
public partial class Files
{
@@ -7,6 +9,23 @@ public static void RefreshSystem(FileDescriptor file)
}
+ public static List ListAssets(string subfolder)
+ {
+ NSBundle mainBundle = NSBundle.MainBundle;
+ string resourcesPath = mainBundle.ResourcePath;
+ string subfolderPath = Path.Combine(resourcesPath, subfolder);
+
+ if (Directory.Exists(subfolderPath))
+ {
+ string[] files = Directory.GetFiles(subfolderPath);
+ return files.Select(Path.GetFileName).ToList();
+ }
+ else
+ {
+ return new List();
+ }
+ }
+
public static string GetPublicDirectory()
{
return FileSystem.Current.AppDataDirectory;
diff --git a/src/Engine/Platforms/Windows/DrawnView.Windows.cs b/src/Engine/Platforms/Windows/DrawnView.Windows.cs
index 70e5fe7e..d86dd64f 100644
--- a/src/Engine/Platforms/Windows/DrawnView.Windows.cs
+++ b/src/Engine/Platforms/Windows/DrawnView.Windows.cs
@@ -1,5 +1,6 @@
-using Microsoft.Maui.Platform;
-using Microsoft.UI.Xaml;
+using Microsoft.Maui.Controls.PlatformConfiguration;
+using Microsoft.Maui.Platform;
+using Microsoft.Maui.Platform;
using System.Runtime.CompilerServices;
using Visibility = Microsoft.UI.Xaml.Visibility;
@@ -38,8 +39,10 @@ protected virtual void OnSizeChanged()
public virtual void SetupRenderingLoop()
{
+#if !LEGACY
Super.OnFrame -= OnSuperFrame;
Super.OnFrame += OnSuperFrame;
+#endif
}
protected virtual void PlatformHardwareAccelerationChanged()
@@ -47,6 +50,37 @@ protected virtual void PlatformHardwareAccelerationChanged()
}
+
+
+
+#if LEGACY
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool CheckCanDraw()
+ {
+ if (UpdateLocked && StopDrawingWhenUpdateIsLocked)
+ return false;
+
+ return CanvasView != null
+ && !IsRendering
+ && IsDirty
+ && IsVisible;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void UpdatePlatform()
+ {
+ IsDirty = true;
+ if (!OrderedDraw && CheckCanDraw())
+ {
+ OrderedDraw = true;
+ InvalidateCanvas();
+ }
+ }
+
+
+
+#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void UpdatePlatform()
{
@@ -65,6 +99,8 @@ public bool CheckCanDraw()
&& IsVisible && Super.EnableRendering;
}
+#endif
+
private long test;
private void OnSuperFrame(object sender, EventArgs e)
{
diff --git a/src/Engine/Platforms/Windows/Files.Windows.cs b/src/Engine/Platforms/Windows/Files.Windows.cs
index 8c5ccbd0..317e454c 100644
--- a/src/Engine/Platforms/Windows/Files.Windows.cs
+++ b/src/Engine/Platforms/Windows/Files.Windows.cs
@@ -1,4 +1,6 @@
-namespace DrawnUi.Maui.Infrastructure
+using Windows.Storage;
+
+namespace DrawnUi.Maui.Infrastructure
{
public partial class Files
{
@@ -12,6 +14,14 @@ public static string GetPublicDirectory()
return Windows.Storage.KnownFolders.DocumentsLibrary.Path;
}
+ public static List ListAssets(string sub)
+ {
+ StorageFolder installFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
+ StorageFolder subfolder = installFolder.GetFolderAsync(sub).GetAwaiter().GetResult();
+ IReadOnlyList files = subfolder.GetFilesAsync().GetAwaiter().GetResult();
+ return files.Select(f => f.Name).ToList();
+ }
+
public static void Share(string message, IEnumerable fullFilenames)
{
MainThread.BeginInvokeOnMainThread(async () =>
diff --git a/src/Engine/Platforms/Windows/Super.Windows.cs b/src/Engine/Platforms/Windows/Super.Windows.cs
index d990d603..3594018e 100644
--- a/src/Engine/Platforms/Windows/Super.Windows.cs
+++ b/src/Engine/Platforms/Windows/Super.Windows.cs
@@ -33,7 +33,7 @@ public static void Init()
static Looper Looper { get; set; }
- public static EventHandler OnFrame;
+ public static event EventHandler OnFrame;
///
/// Opens web link in native browser
@@ -56,13 +56,13 @@ public static void OpenLink(string link)
///
///
///
- public static IEnumerable ListAssets(string subfolder)
+ public static List ListAssets(string subfolder)
{
StorageFolder installFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
StorageFolder sub = installFolder.GetFolderAsync(subfolder).GetAwaiter().GetResult();
IReadOnlyList files = sub.GetFilesAsync().GetAwaiter().GetResult();
- return files.Select(f => f.Name);
+ return files.Select(f => f.Name).ToList();
}
}
diff --git a/src/Engine/Platforms/iOS/Files.iOS.cs b/src/Engine/Platforms/iOS/Files.iOS.cs
index d0c6bc10..fa342a74 100644
--- a/src/Engine/Platforms/iOS/Files.iOS.cs
+++ b/src/Engine/Platforms/iOS/Files.iOS.cs
@@ -1,4 +1,6 @@
-namespace DrawnUi.Maui.Infrastructure
+using Foundation;
+
+namespace DrawnUi.Maui.Infrastructure
{
public partial class Files
{
@@ -7,6 +9,23 @@ public static void RefreshSystem(FileDescriptor file)
}
+ public static List ListAssets(string subfolder)
+ {
+ NSBundle mainBundle = NSBundle.MainBundle;
+ string resourcesPath = mainBundle.ResourcePath;
+ string subfolderPath = Path.Combine(resourcesPath, subfolder);
+
+ if (Directory.Exists(subfolderPath))
+ {
+ string[] files = Directory.GetFiles(subfolderPath);
+ return files.Select(Path.GetFileName).ToList();
+ }
+ else
+ {
+ return new List();
+ }
+ }
+
public static string GetPublicDirectory()
{
return FileSystem.Current.AppDataDirectory;
diff --git a/src/Engine/Views/DrawnView.cs b/src/Engine/Views/DrawnView.cs
index 12ce5154..80b9f429 100644
--- a/src/Engine/Views/DrawnView.cs
+++ b/src/Engine/Views/DrawnView.cs
@@ -11,6 +11,14 @@ namespace DrawnUi.Maui.Views
[ContentProperty("Children")]
public partial class DrawnView : ContentView, IDrawnBase, IAnimatorsManager, IVisualTreeElement
{
+
+ public class DiagnosticData
+ {
+ public int LayersSaved { get; set; }
+ }
+
+ public DiagnosticData Diagnostics = new();
+
public virtual void Update()
{
if (!Super.EnableRendering)
@@ -1049,9 +1057,10 @@ public DrawnView()
public Action Clipping { get; set; }
- public virtual SKPath CreateClip(object arguments, bool usePosition)
+ public virtual SKPath CreateClip(object arguments, bool usePosition, SKPath path = null)
{
- var path = new SKPath();
+ path ??= new SKPath();
+
if (usePosition)
{
path.AddRect(DrawingRect);
@@ -1589,20 +1598,11 @@ protected virtual void Draw(SkiaDrawingContext context, SKRect destination, floa
{
++renderedFrames;
-
- //if (CanvasView is SkiaViewAccelerated accelerated)
- //{
- // var c = accelerated.GRContext;
- // Console.WriteLine($"[FRAME] {++renderedFrames} {c} {destination.Width}x{destination.Height} at {scale}");
- //}
+ //Debug.WriteLine($"[DRAW] {Tag}");
DisposeDisposables();
- //Trace.WriteLine($"[1] {destination.Width}x{destination.Height} at {scale}");
-
- if (IsDisposed || UpdateLocked
- //|| Super.StopRenderingInBackground
- )
+ if (IsDisposed || UpdateLocked)
{
return;
}
@@ -1625,7 +1625,6 @@ protected virtual void Draw(SkiaDrawingContext context, SKRect destination, floa
DrawingThreads++;
FrameTime = CanvasView.FrameTime;
- //context.FrameTimeNanos = FrameTime;
FPS = CanvasFps;
@@ -2416,6 +2415,7 @@ public ISkiaGestureListener FocusedChild
}
ISkiaGestureListener _focusedChild;
private ISkiaDrawable _canvasView;
+ private bool _wasBusy;
///
///
@@ -2464,14 +2464,16 @@ void Continue()
{
if (CanvasView != null)
{
- if (!CanvasView.IsDrawing && CanDraw && !_isWaiting) //passed checks
+ if (CanDraw && !CanvasView.IsDrawing && !_isWaiting) //passed checks //
{
+ _wasBusy = false;
_isWaiting = true;
InvalidatedCanvas++;
MainThread.BeginInvokeOnMainThread(async () =>
{
try
{
+#if !WINDOWS
//cap fps around 120fps
var nowNanos = Super.GetCurrentTimeNanos();
var elapsedMicros = (nowNanos - _lastUpdateTimeNanos) / 1_000.0;
@@ -2479,7 +2481,7 @@ void Continue()
var needWait =
Super.CapMicroSecs
-#if IOS || MACCATALYST
+#if IOS || MACCATALYST
* 2 // apple is double buffered
#endif
- elapsedMicros;
@@ -2490,6 +2492,10 @@ void Continue()
if (ms < 1)
ms = 1;
await Task.Delay(ms);
+#else
+ await Task.Delay(1);
+#endif
+
CanvasView?.Update(); //very rarely could throw on windows here if maui destroys view when navigating, so we secured with try-catch
}
catch (Exception e)
@@ -2499,11 +2505,19 @@ void Continue()
finally
{
_isWaiting = false;
+ if (_wasBusy)
+ {
+ Update();
+ }
}
});
return;
}
+ else
+ {
+ _wasBusy = true;
+ }
}
OrderedDraw = false;
}
diff --git a/src/Engine/Views/SkiaView.cs b/src/Engine/Views/SkiaView.cs
index 70da151c..5e0ee4eb 100644
--- a/src/Engine/Views/SkiaView.cs
+++ b/src/Engine/Views/SkiaView.cs
@@ -131,12 +131,12 @@ private void OnPaintingSurface(object sender, SKPaintSurfaceEventArgs paintArgs)
bool isDirty = OnDraw.Invoke(paintArgs.Surface, new SKRect(0, 0, paintArgs.Info.Width, paintArgs.Info.Height));
#if ANDROID
- if (maybeLowEnd && FPS > 160)
+ if (maybeLowEnd && FPS > 60)
{
maybeLowEnd = false;
}
- if (maybeLowEnd && isDirty && _fps < 55) //kick refresh for low-end devices
+ if (maybeLowEnd && isDirty && _fps < 30) //kick refresh for low-end devices
{
InvalidateSurface();
return;
diff --git a/src/Engine/Views/SkiaViewAccelerated.cs b/src/Engine/Views/SkiaViewAccelerated.cs
index 91677e68..92e74a16 100644
--- a/src/Engine/Views/SkiaViewAccelerated.cs
+++ b/src/Engine/Views/SkiaViewAccelerated.cs
@@ -172,6 +172,7 @@ public void Update(long nanos)
{
_nanos = nanos;
IsDrawing = true;
+
InvalidateSurface();
}
}
@@ -232,12 +233,12 @@ private void OnPaintingSurface(object sender, SKPaintGLSurfaceEventArgs paintArg
var isDirty = OnDraw.Invoke(paintArgs.Surface, rect);
#if ANDROID
- if (maybeLowEnd && FPS > 160)
+ if (maybeLowEnd && FPS > 60)
{
maybeLowEnd = false;
}
- if (maybeLowEnd && isDirty && _fps < 55) //kick refresh for low-end devices
+ if (maybeLowEnd && isDirty && _fps < 30) //kick refresh for low-end devices
{
InvalidateSurface();
return;
diff --git a/src/Engine/skia2.props b/src/Engine/skia2.props
index a8e4088c..4edee202 100644
--- a/src/Engine/skia2.props
+++ b/src/Engine/skia2.props
@@ -1,8 +1,8 @@
-
-
+
+
\ No newline at end of file
diff --git a/src/samples/Sandbox/MainPageDev.xaml b/src/samples/Sandbox/MainPageDev.xaml
index 3d0ee4bb..01732e61 100644
--- a/src/samples/Sandbox/MainPageDev.xaml
+++ b/src/samples/Sandbox/MainPageDev.xaml
@@ -5,14 +5,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Sandbox.Views.Controls"
xmlns:draw="http://schemas.appomobi.com/drawnUi/2023/draw"
- xmlns:mauiNet8="using:MauiNet8"
xmlns:views="clr-namespace:Sandbox.Views"
- xmlns:xaml2Pdf="clr-namespace:Sandbox.Views.Xaml2Pdf"
+ x:Name="ThisPage"
BackgroundColor="Black">
-
-
-
-
-
-
-
-
+
+
+
+ Tag="Content"
+ Type="Column">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
+
_shaders;
+
public MainPageDev()
{
try
@@ -39,6 +43,8 @@ public MainPageDev()
InitializeComponent();
Test();
+
+ _shaders = Files.ListAssets(path);
}
catch (Exception e)
{
@@ -48,7 +54,84 @@ public MainPageDev()
void Test()
{
- // string shaderCode = SkSl.LoadFromResources($"{MauiProgram.ShadersFolder}/apple.sksl");
+ // string shaderCode = SkSl.LoadFromResources($"{MauiProgram.ShadersFolder}/apple.sksl");
//var effect = SkSl.Compile(shaderCode);
}
+
+ async void SelectFIle()
+ {
+ if (_shaders.Count > 1)
+ {
+ var options = _shaders.Select(name => new SelectableAction
+ {
+ Action = async () =>
+ {
+ ShaderFile = name;
+ },
+ Title = name
+ }).ToList();
+ var selected = await PresentSelection(options, "Select Shader") as SelectableAction;
+ selected?.Action();
+ }
+ }
+
+ public async Task PresentSelection(IEnumerable options,
+ string title = null, string cancel = null)
+ {
+ if (string.IsNullOrEmpty(title))
+ title = "Select";
+
+ if (string.IsNullOrEmpty(cancel))
+ cancel = "Cancel";
+
+ var result = await App.Current.MainPage.DisplayActionSheet(title, cancel,
+ null, options.Select(x => x.Title).ToArray()
+ );
+
+ if (string.IsNullOrEmpty(result))
+ {
+ return null; //cancel
+ }
+
+ var selected = options.FirstOrDefault(x => x.Title == result);
+ return selected;
+ }
+
+ private void SkiaButton_OnTapped(object sender, SkiaGesturesParameters e)
+ {
+ MainThread.BeginInvokeOnMainThread(SelectFIle);
+ }
+
+ private string path = @"Shaders\transitions";
+
+ public string FullShaderPath
+ {
+ get
+ {
+ return $"{path}\\{ShaderFile}";
+ }
+ set
+ {
+
+ }
+ }
+
+ private string _ShaderFile = "dreamy.sksl";
+ public string ShaderFile
+ {
+ get
+ {
+ return _ShaderFile;
+ }
+ set
+ {
+ if (_ShaderFile != value)
+ {
+ _ShaderFile = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(FullShaderPath));
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/samples/Sandbox/Sandbox.csproj b/src/samples/Sandbox/Sandbox.csproj
index a9edc551..76e99793 100644
--- a/src/samples/Sandbox/Sandbox.csproj
+++ b/src/samples/Sandbox/Sandbox.csproj
@@ -8,7 +8,7 @@
true
true
enable
-
+ true
false
com.companyname.sandbox2
@@ -182,9 +182,9 @@
-
-
-
+
+
+
@@ -284,15 +284,6 @@
-
-
- MSBuild:Compile
-
-
- MSBuild:Compile
-
-
-
diff --git a/src/samples/Sandbox/Views/Controls/ContentFolder.cs b/src/samples/Sandbox/Views/Controls/ContentFolder.cs
index b485bf05..656c8e9d 100644
--- a/src/samples/Sandbox/Views/Controls/ContentFolder.cs
+++ b/src/samples/Sandbox/Views/Controls/ContentFolder.cs
@@ -27,43 +27,43 @@ public double VerticalMargin
set { SetValue(VerticalMarginProperty, value); }
}
- public static readonly BindableProperty BacksideSourceProperty = BindableProperty.Create(
- nameof(BacksideSource),
+ public static readonly BindableProperty SecondarySourceProperty = BindableProperty.Create(
+ nameof(SecondarySource),
typeof(string),
typeof(ContentFolder),
defaultValue: null,
propertyChanged: ApplySourceProperty);
- public string BacksideSource
+ public string SecondarySource
{
- get { return (string)GetValue(BacksideSourceProperty); }
- set { SetValue(BacksideSourceProperty, value); }
+ get { return (string)GetValue(SecondarySourceProperty); }
+ set { SetValue(SecondarySourceProperty, value); }
}
private static void ApplySourceProperty(BindableObject bindable, object oldvalue, object newvalue)
{
if (oldvalue != newvalue && bindable is ContentFolder control)
{
- control.ApplyBacksideSource((string)newvalue);
+ control.ApplySecondarySource((string)newvalue);
}
}
- public void SetBackside(SKImage image)
+ public void SetSecondary(SKImage image)
{
- var dispose = _imageBackside;
- _imageBackside = image;
- if (dispose != _imageBackside)
+ var dispose = _imageSecondary;
+ _imageSecondary = image;
+ if (dispose != _imageSecondary)
dispose?.Dispose();
UpdateTextures();
}
- void ApplyBacksideSource(string source)
+ void ApplySecondarySource(string source)
{
Task.Run(async () =>
{
var background = await LoadSource(source);
- SetBackside(background);
+ SetSecondary(background);
});
}
@@ -73,7 +73,7 @@ protected override void OnLayoutChanged()
{
base.OnLayoutChanged();
- ApplyBacksideSource(this.BacksideSource);
+ ApplySecondarySource(this.SecondarySource);
}
///
@@ -158,7 +158,7 @@ public override void OnDisposing()
_textureFront?.Dispose();
_textureBack?.Dispose();
- _imageBackside?.Dispose();
+ _imageSecondary?.Dispose();
base.OnDisposing();
}
@@ -195,7 +195,7 @@ void BuildTextures(SKImage front, SKImage back)
_textureFront = front.ToShader();
if (back != null)
{
- _textureBack = _imageBackside.ToShader(SKShaderTileMode.Repeat, SKShaderTileMode.Repeat);
+ _textureBack = _imageSecondary.ToShader(SKShaderTileMode.Repeat, SKShaderTileMode.Repeat);
}
}
@@ -240,7 +240,7 @@ void UpdateTextures()
var cache = content.RenderObject;
if (_compiledShader != null && cache is { Image: not null })
{
- BuildTextures(cache.Image, _imageBackside);
+ BuildTextures(cache.Image, _imageSecondary);
}
}
}
@@ -296,7 +296,7 @@ private void DrawContentImage(CachedObject cache, SkiaDrawingContext ctx, SKRect
///
private SKRuntimeEffectChildren _passTextures;
- private SKImage _imageBackside;
+ private SKImage _imageSecondary;
protected Vector2 _offset;
protected Vector2 _origin;
diff --git a/src/samples/Sandbox/Views/Controls/DrawnSlider.xaml b/src/samples/Sandbox/Views/Controls/DrawnSlider.xaml
index d7b13f04..6e4e6dfd 100644
--- a/src/samples/Sandbox/Views/Controls/DrawnSlider.xaml
+++ b/src/samples/Sandbox/Views/Controls/DrawnSlider.xaml
@@ -11,7 +11,7 @@
SliderHeight="35"
Tag="SliderFun"
Type="Column"
- UseCache="Operations">
+ UseCache="ImageDoubleBuffered">
diff --git a/src/samples/Sandbox/Views/Controls/MultiRippleWithTouchEffect.cs b/src/samples/Sandbox/Views/Controls/MultiRippleWithTouchEffect.cs
index 1964fa3d..980344b9 100644
--- a/src/samples/Sandbox/Views/Controls/MultiRippleWithTouchEffect.cs
+++ b/src/samples/Sandbox/Views/Controls/MultiRippleWithTouchEffect.cs
@@ -1,10 +1,9 @@
using AppoMobi.Maui.Gestures;
-using AppoMobi.Specials;
using System.Collections.Concurrent;
namespace Sandbox.Views.Controls;
-public class MultiRippleWithTouchEffect : SkiaShaderEffect, IStateEffect, ISkiaGestureProcessor
+public class MultiRippleWithTouchEffect : ShaderDoubleTexturesEffect, IStateEffect, ISkiaGestureProcessor
{
public MultiRippleWithTouchEffect()
{
@@ -13,9 +12,10 @@ public MultiRippleWithTouchEffect()
bool _initialized;
private PointF _mouse;
- private SkiaControl _controlSource;
- public void UpdateState()
+ #region IStateEffect
+
+ void IStateEffect.UpdateState()
{
if (Parent != null && !_initialized && Parent.IsLayoutReady)
{
@@ -29,9 +29,12 @@ public override void Attach(SkiaControl parent)
{
base.Attach(parent);
- UpdateState();
+ (this as IStateEffect).UpdateState();
}
+ #endregion
+
+
protected override SKRuntimeEffectUniforms CreateUniforms(SKRect destination)
{
var uniforms = base.CreateUniforms(destination);
@@ -68,252 +71,6 @@ protected override SKRuntimeEffectUniforms CreateUniforms(SKRect destination)
return uniforms;
}
- #region REFLECTION
-
- private SKShader _textureBackground;
-
- protected override SKRuntimeEffectChildren CreateTexturesUniforms(SkiaDrawingContext ctx, SKRect destination, SKImage snapshot)
- {
- var texture2 = GetReflectionTexture();
-
- if (snapshot != null && texture2 != null)
- {
- var texture1 = snapshot.ToShader();
-
- return new SKRuntimeEffectChildren(CompiledShader)
- {
- { "iImage1", texture1 }, //background
- { "iImage2", texture2 } //reflection
- };
- }
- else
- {
- return new SKRuntimeEffectChildren(CompiledShader)
- {
- };
- }
- }
-
- private void OnCacheCreatedTo(object sender, CachedObject e)
- {
- Update();
- }
-
- #region ReflectionFromFile
-
- public static readonly BindableProperty BacksideSourceProperty = BindableProperty.Create(
- nameof(BacksideSource),
- typeof(string),
- typeof(MultiRippleWithTouchEffect),
- defaultValue: null,
- propertyChanged: ApplyBacksideSourceProperty);
-
- public string BacksideSource
- {
- get { return (string)GetValue(BacksideSourceProperty); }
- set { SetValue(BacksideSourceProperty, value); }
- }
-
- private static void ApplyBacksideSourceProperty(BindableObject bindable, object oldvalue, object newvalue)
- {
- if (oldvalue != newvalue && bindable is MultiRippleWithTouchEffect control)
- {
- control.ApplyBacksideSource((string)newvalue);
- }
- }
-
- void ApplyBacksideSource(string source)
- {
- Task.Run(async () =>
- {
- await LoadSource(source);
- if (ParentReady() && _loadedReflectionBitmap != null)
- {
- SetBackside();
- }
-
- });
- }
-
- private bool _reflectionSet;
-
- public void SetBackside()
- {
- SKImage image = null;
-
- if (_loadedReflectionBitmap != null)
- {
- var outRect = Parent.DrawingRect;
- var info = new SKImageInfo((int)outRect.Width, (int)outRect.Height);
- var resizedBitmap = new SKBitmap(info);
- using (var canvas = new SKCanvas(resizedBitmap))
- {
- // This will stretch the original image to fill the new size
- var rect = new SKRect(0, 0, (int)outRect.Width, (int)outRect.Height);
- canvas.DrawBitmap(_loadedReflectionBitmap, rect);
- canvas.Flush();
- }
-
- image = SKImage.FromBitmap(resizedBitmap);
- }
-
- var dispose = _textureReflection;
-
- if (image != null)
- {
- _textureReflection = image.ToShader(SKShaderTileMode.Mirror, SKShaderTileMode.Mirror); ;
- }
-
- if (dispose != _textureReflection)
- dispose?.Dispose();
-
- _reflectionSet = true;
-
- Update();
- }
-
- private SKShader _textureReflection;
-
- SKShader GetReflectionTexture()
- {
- if (!_reflectionSet && ParentReady())
- {
- SetBackside();
- }
- return _textureReflection;
- }
-
- //todo move this to helper whatever to share code
-
- protected bool ParentReady()
- {
- return !(Parent == null || Parent.DrawingRect.Width <= 0 || Parent.DrawingRect.Height <= 0);
- }
-
- private SemaphoreSlim _semaphoreLoadFile = new(1, 1);
-
- ///
- /// Loading from local files only
- ///
- ///
- ///
- public async Task LoadSource(string fileName)
- {
- if (string.IsNullOrEmpty(fileName))
- return;
-
- await _semaphoreLoadFile.WaitAsync();
-
- try
- {
- if (fileName.SafeContainsInLower("file://"))
- {
- var fullFilename = fileName.Replace("file://", "", StringComparison.InvariantCultureIgnoreCase);
- using var stream = new FileStream(fullFilename, System.IO.FileMode.Open);
- _loadedReflectionBitmap = SKBitmap.Decode(stream);
- }
- else
- {
- using var stream = await FileSystem.OpenAppPackageFileAsync(fileName);
- _loadedReflectionBitmap = SKBitmap.Decode(stream);
- }
-
- _reflectionSet = false;
- return;
- }
- catch (Exception e)
- {
- Console.WriteLine($"LoadSource failed to load animation {fileName}");
- Console.WriteLine(e);
- return;
- }
- finally
- {
- _semaphoreLoadFile.Release();
- }
- }
-
- protected override void OnDisposing()
- {
- base.OnDisposing();
-
- _textureReflection?.Dispose();
- _loadedReflectionBitmap?.Dispose();
- }
-
- SKBitmap _loadedReflectionBitmap;
-
- #endregion
-
-
- #region ReflectionFromControl
-
-
-
- /*
-
- SKShader GetReflectionTexture()
- {
- if (ReflectionSourceControl == null || ReflectionSourceControl.RenderObject == null)
- {
- return null;
- }
- return ReflectionSourceControl.RenderObject.Image.ToShader(SKShaderTileMode.Mirror, SKShaderTileMode.Mirror);;;
- }
-
- void DetachTo()
- {
- if (_controlSource != null)
- {
- _controlSource.CreatedCache -= OnCacheCreatedTo;
- _controlSource = null;
- }
- }
-
- private static void ApplyReflectionSourceControlProperty(BindableObject bindable, object oldvalue, object newvalue)
- {
- if (oldvalue != newvalue && bindable is MultiRippleWithTouchEffect control)
- {
- control.ApplyReflectionSourceControl(newvalue as SkiaControl);
- }
- }
-
- void ApplyReflectionSourceControl(SkiaControl control)
- {
- if (_controlSource == control)
- return;
-
- DetachTo();
- _controlSource = control;
- if (_controlSource != null)
- {
- _controlSource.CreatedCache += OnCacheCreatedTo;
- }
- }
-
- public static readonly BindableProperty ReflectionSourceControlProperty = BindableProperty.Create(
- nameof(ReflectionSourceControl),
- typeof(SkiaControl), typeof(TestLoopEffect),
- null,
- propertyChanged: ApplyReflectionSourceControlProperty);
-
- public SkiaControl ReflectionSourceControl
- {
- get { return (SkiaControl)GetValue(ReflectionSourceControlProperty); }
- set { SetValue(ReflectionSourceControlProperty, value); }
- }
-
- protected override void OnDisposing()
- {
- base.OnDisposing();
- DetachTo();
- }
- */
-
- #endregion
-
- #endregion
-
#region RIPPLES
public class Ripple
diff --git a/src/samples/Sandbox/Views/Controls/ShaderDoubleTexturesEffect.cs b/src/samples/Sandbox/Views/Controls/ShaderDoubleTexturesEffect.cs
new file mode 100644
index 00000000..9913cd66
--- /dev/null
+++ b/src/samples/Sandbox/Views/Controls/ShaderDoubleTexturesEffect.cs
@@ -0,0 +1,183 @@
+using AppoMobi.Specials;
+
+namespace Sandbox.Views.Controls;
+
+///
+/// Base shader effect class that has 2 input textures
+///
+public class ShaderDoubleTexturesEffect : SkiaShaderEffect
+{
+ protected bool ParentReady()
+ {
+ return !(Parent == null || Parent.DrawingRect.Width <= 0 || Parent.DrawingRect.Height <= 0);
+ }
+
+ #region SecondaryTexture
+
+ #region FromFile
+
+ protected SKShaderTileMode TilingSecondaryTexture = SKShaderTileMode.Mirror;
+
+ protected override SKRuntimeEffectChildren CreateTexturesUniforms(SkiaDrawingContext ctx, SKRect destination, SKShader texture1)
+ {
+ var texture2 = GetSecondaryTexture();
+
+ if (texture1 != null && texture2 != null)
+ {
+ //var texture1 = snapshot.ToShader();
+
+ return new SKRuntimeEffectChildren(CompiledShader)
+ {
+ { "iImage1", texture1 }, //main
+ { "iImage2", texture2 } //secondary
+ };
+ }
+ else
+ {
+ return new SKRuntimeEffectChildren(CompiledShader)
+ {
+ };
+ }
+ }
+
+ public static readonly BindableProperty SecondarySourceProperty = BindableProperty.Create(
+ nameof(SecondarySource),
+ typeof(string),
+ typeof(ShaderDoubleTexturesEffect),
+ defaultValue: null,
+ propertyChanged: ApplySecondarySourceProperty);
+
+ public string SecondarySource
+ {
+ get { return (string)GetValue(SecondarySourceProperty); }
+ set { SetValue(SecondarySourceProperty, value); }
+ }
+
+ private static void ApplySecondarySourceProperty(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ if (oldvalue != newvalue && bindable is ShaderDoubleTexturesEffect control)
+ {
+ control.ApplySecondarySource((string)newvalue);
+ }
+ }
+
+ void ApplySecondarySource(string source)
+ {
+ Task.Run(async () =>
+ {
+ await LoadSource(source);
+ if (ParentReady() && _loadedReflectionBitmap != null)
+ {
+ CompileSecondaryTexture();
+ }
+
+ });
+ }
+
+ private bool _secondarySourceSet;
+
+ public void CompileSecondaryTexture()
+ {
+ SKImage image = null;
+
+ if (_loadedReflectionBitmap != null)
+ {
+ var outRect = Parent.DrawingRect;
+ var info = new SKImageInfo((int)outRect.Width, (int)outRect.Height);
+ var resizedBitmap = new SKBitmap(info);
+ using (var canvas = new SKCanvas(resizedBitmap))
+ {
+ // This will stretch the original image to fill the new size
+ var rect = new SKRect(0, 0, (int)outRect.Width, (int)outRect.Height);
+ canvas.DrawBitmap(_loadedReflectionBitmap, rect);
+ canvas.Flush();
+ }
+
+ image = SKImage.FromBitmap(resizedBitmap);
+ }
+
+ var dispose = SecondaryTexture;
+
+ if (image != null)
+ {
+ SecondaryTexture = image.ToShader(TilingSecondaryTexture, TilingSecondaryTexture);
+ }
+
+ if (dispose != SecondaryTexture)
+ dispose?.Dispose();
+
+ _secondarySourceSet = true;
+
+ Update();
+ }
+
+ protected SKShader SecondaryTexture;
+
+ protected virtual SKShader GetSecondaryTexture()
+ {
+ if (!_secondarySourceSet && ParentReady())
+ {
+ CompileSecondaryTexture();
+ }
+ return SecondaryTexture;
+ }
+
+
+ private SemaphoreSlim _semaphoreLoadFile = new(1, 1);
+
+ ///
+ /// Loading from local files only
+ ///
+ ///
+ ///
+ public async Task LoadSource(string fileName)
+ {
+ if (string.IsNullOrEmpty(fileName))
+ return;
+
+ await _semaphoreLoadFile.WaitAsync();
+
+ try
+ {
+ if (fileName.SafeContainsInLower("file://"))
+ {
+ var fullFilename = fileName.Replace("file://", "", StringComparison.InvariantCultureIgnoreCase);
+ using var stream = new FileStream(fullFilename, System.IO.FileMode.Open);
+ _loadedReflectionBitmap = SKBitmap.Decode(stream);
+ }
+ else
+ {
+ using var stream = await FileSystem.OpenAppPackageFileAsync(fileName);
+ _loadedReflectionBitmap = SKBitmap.Decode(stream);
+ }
+
+ _secondarySourceSet = false;
+ return;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"LoadSource failed to load animation {fileName}");
+ Console.WriteLine(e);
+ return;
+ }
+ finally
+ {
+ _semaphoreLoadFile.Release();
+ }
+ }
+
+ protected override void OnDisposing()
+ {
+ base.OnDisposing();
+
+ SecondaryTexture?.Dispose();
+ _loadedReflectionBitmap?.Dispose();
+ }
+
+ SKBitmap _loadedReflectionBitmap;
+
+ #endregion
+
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/samples/Sandbox/Views/Controls/ShaderTransition.cs b/src/samples/Sandbox/Views/Controls/ShaderTransition.cs
index ee7533f8..664ea1dd 100644
--- a/src/samples/Sandbox/Views/Controls/ShaderTransition.cs
+++ b/src/samples/Sandbox/Views/Controls/ShaderTransition.cs
@@ -3,11 +3,6 @@
namespace Sandbox.Views.Controls;
-public class ShaderTransitionEffect : ShaderAnimatedEffect
-{
-
-}
-
public class TestLoopEffect : SkiaShaderEffect, IStateEffect, ISkiaGestureProcessor
{
@@ -53,7 +48,7 @@ protected override SKRuntimeEffectUniforms CreateUniforms(SKRect destination)
return uniforms;
}
- protected override SKRuntimeEffectChildren CreateTexturesUniforms(SkiaDrawingContext ctx, SKRect destination, SKImage snapshot)
+ protected override SKRuntimeEffectChildren CreateTexturesUniforms(SkiaDrawingContext ctx, SKRect destination, SKShader texture1)
{
if (ControlTo == null || ControlTo.RenderObject == null)
{
@@ -64,9 +59,9 @@ protected override SKRuntimeEffectChildren CreateTexturesUniforms(SkiaDrawingCon
var snapshot2 = ControlTo.RenderObject.Image;
- if (snapshot != null && snapshot2 != null)
+ if (texture1 != null && snapshot2 != null)
{
- var texture1 = snapshot.ToShader(SKShaderTileMode.Repeat, SKShaderTileMode.Repeat);
+ //var texture1 = snapshot.ToShader(SKShaderTileMode.Repeat, SKShaderTileMode.Repeat);
var texture2 = snapshot2.ToShader(SKShaderTileMode.Repeat, SKShaderTileMode.Repeat);
return new SKRuntimeEffectChildren(CompiledShader)
diff --git a/src/samples/Sandbox/Views/Controls/ShaderTransitionEffect.cs b/src/samples/Sandbox/Views/Controls/ShaderTransitionEffect.cs
new file mode 100644
index 00000000..17499239
--- /dev/null
+++ b/src/samples/Sandbox/Views/Controls/ShaderTransitionEffect.cs
@@ -0,0 +1,203 @@
+using AppoMobi.Maui.Gestures;
+
+namespace Sandbox.Views.Controls;
+
+///
+/// Will animate from Parent control to Secondary then call TransitionEnded event that could make Parent invisible, dispose, whatever.
+///
+public class ShaderTransitionEffect : ShaderDoubleTexturesEffect, IStateEffect, ISkiaGestureProcessor
+{
+
+ protected virtual void OnTransitionEnded()
+ {
+ TransitionEnded?.Invoke(this, EventArgs.Empty);
+ }
+
+ public event EventHandler TransitionEnded;
+
+ bool _initialized;
+ private PointF _mouse;
+
+ #region IStateEffect
+
+ public void UpdateState()
+ {
+ if (Parent != null && !_initialized && Parent.IsLayoutReady)
+ {
+ _initialized = true;
+ if (_animator == null)
+ {
+ _animator = new(Parent);
+
+ _animator.Start((v) =>
+ {
+ this.Progress = v;
+ Update();
+ }, 0, 1, 3500);
+ }
+ }
+
+ base.Update();
+ }
+
+ public override void Attach(SkiaControl parent)
+ {
+ base.Attach(parent);
+
+ UpdateState();
+ }
+
+ #endregion
+
+ #region GESTURES
+
+ public virtual ISkiaGestureListener ProcessGestures(
+ SkiaGesturesParameters args,
+ GestureEventProcessingInfo apply)
+ {
+ _mouse = args.Event.Location;
+
+ if (args.Type == TouchActionResult.Down && _initialized)
+ {
+
+ //var ripple = CreateRipple(_mouse);
+
+ ////run new animator for every Down
+ ////we use this helper task so that every new rangeanimator is disposed properly at the end
+ //Task.Run(async () =>
+ //{
+ // await Parent.AnimateRangeAsync((v) =>
+ // {
+ // ripple.Progress = v;
+ // Update();
+ // }, 0, 1, 4500);
+
+ // RemoveRipple(ripple.Uid);
+
+ //}).ConfigureAwait(false);
+
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ void ApplyReflectionSourceControl(SkiaControl control)
+ {
+ if (_controlSource == control)
+ return;
+
+ DetachTo();
+ _controlSource = control;
+ if (_controlSource != null)
+ {
+ _controlSource.CreatedCache += OnCacheCreatedTo;
+ }
+ }
+
+ public static readonly BindableProperty TargetProperty = BindableProperty.Create(
+ nameof(Target),
+ typeof(SkiaControl),
+ typeof(ShaderTransitionEffect),
+ defaultValue: null,
+ propertyChanged: ApplyTargetProperty);
+
+ private SkiaControl _controlSource;
+
+ public SkiaControl Target
+ {
+ get { return (SkiaControl)GetValue(TargetProperty); }
+ set { SetValue(TargetProperty, value); }
+ }
+
+ private static void ApplyTargetProperty(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ if (oldvalue != newvalue && bindable is ShaderTransitionEffect control)
+ {
+ control.ApplyReflectionSourceControl((SkiaControl)newvalue);
+ }
+ }
+
+ protected override SKShader GetSecondaryTexture()
+ {
+ if (Target == null)
+ {
+ //from file
+ return base.GetSecondaryTexture();
+ }
+
+ if (!_secondarySourceSet && ParentReady())
+ {
+ SetSecondaryFromTarget();
+ }
+ return SecondaryTexture;
+ }
+
+ private bool _secondarySourceSet;
+
+
+
+ #region TargetCache
+
+ void SetSecondaryFromTarget()
+ {
+ if (Target != null && Target.RenderObject != null && Target.RenderObject.Image != null)
+ {
+ var dispose = SecondaryTexture;
+
+ SecondaryTexture = Target.RenderObject.Image.ToShader(TilingSecondaryTexture, TilingSecondaryTexture);
+
+ if (dispose != SecondaryTexture)
+ dispose?.Dispose();
+
+ _secondarySourceSet = true;
+
+ Update();
+ }
+ }
+
+ protected void DetachTo()
+ {
+ if (Target != null)
+ {
+ Target.CreatedCache -= OnCacheCreatedTo;
+ Target = null;
+ }
+ }
+
+ void OnCacheCreatedTo(object sender, CachedObject e)
+ {
+ Update();
+ }
+
+
+ protected override void OnDisposing()
+ {
+ base.OnDisposing();
+ DetachTo();
+ }
+
+ #endregion
+
+ #region PROGRESS ANIMATOR
+
+ private PingPongAnimator _animator;
+
+ public double Progress { get; set; }
+
+ protected override SKRuntimeEffectUniforms CreateUniforms(SKRect destination)
+ {
+ var uniforms = base.CreateUniforms(destination);
+
+ uniforms["progress"] = (float)Progress;
+ uniforms["ratio"] = (float)(destination.Width / destination.Height);
+
+ //uniforms["iMouse"] = new[] { _mouse.X, _mouse.Y, 0f, 0f };
+
+ return uniforms;
+ }
+
+ #endregion
+
+}
\ No newline at end of file
diff --git a/src/samples/Sandbox/Views/Controls/SmallButton.xaml.cs b/src/samples/Sandbox/Views/Controls/SmallButton.xaml.cs
index b2210afb..f69dffd9 100644
--- a/src/samples/Sandbox/Views/Controls/SmallButton.xaml.cs
+++ b/src/samples/Sandbox/Views/Controls/SmallButton.xaml.cs
@@ -13,14 +13,14 @@ public SmallButton()
/// Clip effects with rounded rect of the frame inside
///
///
- public override SKPath CreateClip(object arguments, bool usePosition)
+ public override SKPath CreateClip(object arguments, bool usePosition, SKPath path = null)
{
if (MainFrame != null)
{
- return MainFrame.CreateClip(arguments, usePosition);
+ return MainFrame.CreateClip(arguments, usePosition, path);
}
- return base.CreateClip(arguments, usePosition);
+ return base.CreateClip(arguments, usePosition, path);
}
async void AnimatePress(SkiaControl icon)
diff --git a/src/samples/Sandbox/Views/MainPageIOS17_Tabs.xaml b/src/samples/Sandbox/Views/MainPageIOS17_Tabs.xaml
index b4903b22..bbd09593 100644
--- a/src/samples/Sandbox/Views/MainPageIOS17_Tabs.xaml
+++ b/src/samples/Sandbox/Views/MainPageIOS17_Tabs.xaml
@@ -31,7 +31,7 @@
-