diff --git a/Core/UI.Composite/Layouts/Stack.cs b/Core/UI.Composite/Layouts/Stack.cs index 0737eac..062291a 100644 --- a/Core/UI.Composite/Layouts/Stack.cs +++ b/Core/UI.Composite/Layouts/Stack.cs @@ -173,5 +173,19 @@ public void LayoutChildren() else RearrangeItemsVertically(); } + + public readonly AsyncEvent BlurredChanged = new(); + + bool blurred; + public bool Blurred + { + get => blurred; + set + { + if (blurred == value) return; + blurred = value; + BlurredChanged.Raise(); + } + } } } \ No newline at end of file diff --git a/Zebble/Android/Controls/Containers/AndroidBaseContainer.cs b/Zebble/Android/Controls/Containers/AndroidBaseContainer.cs index 8f85360..d6ecaf8 100644 --- a/Zebble/Android/Controls/Containers/AndroidBaseContainer.cs +++ b/Zebble/Android/Controls/Containers/AndroidBaseContainer.cs @@ -4,12 +4,12 @@ namespace Zebble.AndroidOS using Android.Runtime; using Android.Widget; - public class AndroidBaseContainer : FrameLayout + public class AndroidBaseContainer : FrameLayout where TView : View { - View View; + protected TView View; protected bool IsDisposed; - public AndroidBaseContainer(View view) : base(Renderer.Context) + public AndroidBaseContainer(TView view) : base(Renderer.Context) { View = view; this.ConfigureLayout(); diff --git a/Zebble/Android/Controls/Containers/AndroidBlurredContainer.cs b/Zebble/Android/Controls/Containers/AndroidBlurredContainer.cs new file mode 100644 index 0000000..114386b --- /dev/null +++ b/Zebble/Android/Controls/Containers/AndroidBlurredContainer.cs @@ -0,0 +1,48 @@ +using Android.Graphics; +using Android.Runtime; +using System; + +namespace Zebble.AndroidOS +{ + public class AndroidBlurredContainer : AndroidBaseContainer + { + RenderEffect BlurEffect; + + public AndroidBlurredContainer(Stack view) : base(view) + { + view.BlurredChanged.HandleOnUI(MaintainBlur); + CreateBlur(); + MaintainBlur(); + } + + [Preserve] + protected AndroidBlurredContainer(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) { } + + void CreateBlur() => BlurEffect = RenderEffect.CreateBlurEffect(10, 10, Shader.TileMode.Clamp); + + void MaintainBlur() + { + if (IsDead(out var view)) return; + SetRenderEffect(view.Blurred ? BlurEffect : null); + } + + bool IsDead(out Stack result) + { + result = View; + if (result is null || result.IsDisposing) return true; + return result.IsDisposing; + } + + protected override void Dispose(bool disposing) + { + if (disposing && !IsDisposed) + { + View?.BlurredChanged.RemoveActionHandler(MaintainBlur); + BlurEffect.Dispose(); + BlurEffect = null; + } + + base.Dispose(disposing); + } + } +} diff --git a/Zebble/Android/Controls/Containers/AndroidCustomContainer.cs b/Zebble/Android/Controls/Containers/AndroidCustomContainer.cs index 02842ca..3138018 100644 --- a/Zebble/Android/Controls/Containers/AndroidCustomContainer.cs +++ b/Zebble/Android/Controls/Containers/AndroidCustomContainer.cs @@ -3,10 +3,10 @@ namespace Zebble.AndroidOS { - public class AndroidCustomContainer : AndroidBaseContainer + public class AndroidCustomContainer : AndroidBaseContainer { public AndroidCustomContainer(View view) : base(view) { } - + [Preserve] protected AndroidCustomContainer(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) { } } diff --git a/Zebble/Android/Renderer.cs b/Zebble/Android/Renderer.cs index d6c4eb8..4f1fe56 100644 --- a/Zebble/Android/Renderer.cs +++ b/Zebble/Android/Renderer.cs @@ -78,7 +78,8 @@ void RenderResult() else if (view is TextInput) Result = new AndroidTextInput(view as TextInput); else if (view is ImageView) Result = AndroidImageFactory.Create(view as ImageView); else if (view is ScrollView) Result = ScrollViewFactory.Render(view as ScrollView); - else if (view == UIRuntime.RenderRoot || (view as Overlay)?.BlocksGestures == true) Result = new AndroidGestureView(view); + else if (view == UIRuntime.RenderRoot || view is Overlay { BlocksGestures: true }) Result = new AndroidGestureView(view); + else if (view is Stack stack) Result = new AndroidBlurredContainer(stack); else Result = new AndroidCustomContainer(view); } diff --git a/Zebble/UWP/Controls/UWPCanvas.cs b/Zebble/UWP/Controls/UWPCanvas.cs index d2bb61b..8c2636f 100644 --- a/Zebble/UWP/Controls/UWPCanvas.cs +++ b/Zebble/UWP/Controls/UWPCanvas.cs @@ -4,16 +4,18 @@ namespace Zebble.UWP using controls = Windows.UI.Xaml.Controls; using Olive; - public class UWPCanvas : controls.Canvas, IDisposable, UIChangeCommand.IHandler + public abstract class UWPCanvasBase : controls.Canvas, IDisposable, UIChangeCommand.IHandler where TView : View { readonly WeakReference RendererRef; - readonly WeakReference ViewRef; - View View => ViewRef?.GetTargetOrDefault(); + readonly WeakReference ViewRef; - public UWPCanvas(Renderer renderer) + protected bool IsDisposed; + protected TView View => ViewRef?.GetTargetOrDefault(); + + public UWPCanvasBase(Renderer renderer, TView view) { RendererRef = renderer.GetWeakReference(); - ViewRef = renderer?.View.GetWeakReference(); + ViewRef = view.GetWeakReference(); Configure(); } @@ -53,10 +55,19 @@ void HandleTouches() else Background = view.BackgroundColor.RenderBrush(); } - public void Dispose() + public virtual void Dispose() { - ViewRef?.SetTarget(null); - RendererRef?.SetTarget(null); + if (!IsDisposed) + { + IsDisposed = true; + ViewRef?.SetTarget(null); + RendererRef?.SetTarget(null); + } } } + + public class UWPCanvas : UWPCanvasBase + { + public UWPCanvas(Renderer renderer, View view) : base(renderer, view) { } + } } \ No newline at end of file diff --git a/Zebble/UWP/Controls/UWPStack.cs b/Zebble/UWP/Controls/UWPStack.cs new file mode 100644 index 0000000..16c68f6 --- /dev/null +++ b/Zebble/UWP/Controls/UWPStack.cs @@ -0,0 +1,36 @@ +namespace Zebble.UWP +{ + public class UWPStack : UWPCanvasBase + { + public UWPStack(Renderer renderer, Stack view) : base(renderer, view) + { + view.BlurredChanged.Handle(MaintainBlur); + CreateBlur(); + MaintainBlur(); + } + + void CreateBlur() { } + + void MaintainBlur() + { + if (IsDead(out var view)) return; + // TODO + // View.BackgroundColor(view.Blurred ? Colors.Black.WithAlpha(100) : null); + } + + bool IsDead(out Stack result) + { + result = View; + if (result is null || result.IsDisposing) return true; + return result.IsDisposing; + } + + public override void Dispose() + { + if (!IsDisposed) + View?.BlurredChanged.RemoveActionHandler(MaintainBlur); + + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Zebble/UWP/Renderer.cs b/Zebble/UWP/Renderer.cs index d1f818d..0bb4d50 100644 --- a/Zebble/UWP/Renderer.cs +++ b/Zebble/UWP/Renderer.cs @@ -70,7 +70,8 @@ async Task CreateNativeElement() NativeElement = await (RenderOrchestrator = new UWPImageView(image)).Render(); else if (View is ScrollView scroll) NativeElement = await (RenderOrchestrator = new UWPScrollView(scroll)).Render(); - else NativeElement = new UWPCanvas(this); + else if (View is Stack stack) NativeElement = new UWPStack(this, stack); + else NativeElement = new UWPCanvas(this, View); } internal void Apply(string property, UIChangedEventArgs change) diff --git a/Zebble/Zebble.csproj b/Zebble/Zebble.csproj index 8455a72..e9158f5 100644 --- a/Zebble/Zebble.csproj +++ b/Zebble/Zebble.csproj @@ -7,7 +7,7 @@ Zebble Zebble $(AssemblyName) ($(TargetFramework)) - 5.0.9.0 + 5.0.11.0 true true true diff --git a/Zebble/iOS/Controls/IosBlurredContainer.cs b/Zebble/iOS/Controls/IosBlurredContainer.cs new file mode 100644 index 0000000..c37b5ca --- /dev/null +++ b/Zebble/iOS/Controls/IosBlurredContainer.cs @@ -0,0 +1,71 @@ +namespace Zebble.IOS +{ + using System; + using UIKit; + + public class IosBlurredContainer : IosContainerBase + { + UIVisualEffectView BlurSubview; + bool IsSubviewAdded; + + public IosBlurredContainer(Stack view) : base(view) + { + View.BlurredChanged.HandleOnUI(MaintainBlur); + CreateBlur(); + MaintainBlur(); + } + + void CreateBlur() => BlurSubview = new UIVisualEffectView + { + Alpha = .975f, + Effect = UIBlurEffect.FromStyle(UIBlurEffectStyle.Regular) + }; + + public void OnBoundsChanged() => BlurSubview.Set(x => x.Frame = Bounds); + + void MaintainBlur() + { + if (IsDead(out var view)) return; + if (view.Blurred) + { + if (IsSubviewAdded) return; + AddSubview(BlurSubview); + IsSubviewAdded = true; + } + else + { + if (IsSubviewAdded == false) return; + BlurSubview.RemoveFromSuperview(); + IsSubviewAdded = false; + } + } + + public override void SubviewAdded(UIView uiview) + { + base.SubviewAdded(uiview); + if (IsSubviewAdded == false) return; + if (uiview == BlurSubview) return; + BringSubviewToFront(BlurSubview); + } + + bool IsDead(out Stack result) + { + result = View; + if (result is null || result.IsDisposing) return true; + return result.IsDisposing; + } + + protected override void Dispose(bool disposing) + { + if (disposing && !IsDisposed) + { + View?.BlurredChanged.RemoveActionHandler(MaintainBlur); + BlurSubview?.RemoveFromSuperview(); + BlurSubview?.Dispose(); + BlurSubview = null; + } + + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/Zebble/iOS/Controls/IosContainer.cs b/Zebble/iOS/Controls/IosContainer.cs index fe05104..37ea93b 100644 --- a/Zebble/iOS/Controls/IosContainer.cs +++ b/Zebble/iOS/Controls/IosContainer.cs @@ -2,17 +2,18 @@ namespace Zebble.IOS { using UIKit; - public class IosContainer : UIView + public abstract class IosContainerBase : UIView where TView : View { - bool IsDisposing; + protected TView View; + protected bool IsDisposed; - public IosContainer(View view) { } + public IosContainerBase(TView view) => View = view; protected override void Dispose(bool disposing) { - if (disposing && !IsDisposing) + if (disposing && !IsDisposed) { - IsDisposing = true; + IsDisposed = true; try { foreach (var subview in Subviews) subview?.Dispose(); } catch { /* No loggins is needed */ } } @@ -20,4 +21,9 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } } + + public class IosContainer : IosContainerBase + { + public IosContainer(View view) : base(view) { } + } } \ No newline at end of file diff --git a/Zebble/iOS/IosRenderer.cs b/Zebble/iOS/IosRenderer.cs index e31294e..44b8be4 100644 --- a/Zebble/iOS/IosRenderer.cs +++ b/Zebble/iOS/IosRenderer.cs @@ -33,6 +33,7 @@ public async Task Render() else { if (view is ScrollView sc) Result = await new IosScrollView(sc).GetResult(); + else if (view is Stack stack) Result = new IosBlurredContainer(stack); else Result = new IosContainer(view); Result.ResignFirstResponder(); @@ -200,6 +201,9 @@ void OnBoundsChanged(BoundsChangedEventArgs args) // As gradient color is a sublayer, sync its size. // Layer.SyncBackgroundFrame(Result.Frame); + if (Result is IosBlurredContainer blurred) + blurred.OnBoundsChanged(); + RenderBorder(); }