diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 724f039ce073..1ae92eeb6bee 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -1821,6 +1821,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -9821,6 +9825,9 @@ ContentPresenter_NativeEmbedding.xaml + + ContentPresenter_NativeEmbedding_ZIndex.xaml + ContentPresenter_NativeEmbedding_Android_FillType.xaml @@ -10172,4 +10179,4 @@ - \ No newline at end of file + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_NativeEmbedding_ZIndex.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_NativeEmbedding_ZIndex.xaml new file mode 100644 index 000000000000..39a067c6158d --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_NativeEmbedding_ZIndex.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_NativeEmbedding_ZIndex.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_NativeEmbedding_ZIndex.xaml.cs new file mode 100644 index 000000000000..3bd82215c754 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_NativeEmbedding_ZIndex.xaml.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml; +using Windows.UI.Core; +using Microsoft.UI.Xaml.Data; +using Uno.UI.Samples.Controls; + +namespace Uno.UI.Samples.Content.UITests.ContentPresenter +{ + [Sample("ContentPresenter", IsManualTest = true)] + public sealed partial class ContentPresenter_NativeEmbedding_ZIndex : UserControl + { + public ContentPresenter_NativeEmbedding_ZIndex() + { + this.InitializeComponent(); + var state = new ToggleableState(); + sv.DataContext = state; + var timer = new DispatcherTimer(); + timer.Interval = TimeSpan.FromSeconds(1); + timer.Tick += (s, e) => + { + state.State++; + }; + timer.Start(); + } + } + + public class ToggleableState : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (EqualityComparer.Default.Equals(field, value)) return false; + field = value; + OnPropertyChanged(propertyName); + return true; + } + + private int _state; + public int State { get => _state; set => SetField(ref _state, value); } + } + + public class StateToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + => ((int)value) % 2 == 0 ? Visibility.Visible : Visibility.Collapsed; + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } + + public class StateToZIndexConverter : IValueConverter + { + private int _lastRet; + public int Modulus { get; set; } + public int Remainder { get; set; } + public object Convert(object value, Type targetType, object parameter, string language) + { + return ((int)value) % Modulus == Remainder ? _lastRet = ((int)value) : _lastRet; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/Uno.UI.Composition/Composition/Visual.skia.cs b/src/Uno.UI.Composition/Composition/Visual.skia.cs index 9083d5578040..a936c6295780 100644 --- a/src/Uno.UI.Composition/Composition/Visual.skia.cs +++ b/src/Uno.UI.Composition/Composition/Visual.skia.cs @@ -9,6 +9,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Text; +using Windows.Foundation; using Microsoft.CodeAnalysis.PooledObjects; using SkiaSharp; using Uno.Disposables; @@ -519,7 +520,7 @@ static void RenderChildrenStep(Visual visual, PaintingSession session, bool appl } } - internal void GetNativeViewPath(SKPath clipFromParent, SKPath clipPath) + internal void GetNativeViewPathAndZOrder(SKPath clipFromParent, SKPath clipPath, List nativeVisualsInZOrder) { if (this is { Opacity: 0 } or { IsVisible: false } || clipFromParent.IsEmpty) { @@ -546,6 +547,11 @@ internal void GetNativeViewPath(SKPath clipFromParent, SKPath clipPath) clipPath.Op(localClipCombinedByClipFromParent, IsNativeHostVisual ? SKPathOp.Union : SKPathOp.Difference, clipPath); } + if (IsNativeHostVisual && !localClipCombinedByClipFromParent.IsEmpty) + { + nativeVisualsInZOrder.Add(this); + } + if (GetPostPaintingClipping() is { } postClip) { postClip.Transform(TotalMatrix.ToSKMatrix(), postClip); @@ -553,7 +559,7 @@ internal void GetNativeViewPath(SKPath clipFromParent, SKPath clipPath) } foreach (var child in GetChildrenInRenderOrder()) { - child.GetNativeViewPath(localClipCombinedByClipFromParent, clipPath); + child.GetNativeViewPathAndZOrder(localClipCombinedByClipFromParent, clipPath, nativeVisualsInZOrder); } } diff --git a/src/Uno.UI.Runtime.Skia.Android/Native/AndroidSkiaNativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.Android/Native/AndroidSkiaNativeElementHostingExtension.cs index 388016cd9c7d..3c534da33f1a 100644 --- a/src/Uno.UI.Runtime.Skia.Android/Native/AndroidSkiaNativeElementHostingExtension.cs +++ b/src/Uno.UI.Runtime.Skia.Android/Native/AndroidSkiaNativeElementHostingExtension.cs @@ -18,7 +18,7 @@ public AndroidSkiaNativeElementHostingExtension(ContentPresenter owner) _owner = owner; } - public void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect) + public void ArrangeNativeElement(object content, Rect arrangeRect) { if (content is View view) { @@ -77,14 +77,6 @@ public void ChangeNativeElementOpacity(object content, double opacity) } } - public void ChangeNativeElementVisibility(object content, bool visible) - { - if (content is View view) - { - view.Visibility = visible ? ViewStates.Visible : ViewStates.Invisible; - } - } - public object? CreateSampleComponent(string text) { if (ApplicationActivity.NativeLayerHost is not { } host) diff --git a/src/Uno.UI.Runtime.Skia.AppleUIKit/Native/UIKitNativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.AppleUIKit/Native/UIKitNativeElementHostingExtension.cs index bfc409462c1f..2b9e4b2ef3dc 100644 --- a/src/Uno.UI.Runtime.Skia.AppleUIKit/Native/UIKitNativeElementHostingExtension.cs +++ b/src/Uno.UI.Runtime.Skia.AppleUIKit/Native/UIKitNativeElementHostingExtension.cs @@ -20,7 +20,7 @@ public UIKitNativeElementHostingExtension(ContentPresenter presenter) private UIView? OverlayLayer => _presenter.XamlRoot is { } xamlRoot ? (XamlRootMap.GetHostForRoot(xamlRoot) as IAppleUIKitXamlRootHost)?.NativeOverlayLayer : null; - public void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect) + public void ArrangeNativeElement(object content, Rect arrangeRect) { if (content is UIView view) { @@ -72,14 +72,6 @@ public void ChangeNativeElementOpacity(object content, double opacity) } } - public void ChangeNativeElementVisibility(object content, bool visible) - { - if (content is UIView view) - { - view.Opaque = visible; - } - } - public object? CreateSampleComponent(string text) { if (OverlayLayer is null) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/Native/MacOSNativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.MacOS/Native/MacOSNativeElementHostingExtension.cs index 08c2038fab74..3f81829e4a81 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/Native/MacOSNativeElementHostingExtension.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/Native/MacOSNativeElementHostingExtension.cs @@ -11,6 +11,14 @@ namespace Uno.UI.Runtime.Skia.MacOS; internal class MacOSNativeElement : Microsoft.UI.Xaml.FrameworkElement { + public MacOSNativeElement() + { + Unloaded += (s, e) => + { + NativeUno.uno_native_dispose(NativeHandle); + }; + } + public nint NativeHandle { get; internal set; } internal bool Detached { get; set; } @@ -29,7 +37,7 @@ private MacOSNativeElementHostingExtension(ContentPresenter contentPresenter) public static void Register() => ApiExtensibility.Register(typeof(ContentPresenter.INativeElementHostingExtension), o => new MacOSNativeElementHostingExtension(o)); - public void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect) + public void ArrangeNativeElement(object content, Rect arrangeRect) { if (content is MacOSNativeElement element) { @@ -39,7 +47,7 @@ public void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect } else { - NativeUno.uno_native_arrange(element.NativeHandle, arrangeRect.Left, arrangeRect.Top, arrangeRect.Width, arrangeRect.Height, clipRect.Left, clipRect.Top, clipRect.Width, clipRect.Height); + NativeUno.uno_native_arrange(element.NativeHandle, arrangeRect.Left, arrangeRect.Top, arrangeRect.Width, arrangeRect.Height); } } else if (this.Log().IsEnabled(LogLevel.Debug)) @@ -53,6 +61,7 @@ public void AttachNativeElement(object content) if (content is MacOSNativeElement element) { NativeUno.uno_native_attach(element.NativeHandle); + element.Detached = false; } else if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -74,19 +83,6 @@ public void ChangeNativeElementOpacity(object content, double opacity) } } - public void ChangeNativeElementVisibility(object content, bool visible) - { - if (content is MacOSNativeElement element) - { - // https://developer.apple.com/documentation/appkit/nsview/1483369-hidden?language=objc - NativeUno.uno_native_set_visibility(element.NativeHandle, visible); - } - else if (this.Log().IsEnabled(LogLevel.Debug)) - { - this.Log().Debug($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); - } - } - public object? CreateSampleComponent(string text) { if (_window is null) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/Native/NativeUno.cs b/src/Uno.UI.Runtime.Skia.MacOS/Native/NativeUno.cs index 516b7e59ac3e..6ced261d415c 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/Native/NativeUno.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/Native/NativeUno.cs @@ -342,7 +342,7 @@ internal static unsafe partial void uno_set_window_close_callbacks( internal static partial nint uno_native_create_sample(nint window, string text); [LibraryImport("libUnoNativeMac.dylib")] - internal static partial void uno_native_arrange(nint element, double arrangeLeft, double arrangeTop, double arrangeWidth, double arrangeHeight, double clipLeft, double clipTop, double clipWidth, double clipHeight); + internal static partial void uno_native_arrange(nint element, double arrangeLeft, double arrangeTop, double arrangeWidth, double arrangeHeight); [LibraryImport("libUnoNativeMac.dylib")] internal static partial void uno_native_attach(nint element); @@ -358,10 +358,10 @@ internal static unsafe partial void uno_set_window_close_callbacks( internal static partial void uno_native_set_opacity(nint element, double opacity); [LibraryImport("libUnoNativeMac.dylib")] - internal static partial void uno_native_set_visibility(nint element, [MarshalAs(UnmanagedType.I1)] bool visible); + internal static partial void uno_native_measure(nint element, double childWidth, double childHeight, double availableWidth, double availableHeight, out double width, out double height); [LibraryImport("libUnoNativeMac.dylib")] - internal static partial void uno_native_measure(nint element, double childWidth, double childHeight, double availableWidth, double availableHeight, out double width, out double height); + internal static partial void uno_native_dispose(nint element); [LibraryImport("libUnoNativeMac.dylib")] internal static unsafe partial nint uno_set_execute_callback(delegate* unmanaged[Cdecl] callback); @@ -384,9 +384,6 @@ internal static unsafe partial void uno_set_webview_navigation_callbacks( [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] internal static partial nint uno_webview_create(nint window, string ok, string cancel); - [LibraryImport("libUnoNativeMac.dylib")] - internal static partial void uno_webview_dispose(nint webview); - [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] internal static partial string uno_webview_get_title(nint webview); diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UI/Xaml/Controls/WebView/MacOSNativeWebView.cs b/src/Uno.UI.Runtime.Skia.MacOS/UI/Xaml/Controls/WebView/MacOSNativeWebView.cs index b275c2e7b3e1..f48d06f0958d 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UI/Xaml/Controls/WebView/MacOSNativeWebView.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/UI/Xaml/Controls/WebView/MacOSNativeWebView.cs @@ -59,7 +59,6 @@ public MacOSNativeWebView(MacOSWindowNative window, CoreWebView2 owner) Unloaded += (s, e) => { - NativeUno.uno_webview_dispose(NativeHandle); _webViews.Remove(NativeHandle); }; @@ -383,6 +382,10 @@ internal static unsafe void DidReceiveScriptMessage(nint handle, sbyte* messageB var message = messageBody == null ? "" : new string(messageBody); webview._owner.RaiseWebMessageReceived(message); } + else if (typeof(MacOSNativeWebView).Log().IsEnabled(LogLevel.Warning)) + { + typeof(MacOSNativeWebView).Log().Warn($"MacOSNativeWebView.DidReceiveScriptMessage could not map 0x{handle:X} with an WKWebView"); + } } [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOMediaPlayer.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOMediaPlayer.m index 56416b21849e..63d81aa68ec0 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOMediaPlayer.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOMediaPlayer.m @@ -294,7 +294,7 @@ void uno_mediaplayer_set_view(UNOMediaPlayer *media, UNOMediaPlayerView *view, N media.videoLayer.frame = view.frame; media.videoLayer.videoGravity = kCAGravityResizeAspect; [view.layer addSublayer:media.videoLayer]; - [window.contentViewController.view addSubview:view positioned:NSWindowAbove relativeTo:nil]; + view.originalSuperView = window.contentViewController.view; } static NSMutableSet *players; @@ -511,15 +511,15 @@ - (BOOL)wantsUpdateLayer return true; } -@synthesize visible; - // UNONativeElement -- (void)detach { +- (void)dispose { #if DEBUG - NSLog(@"detach mediaplayer %p", self); + NSLog(@"UNOMediaPlayer %p disposing with superview %p", self, self.superview); #endif -// [self removeFromSuperview]; + if (self.superview) { + [self removeFromSuperview]; + } } - (void)layout { @@ -536,4 +536,6 @@ - (void)layout { } } +@synthesize originalSuperView; + @end diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h index e9cdec54ebe7..858856fdcaaf 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h @@ -11,9 +11,9 @@ NS_ASSUME_NONNULL_BEGIN @protocol UNONativeElement -@property (nonatomic) bool visible; +@property (nonatomic) NSView* originalSuperView; --(void) detach; +-(void) dispose; @end @@ -23,18 +23,18 @@ NS_ASSUME_NONNULL_BEGIN NSView* uno_native_create_sample(NSWindow *window, const char* _Nullable text); -void uno_native_arrange(NSView *element, double arrangeLeft, double arrangeTop, double arrangeWidth, double arrangeHeight, double clipLeft, double clipTop, double clipWidth, double clipHeight); +void uno_native_arrange(NSView* element, double arrangeLeft, double arrangeTop, double arrangeWidth, double arrangeHeight); -void uno_native_attach(NSView* element); +void uno_native_attach(NSView* element); -void uno_native_detach(NSView* element); +void uno_native_detach(NSView* element); -bool uno_native_is_attached(NSView* element); +bool uno_native_is_attached(NSView* element); -void uno_native_measure(NSView* element, double childWidth, double childHeight, double availableWidth, double availableHeight, double* width, double* height); +void uno_native_measure(NSView* element, double childWidth, double childHeight, double availableWidth, double availableHeight, double* width, double* height); -void uno_native_set_opacity(NSView* element, double opacity); +void uno_native_set_opacity(NSView* element, double opacity); -void uno_native_set_visibility(NSView* element, bool visible); +void uno_native_dispose(NSView *element); NS_ASSUME_NONNULL_END diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m index 85a97d1ce4f9..7a61f265087a 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m @@ -5,9 +5,12 @@ #import "UNONative.h" static NSMutableSet *elements; +static NSMutableSet *transients; @implementation UNORedView : NSView +@synthesize originalSuperView; + // make the background red for easier tracking - (BOOL)wantsUpdateLayer { @@ -19,12 +22,19 @@ - (void)updateLayer self.layer.backgroundColor = NSColor.redColor.CGColor; } -@synthesize visible; - - (void)detach { // nothing needed } +- (void)dispose { +#if DEBUG + NSLog(@"UNORedView %p disposing with superview %p", self, self.superview); +#endif + if (self.superview) { + [self removeFromSuperview]; + } +} + @end NSView* uno_native_create_sample(NSWindow *window, const char* _Nullable text) @@ -38,54 +48,63 @@ - (void)detach { label.stringValue = [NSString stringWithUTF8String:text]; label.frame = NSMakeRect(0, 0, label.fittingSize.width, label.fittingSize.height); - NSView* sample = [[UNORedView alloc] initWithFrame:label.frame]; + UNORedView* sample = [[UNORedView alloc] initWithFrame:label.frame]; [sample addSubview:label]; #if DEBUG NSLog(@"uno_native_create_sample #%p label: %@", sample, label.stringValue); #endif - [window.contentViewController.view addSubview:sample]; + sample.originalSuperView = window.contentViewController.view; return sample; } -void uno_native_arrange(NSView *element, double arrangeLeft, double arrangeTop, double arrangeWidth, double arrangeHeight, double clipLeft, double clipTop, double clipWidth, double clipHeight) +void uno_native_arrange(NSView *element, double arrangeLeft, double arrangeTop, double arrangeWidth, double arrangeHeight) { NSRect arrange = NSMakeRect(arrangeLeft, arrangeTop, arrangeWidth, arrangeHeight); element.frame = arrange; #if DEBUG - NSLog(@"uno_native_arrange %p arrange(%g,%g,%g,%g) clip(%g,%g,%g,%g)", element, - arrangeLeft, arrangeTop, arrangeWidth, arrangeHeight, - clipLeft, clipTop, clipWidth, clipHeight); + NSLog(@"uno_native_arrange %p arrange(%g,%g,%g,%g)", element, + arrangeLeft, arrangeTop, arrangeWidth, arrangeHeight); #endif } -void uno_native_attach(NSView* element) +void uno_native_attach(NSView* element) { #if DEBUG - NSLog(@"uno_native_attach %p -> %s attached", element, [elements containsObject:element] ? "already" : "not previously"); + NSLog(@"!!uno_native_attach %p", element); #endif + bool already_attached = NO; if (!elements) { elements = [[NSMutableSet alloc] initWithCapacity:10]; + } else { + already_attached = [elements containsObject:element]; } - // note: it's too early to add a mask since the layer has not been set yet - [elements addObject:element]; +#if DEBUG + NSLog(@"uno_native_attach %p -> %s attached", element, already_attached ? "already" : "not previously"); +#endif + if (!already_attached) { + // note: it's too early to add a mask since the layer has not been set yet + [elements addObject:element]; + } + [element.originalSuperView addSubview:element]; } -void uno_native_detach(NSView *element) +void uno_native_detach(NSView* element) { #if DEBUG NSLog(@"uno_native_detach %p", element); #endif element.layer.mask = nil; - if (elements) { - if ([element conformsToProtocol:@protocol(UNONativeElement)]) { - id native = (id) element; - [native detach]; - } - [elements removeObject:element]; + + if (!transients) { + transients = [[NSMutableSet alloc] initWithCapacity:10]; } + // once removed from superview the instance can be freed by the runtime unless we keep another reference to it + [transients addObject:element]; + [elements removeObject:element]; + [element removeFromSuperview]; } -bool uno_native_is_attached(NSView* element) +bool uno_native_is_attached(NSView* element) { bool attached = elements ? [elements containsObject:element] : NO; #if DEBUG @@ -94,7 +113,7 @@ bool uno_native_is_attached(NSView* element) return attached; } -void uno_native_measure(NSView* element, double childWidth, double childHeight, double availableWidth, double availableHeight, double* width, double* height) +void uno_native_measure(NSView* element, double childWidth, double childHeight, double availableWidth, double availableHeight, double* width, double* height) { CGSize size = element.subviews.firstObject.frame.size; *width = size.width; @@ -104,7 +123,7 @@ void uno_native_measure(NSView* element, double childWidth, double childHeight, #endif } -void uno_native_set_opacity(NSView* element, double opacity) +void uno_native_set_opacity(NSView* element, double opacity) { #if DEBUG NSLog(@"uno_native_set_opacity #%p : %g -> %g", element, element.alphaValue, opacity); @@ -112,13 +131,11 @@ void uno_native_set_opacity(NSView* element, double opacity) element.alphaValue = opacity; } -void uno_native_set_visibility(NSView* element, bool visible) +void uno_native_dispose(NSView* element) { #if DEBUG - NSLog(@"uno_native_set_visibility #%p : hidden %s -> visible %s", element, element.hidden ? "TRUE" : "FALSE", visible ? "TRUE" : "FALSE"); + NSLog(@"uno_native_dispose #%p", element); #endif - element.visible = visible; - // hidden is controlled by both visible and clipping - if (!visible) - element.hidden = true; + [element dispose]; + [transients removeObject:element]; } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWebView.h b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWebView.h index 6b525670cacb..0be3a68c8cd3 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWebView.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWebView.h @@ -37,7 +37,6 @@ uno_webview_unsupported_scheme_identified_fn_ptr uno_get_webview_unsupported_sch void uno_set_webview_unsupported_scheme_identified_callback(uno_webview_unsupported_scheme_identified_fn_ptr fn_ptr); NSView* uno_webview_create(NSWindow *window, const char *ok, const char *cancel); -void uno_webview_dispose(WKWebView *webview); const char* uno_webview_get_title(WKWebView *webview); diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWebView.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWebView.m index 8d152118901f..f1a85722b804 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWebView.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWebView.m @@ -95,16 +95,10 @@ void uno_set_webview_unsupported_scheme_identified_callback(uno_webview_unsuppor webview.okString = [NSString stringWithUTF8String:ok]; webview.cancelString = [NSString stringWithUTF8String:cancel]; - [window.contentViewController.view addSubview:webview positioned:NSWindowBelow relativeTo:nil]; + webview.originalSuperView = window.contentViewController.view; return webview; } -void uno_webview_dispose(WKWebView *webview) -{ - [webview stopLoading]; - [webview removeFromSuperview]; -} - const char* uno_webview_get_title(WKWebView *webview) { return strdup(webview.title.UTF8String); @@ -335,10 +329,14 @@ - (void)scrollWheel:(NSEvent *)event { // UNONativeElement -- (void) detach { +- (void) dispose { #if DEBUG - NSLog(@"detach webview %p", self); + NSLog(@"UNOWebView %p disposing with superview %p", self, self.superview); #endif + if (self.superview) { + [self stopLoading]; + [self removeFromSuperview]; + } [self.configuration.userContentController removeScriptMessageHandlerForName:@"unoWebView"]; } @@ -573,6 +571,6 @@ - (void)userContentController:(WKUserContentController *)userContentController d } } -@synthesize visible; +@synthesize originalSuperView; @end diff --git a/src/Uno.UI.Runtime.Skia.WebAssembly.Browser/Native/BrowserNativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.WebAssembly.Browser/Native/BrowserNativeElementHostingExtension.cs index de9cc7c1131e..cbdf245c60af 100644 --- a/src/Uno.UI.Runtime.Skia.WebAssembly.Browser/Native/BrowserNativeElementHostingExtension.cs +++ b/src/Uno.UI.Runtime.Skia.WebAssembly.Browser/Native/BrowserNativeElementHostingExtension.cs @@ -33,17 +33,12 @@ public void DetachNativeElement(object content) NativeMethods.DetachNativeElement(((BrowserHtmlElement)content).ElementId); } - public void ArrangeNativeElement(object content, Windows.Foundation.Rect arrangeRect, Windows.Foundation.Rect clipRect) + public void ArrangeNativeElement(object content, Windows.Foundation.Rect arrangeRect) { Debug.Assert(content is BrowserHtmlElement); NativeMethods.ArrangeNativeElement(((BrowserHtmlElement)content).ElementId, arrangeRect.X, arrangeRect.Y, arrangeRect.Width, arrangeRect.Height); } - public void ChangeNativeElementVisibility(object content, bool visible) - { - // no need to do anything here, airspace clipping logic will take care of it automatically - } - public void ChangeNativeElementOpacity(object content, double opacity) { Debug.Assert(content is BrowserHtmlElement); diff --git a/src/Uno.UI.Runtime.Skia.Win32/Native/Win32NativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.Win32/Native/Win32NativeElementHostingExtension.cs index f67fd3e1514b..91d7c2c7376a 100644 --- a/src/Uno.UI.Runtime.Skia.Win32/Native/Win32NativeElementHostingExtension.cs +++ b/src/Uno.UI.Runtime.Skia.Win32/Native/Win32NativeElementHostingExtension.cs @@ -286,7 +286,7 @@ public void DetachNativeElement(object content) ((Win32WindowWrapper)XamlRootMap.GetHostForRoot(_presenter.XamlRoot!)!).RenderingNegativePathReevaluated -= OnRenderingNegativePathReevaluated; } - public void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect) + public void ArrangeNativeElement(object content, Rect arrangeRect) { if (content is not Win32NativeWindow window) { @@ -347,11 +347,6 @@ public unsafe object CreateSampleComponent(string text) return new Win32NativeWindow(hwnd); } - public void ChangeNativeElementVisibility(object content, bool visible) - { - // no need to do anything here, airspace clipping logic will take care of it automatically - } - public void ChangeNativeElementOpacity(object content, double opacity) { if (content is not Win32NativeWindow window) diff --git a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeElementHostingExtension.cs index 38b8b1b620d5..cda63fef005d 100644 --- a/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeElementHostingExtension.cs +++ b/src/Uno.UI.Runtime.Skia.Wpf/Extensions/WpfNativeElementHostingExtension.cs @@ -60,14 +60,6 @@ public void DetachNativeElement(object content) } } - public void ChangeNativeElementVisibility(object content, bool visible) - { - if (content is System.Windows.UIElement contentAsUIElement) - { - contentAsUIElement.Visibility = visible ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed; - } - } - public void ChangeNativeElementOpacity(object content, double opacity) { if (content is System.Windows.UIElement contentAsUIElement) @@ -76,7 +68,7 @@ public void ChangeNativeElementOpacity(object content, double opacity) } } - public void ArrangeNativeElement(object content, Windows.Foundation.Rect arrangeRect, Windows.Foundation.Rect clipRect) + public void ArrangeNativeElement(object content, Windows.Foundation.Rect arrangeRect) { if (content is System.Windows.UIElement contentAsUIElement) { diff --git a/src/Uno.UI.Runtime.Skia.X11/Native/X11NativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.X11/Native/X11NativeElementHostingExtension.cs index d16838560a38..7cda7eca2422 100644 --- a/src/Uno.UI.Runtime.Skia.X11/Native/X11NativeElementHostingExtension.cs +++ b/src/Uno.UI.Runtime.Skia.X11/Native/X11NativeElementHostingExtension.cs @@ -18,7 +18,6 @@ namespace Uno.WinUI.Runtime.Skia.X11; internal partial class X11NativeElementHostingExtension : ContentPresenter.INativeElementHostingExtension { private Rect? _lastArrangeRect; - private Rect? _lastClipRect; private bool _layoutDirty = true; private readonly ContentPresenter _presenter; @@ -142,7 +141,6 @@ public void DetachNativeElement(object content) _ = XLib.XUnmapWindow(Display, nativeWindow.WindowId); _ = XLib.XSync(Display, false); - _lastClipRect = null; _lastArrangeRect = null; Debug.Assert(_frameRenderedDisposable is not null); @@ -155,10 +153,9 @@ public void DetachNativeElement(object content) } } - public void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect) + public void ArrangeNativeElement(object content, Rect arrangeRect) { _lastArrangeRect = arrangeRect; - _lastClipRect = clipRect; _layoutDirty = true; _presenter.Visual.Compositor.InvalidateRender(_presenter.Visual); // we don't update the layout right now. We wait for the next render to happen, as @@ -175,7 +172,6 @@ private void UpdateLayout() _layoutDirty = false; if (_presenter.Content is X11NativeWindow nativeWindow && _lastArrangeRect is { } arrangeRect && - _lastClipRect is { } clipRect && XamlRoot is { } xamlRoot && XamlRootMap.GetHostForRoot(xamlRoot) is X11XamlRootHost host) { @@ -198,11 +194,6 @@ XamlRoot is { } xamlRoot && public Size MeasureNativeElement(object content, Size childMeasuredSize, Size availableSize) => availableSize; - public void ChangeNativeElementVisibility(object content, bool visible) - { - // no need to do anything here, airspace clipping logic will take care of it automatically - } - // This doesn't seem to work as most (all?) WMs won't change the opacity for subwindows, only top-level windows public void ChangeNativeElementOpacity(object content, double opacity) { diff --git a/src/Uno.UI/Helpers/SkiaRenderHelper.skia.cs b/src/Uno.UI/Helpers/SkiaRenderHelper.skia.cs index ff7acda4f978..da79a82eab88 100644 --- a/src/Uno.UI/Helpers/SkiaRenderHelper.skia.cs +++ b/src/Uno.UI/Helpers/SkiaRenderHelper.skia.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; @@ -17,6 +18,8 @@ internal static class SkiaRenderHelper { private static readonly SKPictureRecorder _recorder = new(); + private static readonly List _emptyList = new(); + // This is used all the time, on all platforms but X11, when no native elements are present - DO NOT MODIFY private static readonly SKPath _emptyClipPath = new(); @@ -28,7 +31,7 @@ internal static class SkiaRenderHelper internal static bool CanRecordPicture([NotNullWhen(true)] UIElement? rootElement) => rootElement is { IsArrangeDirtyOrArrangeDirtyPath: false, IsMeasureDirtyOrMeasureDirtyPath: false }; - internal static (IntPtr, SKPath) RecordPictureAndReturnPath(float width, float height, ContainerVisual rootVisual, bool invertPath) + internal static (IntPtr picture, SKPath nativeClipPath, List nativeVisualsInZOrder) RecordPictureAndReturnPath(float width, float height, ContainerVisual rootVisual, bool invertPath) { var canvas = _recorder.BeginRecording(new SKRect(-999999, -999999, 999999, 999999)); using var _ = new SKAutoCanvasRestore(canvas, true); @@ -36,15 +39,13 @@ internal static (IntPtr, SKPath) RecordPictureAndReturnPath(float width, float h rootVisual.Compositor.RenderRootVisual(canvas, rootVisual); - var path = !ContentPresenter.HasNativeElements() ? - !invertPath ? - _emptyClipPath : - GetOrUpdateInvertedClippingPath(width, height) : + var (path, nativeVisualsInZOrder) = !ContentPresenter.HasNativeElements() ? + (!invertPath ? _emptyClipPath : GetOrUpdateInvertedClippingPath(width, height), _emptyList) : CalculateClippingPath(width, height, rootVisual, invertPath); var picture = UnoSkiaApi.sk_picture_recorder_end_recording(_recorder.Handle); - return (picture, path); + return (picture, path, nativeVisualsInZOrder); } internal static void RenderPicture(SKCanvas canvas, IntPtr picture, SKColor background, FpsHelper fpsHelper) @@ -74,7 +75,7 @@ internal static void RenderPicture(SKCanvas canvas, IntPtr picture, SKColor back /// /// Does a rendering cycle and returns a path that represents the visible area of the native views. /// - private static SKPath CalculateClippingPath(float width, float height, ContainerVisual rootVisual, bool invertPath) + private static (SKPath nativeClipPath, List nativeVisualsInZOrder) CalculateClippingPath(float width, float height, ContainerVisual rootVisual, bool invertPath) { var clipPath = new SKPath(); @@ -84,11 +85,12 @@ private static SKPath CalculateClippingPath(float width, float height, Container parentClipPath.Rewind(); parentClipPath.AddRect(rect); - rootVisual.GetNativeViewPath(parentClipPath, clipPath); + var nativeVisualsInZOrder = new List(); + rootVisual.GetNativeViewPathAndZOrder(parentClipPath, clipPath, nativeVisualsInZOrder); if (!invertPath) { - return clipPath; + return (clipPath, nativeVisualsInZOrder); } else { @@ -98,7 +100,7 @@ private static SKPath CalculateClippingPath(float width, float height, Container clipPath.Dispose(); - return invertedPath; + return (invertedPath, nativeVisualsInZOrder); } } diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.skia.cs b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.skia.cs index 7208fc3f7fed..f80577a2543a 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.skia.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.skia.cs @@ -1,12 +1,15 @@ using System; +using System.Buffers; using System.Collections.Generic; -using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using Uno.Disposables; using Uno.Foundation.Extensibility; using Uno.Foundation.Logging; using Uno.UI; using Windows.Foundation; +using Microsoft.UI.Composition; +using Microsoft.UI.Xaml.Media; namespace Microsoft.UI.Xaml.Controls; @@ -15,10 +18,8 @@ partial class ContentPresenter private Lazy _nativeElementHostingExtension; private static readonly HashSet _nativeHosts = new(); - private Rect _lastFinalRect; -#if DEBUG + private (Rect layoutRect, int zOrder)? _lastNativeArrangeArgs; private bool _nativeElementAttached; -#endif internal static bool HasNativeElements() => _nativeHosts.Count > 0; @@ -42,8 +43,6 @@ partial void InitializePlatform() }); } - private IDisposable _nativeElementDisposable; - partial void TryRegisterNativeElement(object oldValue, object newValue) { if (IsNativeHost && IsInLiveTree) @@ -78,76 +77,28 @@ partial void TryRegisterNativeElement(object oldValue, object newValue) } } - private void ArrangeNativeElement() - { - if (!IsNativeHost) - { - // the ArrangeNativeElement call is queued on the dispatcher, so by the time we get here, the ContentPresenter - // might no longer be a NativeHost - return; - } - var arrangeRect = this.GetAbsoluteBoundsRect(); - var ev = GetParentViewport().Effective; - - Rect clipRect; - if (ev.IsEmpty) - { - clipRect = new Rect(0, 0, 0, 0); - } - else if (ev.IsInfinite) - { - clipRect = null; - } - else - { - var top = Math.Min(Math.Max(0, ev.Y), ActualHeight); - var height = Math.Max(0, Math.Min(ev.Height + ev.Y, ActualHeight - top)); - var left = Math.Min(Math.Max(0, ev.X), ActualWidth); - var width = Math.Max(0, Math.Min(ev.Width + ev.X, ActualWidth - left)); - clipRect = new Rect(left, top, width, height); - } - - var clipInGlobalCoordinates = new Rect( - arrangeRect.X + clipRect.X, - arrangeRect.Y + clipRect.Y, - clipRect.Width, - clipRect.Height); - _lastFinalRect = arrangeRect.IntersectWith(clipInGlobalCoordinates) ?? new Rect(arrangeRect.X, arrangeRect.Y, 0, 0); - - _nativeElementHostingExtension.Value!.ArrangeNativeElement( - Content, - arrangeRect, - clipRect); - } - partial void AttachNativeElement() { #if DEBUG global::System.Diagnostics.Debug.Assert(IsNativeHost && XamlRoot is not null && !_nativeElementAttached); - _nativeElementAttached = true; #endif + _nativeElementAttached = true; _nativeElementHostingExtension.Value!.AttachNativeElement(Content); _nativeHosts.Add(this); - EffectiveViewportChanged += OnEffectiveViewportChanged; - LayoutUpdated += OnLayoutUpdated; - var visiblityToken = RegisterPropertyChangedCallback(HitTestVisibilityProperty, OnHitTestVisiblityChanged); - _nativeElementDisposable = Disposable.Create(() => - { - UnregisterPropertyChangedCallback(HitTestVisibilityProperty, visiblityToken); - }); } partial void DetachNativeElement(object content) { #if DEBUG - global::System.Diagnostics.Debug.Assert(IsNativeHost && _nativeElementAttached); - _nativeElementAttached = false; + global::System.Diagnostics.Debug.Assert(IsNativeHost); #endif + _lastNativeArrangeArgs = null; _nativeHosts.Remove(this); - EffectiveViewportChanged -= OnEffectiveViewportChanged; - LayoutUpdated -= OnLayoutUpdated; - _nativeElementHostingExtension.Value!.DetachNativeElement(content); - _nativeElementDisposable?.Dispose(); + if (_nativeElementAttached) + { + _nativeElementAttached = false; + _nativeElementHostingExtension.Value!.DetachNativeElement(content); + } } private Size MeasureNativeElement(Size childMeasuredSize, Size availableSize) @@ -165,11 +116,6 @@ private Size MeasureNativeElement(Size childMeasuredSize, Size availableSize) return ret; } - private void OnHitTestVisiblityChanged(DependencyObject sender, DependencyProperty dp) - { - _nativeElementHostingExtension.Value!.ChangeNativeElementVisibility(Content, HitTestVisibility != HitTestability.Collapsed); - } - internal static void UpdateNativeHostContentPresentersOpacities() { foreach (var contentPresenter in _nativeHosts) @@ -186,19 +132,52 @@ internal static void UpdateNativeHostContentPresentersOpacities() } } - private void OnLayoutUpdated(object sender, object e) + /// + /// is read-only and won't be modified. + /// + internal static void OnNativeHostsRenderOrderChanged(List nativeVisualsInZOrder) { - // Not quite sure why we need to queue the arrange call, but the native element either explodes or doesn't - // respect alignments correctly otherwise. This is particularly relevant for the initial load. - DispatcherQueue.TryEnqueue(ArrangeNativeElement); - } + var rentedArray = ArrayPool<(int, ContentPresenter)>.Shared.Rent(_nativeHosts.Count); + using var _ = new DisposableStruct<(int, ContentPresenter)[]>(static rentedArray => ArrayPool<(int, ContentPresenter)>.Shared.Return(rentedArray, clearArray: true), rentedArray); - private void OnEffectiveViewportChanged(FrameworkElement sender, EffectiveViewportChangedEventArgs args) - { - global::System.Diagnostics.Debug.Assert(IsNativeHost); - // The arrange call here is queued because EVPChanged is fired before the layout of the ContentPresenter is updated, - // so calling ArrangeNativeElement synchronously would get outdated coordinates. - DispatcherQueue.TryEnqueue(ArrangeNativeElement); + var count = 0; + foreach (var host in _nativeHosts) + { + rentedArray[count++] = (nativeVisualsInZOrder.IndexOf(host.Visual), host); + } + new Span<(int, ContentPresenter)>(rentedArray, 0, _nativeHosts.Count).Sort((one, two) => one.Item1 - two.Item1); + + for (var index = 0; index < _nativeHosts.Count; index++) + { + var host = rentedArray[index].Item2; + + if (index == -1 && host._nativeElementAttached) + { + // We're detaching the native element as it's no longer in view, but conceptually, it's still in the tree, so IsNativeHost is still true + Debug.Assert(host.IsNativeHost); + host._nativeElementAttached = false; + host._lastNativeArrangeArgs = null; + host._nativeElementHostingExtension.Value!.DetachNativeElement(host.Content); + } + else if (host._nativeElementAttached) + { + host.DetachNativeElement(host.Content); + host.AttachNativeElement(); + ArrangeNativeElement(host, index); + } + } + + static void ArrangeNativeElement(ContentPresenter host, int zOrder) + { + var arrangeRect = host.GetAbsoluteBoundsRect(); + + var nativeArrangeArgs = (arrangeRect, zOrder); + if (host._lastNativeArrangeArgs != nativeArrangeArgs) + { + host._lastNativeArrangeArgs = nativeArrangeArgs; + host._nativeElementHostingExtension.Value!.ArrangeNativeElement(host.Content, arrangeRect); + } + } } internal object CreateSampleComponent(string text) diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/INativeElementHostingExtension.cs b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/INativeElementHostingExtension.cs index 180036ac11e5..0aa5d63e9d2f 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/INativeElementHostingExtension.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/INativeElementHostingExtension.cs @@ -13,14 +13,12 @@ internal interface INativeElementHostingExtension void DetachNativeElement(object content); - void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect); + void ArrangeNativeElement(object content, Rect arrangeRect); Size MeasureNativeElement(object content, Size childMeasuredSize, Size availableSize); object CreateSampleComponent(string text); - void ChangeNativeElementVisibility(object content, bool visible); - void ChangeNativeElementOpacity(object content, double opacity); #endif } diff --git a/src/Uno.UI/UI/Xaml/Media/CompositionTarget.Rendering.skia.cs b/src/Uno.UI/UI/Xaml/Media/CompositionTarget.Rendering.skia.cs index a8719bc4a2ac..c4aac9223161 100644 --- a/src/Uno.UI/UI/Xaml/Media/CompositionTarget.Rendering.skia.cs +++ b/src/Uno.UI/UI/Xaml/Media/CompositionTarget.Rendering.skia.cs @@ -1,9 +1,12 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using Windows.Foundation; +using Microsoft.UI.Composition; +using Microsoft.UI.Xaml.Controls; using SkiaSharp; using Uno.Foundation.Logging; using Uno.UI.Composition; @@ -38,6 +41,8 @@ public partial class CompositionTarget private Size _xamlRootBounds; // only set and read under _xamlRootBoundsGate private float _xamlRootRasterizationScale; + // only set and read on the UI thread + private List _nativeVisualsInZOrder = new(); internal event Action? FrameRendered; @@ -78,11 +83,12 @@ private void Render() var rootElement = ContentRoot.VisualTree.RootElement; var bounds = ContentRoot.VisualTree.Size; - var renderedFrame = SkiaRenderHelper.RecordPictureAndReturnPath( + var (picture, path, nativeVisualsInZOrder) = SkiaRenderHelper.RecordPictureAndReturnPath( (float)bounds.Width, (float)bounds.Height, rootElement.Visual, invertPath: FrameRenderingOptions.invertNativeElementClipPath); + var renderedFrame = (picture, path); var previousFrame = default((IntPtr frame, SKPath path)?); lock (_frameGate) { @@ -108,6 +114,25 @@ private void Render() XamlRootMap.GetHostForRoot(rootElement.XamlRoot)?.InvalidateRender(); } + var nativeVisualsZOrderChanged = _nativeVisualsInZOrder.Count != nativeVisualsInZOrder.Count; + if (!nativeVisualsZOrderChanged) + { + for (int i = 0; i < nativeVisualsInZOrder.Count; i++) + { + if (nativeVisualsInZOrder[i] != _nativeVisualsInZOrder[i]) + { + nativeVisualsZOrderChanged = true; + break; + } + } + } + + if (nativeVisualsZOrderChanged) + { + _nativeVisualsInZOrder = nativeVisualsInZOrder; + ContentPresenter.OnNativeHostsRenderOrderChanged(nativeVisualsInZOrder); + } + this.LogTrace()?.Trace($"CompositionTarget#{GetHashCode()}: {nameof(Render)} ends"); }