From 66daa6504568f4f0588f85088fc53c47a0ac5e14 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Fri, 28 Jun 2024 09:00:05 -0400 Subject: [PATCH 01/17] feat: Add native embedding to netXX-desktop/macOS --- .../MacOSNativeElementHostingExtension.cs | 154 ++++++++++++++++++ src/Uno.UI.Runtime.Skia.MacOS/MacSkiaHost.cs | 1 + src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs | 12 ++ .../UnoNativeMac.xcodeproj/project.pbxproj | 6 + .../UnoNativeMac/UnoNativeMac/UNONative.h | 19 +++ .../UnoNativeMac/UnoNativeMac/UNONative.m | 60 +++++++ 6 files changed, 252 insertions(+) create mode 100644 src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs create mode 100644 src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h create mode 100644 src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs new file mode 100644 index 000000000000..7a7b02e46764 --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs @@ -0,0 +1,154 @@ +#nullable enable + +using Windows.Foundation; +using Windows.UI.Core; +using Microsoft.UI.Xaml.Controls; + +using Uno.Foundation.Extensibility; +using Uno.Foundation.Logging; + +namespace Uno.UI.Runtime.Skia.MacOS; + +internal class MacOSNativeElement +{ + internal MacOSNativeElement(nint handle) + { + Handle = handle; + } + + public nint Handle { get; private set; } +} + +internal class MacOSNativeElementHostingExtension : ContentPresenter.INativeElementHostingExtension +{ + private readonly ContentPresenter _presenter; + private readonly MacOSWindowNative? _window; + + private MacOSNativeElementHostingExtension(ContentPresenter contentPresenter) + { + _presenter = contentPresenter; + _window = _presenter.XamlRoot?.HostWindow?.NativeWindow as MacOSWindowNative; + } + + public static void Register() => ApiExtensibility.Register(typeof(ContentPresenter.INativeElementHostingExtension), o => new MacOSNativeElementHostingExtension(o)); + + public void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect) + { + if (content is MacOSNativeElement element) + { + // TODO uno_native_arrange(element.Handle, arrangeRect.Left, arrangeRect.Top, arrangeRect.Width, arrangeRect.Height, clipRect.Left, clipRect.Top, clipRect.Width, clipRect.Height); + } + else + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); + } + } + } + + public void AttachNativeElement(object content) + { + if (content is MacOSNativeElement element) + { + // TODO uno_native_attach(element.Handle); + } + else + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); + } + } + } + + public void ChangeNativeElementOpacity(object content, double opacity) + { + if (content is MacOSNativeElement element) + { + // https://developer.apple.com/documentation/appkit/nsview/1483560-alphavalue?language=objc + // note: no marshaling needed as CGFloat is double for 64bits apps + NativeUno.uno_native_set_opacity(element.Handle, opacity); + } + else + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); + } + } + } + + 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.Handle, visible); + } + else + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); + } + } + } + + public object? CreateSampleComponent(string text) + { + var handle = NativeUno.uno_native_create_sample(_window!.Handle, text); + return new MacOSNativeElement(handle); + } + + public void DetachNativeElement(object content) + { + if (content is MacOSNativeElement element) + { + // TODO uno_native_detach(element.Handle); + } + else + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); + } + } + } + + public bool IsNativeElement(object content) => content is MacOSNativeElement; + + public bool IsNativeElementAttached(object owner, object nativeElement) + { + if (nativeElement is MacOSNativeElement element) + { + // TODO uno_native_is_attached(element.Handle); + return false; + } + else + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().Warn($"Object `{nameof(owner)}` is a {owner.GetType().FullName} and not a MacOSNativeElement subclass."); + } + return false; + } + } + + public Size MeasureNativeElement(object content, Size childMeasuredSize, Size availableSize) + { + if (content is MacOSNativeElement element) + { + NativeUno.uno_native_measure(element.Handle, childMeasuredSize.Width, childMeasuredSize.Height, availableSize.Width, availableSize.Height, out var width, out var height); + return new Size(width, height); + } + else + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); + } + return Size.Empty; + } + } +} diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacSkiaHost.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacSkiaHost.cs index 6aedc2abd791..28ed27db81e7 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacSkiaHost.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacSkiaHost.cs @@ -25,6 +25,7 @@ static MacSkiaHost() MacOSFileSavePickerExtension.Register(); MacOSFolderPickerExtension.Register(); MacOSLauncherExtension.Register(); + MacOSNativeElementHostingExtension.Register(); MacOSNativeWindowFactoryExtension.Register(); MacOSSystemNavigationManagerPreviewExtension.Register(); MacOSSystemThemeHelperExtension.Register(); diff --git a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs index a3a4835f4308..f19766f53097 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs @@ -306,4 +306,16 @@ internal static unsafe partial void uno_set_window_close_callbacks( [LibraryImport("libUnoNativeMac.dylib")] [return: MarshalAs(UnmanagedType.I1)] internal static partial bool uno_cursor_set(CoreCursorType cursorType); + + [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] + internal static partial nint uno_native_create_sample(nint window, string text); + + [LibraryImport("libUnoNativeMac.dylib")] + 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); + + [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); } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj index 7dbcf6cb66ec..0be42284d811 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ D116C63E2AC79876004B975F /* UNOCursor.m in Sources */ = {isa = PBXBuildFile; fileRef = D116C63D2AC79876004B975F /* UNOCursor.m */; }; D13AB8A82B5839B200693F8E /* libSkiaSharp.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D13AB8A72B5839B200693F8E /* libSkiaSharp.dylib */; }; + D18D4FAC2C2DE804003E4BBF /* UNONative.m in Sources */ = {isa = PBXBuildFile; fileRef = D18D4FAB2C2DE804003E4BBF /* UNONative.m */; }; D1A0651E2A7066F700101BE6 /* UNOMetalViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D1A0651D2A7066F700101BE6 /* UNOMetalViewDelegate.m */; }; D1A065202A8467B200101BE6 /* UNOApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D1A0651F2A8467B200101BE6 /* UNOApplication.h */; }; D1A065222A84688000101BE6 /* UNOApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = D1A065212A84688000101BE6 /* UNOApplication.m */; }; @@ -24,6 +25,8 @@ D116C63D2AC79876004B975F /* UNOCursor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UNOCursor.m; sourceTree = ""; }; D13AB8A72B5839B200693F8E /* libSkiaSharp.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libSkiaSharp.dylib; path = UnoNativeMac/libSkiaSharp.dylib; sourceTree = ""; }; D13AB8AC2B58566400693F8E /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; + D18D4FAA2C2DE76F003E4BBF /* UNONative.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UNONative.h; sourceTree = ""; }; + D18D4FAB2C2DE804003E4BBF /* UNONative.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UNONative.m; sourceTree = ""; }; D1A0651C2A70664A00101BE6 /* UNOMetalViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UNOMetalViewDelegate.h; sourceTree = ""; }; D1A0651D2A7066F700101BE6 /* UNOMetalViewDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UNOMetalViewDelegate.m; sourceTree = ""; }; D1A0651F2A8467B200101BE6 /* UNOApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UNOApplication.h; sourceTree = ""; }; @@ -90,6 +93,8 @@ D116C63D2AC79876004B975F /* UNOCursor.m */, D1A0651C2A70664A00101BE6 /* UNOMetalViewDelegate.h */, D1A0651D2A7066F700101BE6 /* UNOMetalViewDelegate.m */, + D18D4FAA2C2DE76F003E4BBF /* UNONative.h */, + D18D4FAB2C2DE804003E4BBF /* UNONative.m */, D1CC768E2ABA1368002A44F0 /* UNOPickers.h */, D1CC768C2ABA1337002A44F0 /* UNOPickers.m */, D1FE7A2C2B75C8E100ACFC76 /* UNOSoftView.h */, @@ -176,6 +181,7 @@ D1A065252A8AC23800101BE6 /* UNOWindow.m in Sources */, D1CC768B2AB9D464002A44F0 /* UNOClipboard.m in Sources */, D116C63E2AC79876004B975F /* UNOCursor.m in Sources */, + D18D4FAC2C2DE804003E4BBF /* UNONative.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h new file mode 100644 index 000000000000..1a5c33744f1d --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h @@ -0,0 +1,19 @@ +// +// UNONative.h +// + +#pragma once + +#import "UnoNativeMac.h" + +NS_ASSUME_NONNULL_BEGIN + +NSView* uno_native_create_sample(NSWindow *window, const char* _Nullable text); + +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_visibility(NSView* element, bool visible); + +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 new file mode 100644 index 000000000000..c4eec93415e5 --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m @@ -0,0 +1,60 @@ +// +// UNONative.m +// + +#import "UNONative.h" + +static NSMutableSet *elements; + +NSView* uno_native_create_sample(NSWindow *window, const char* _Nullable text) +{ + // no NSLabel on macOS + NSTextField* label = [[NSTextField alloc] initWithFrame:NSMakeRect(100, 300, 200, 200)]; + label.bezeled = NO; + label.drawsBackground = NO; + label.editable = NO; + label.selectable = NO; +#if DEBUG + NSLog(@"uno_native_create_sample label #%p : %s", label, text); +#endif + label.stringValue = [NSString stringWithUTF8String:text]; + + NSView* sample = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 300, 600)]; + [sample addSubview:label]; +#if DEBUG + NSLog(@"uno_native_create_sample #%p : %@", sample, label.stringValue); +#endif + [window.contentViewController.view addSubview:sample]; + if (!elements) { + elements = [[NSMutableSet alloc] initWithCapacity:10]; + } + [elements addObject:sample]; + return sample; +} + +void uno_native_measure(NSView* element, double childWidth, double childHeight, double availableWidth, double availableHeight, double* width, double* height) +{ + // FIXME + CGSize size = element.frame.size; // element.fittingSize; + *width = size.width; + *height = size.height; +#if DEBUG + NSLog(@"uno_native_measure #%p : child %g x %g / available %g x %g -> %g x %g", element, childWidth, childHeight, availableWidth, availableHeight, *width, *height); +#endif +} + +void uno_native_set_opacity(NSView* element, double opacity) +{ +#if DEBUG + NSLog(@"uno_native_set_opacity #%p : %g -> %g", element, element.alphaValue, opacity); +#endif + element.alphaValue = opacity; +} + +void uno_native_set_visibility(NSView* element, bool visible) +{ +#if DEBUG + NSLog(@"uno_native_set_visibility #%p : %s -> %s", element, element.hidden ? "TRUE" : "FALSE", visible ? "TRUE" : "FALSE"); +#endif + element.hidden = !visible; +} From 14c0f993f8f9617aeaa790c54c1b7860b1defa2a Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Thu, 4 Jul 2024 13:55:53 -0400 Subject: [PATCH 02/17] chore: rework logging code --- .../MacOSNativeElementHostingExtension.cs | 64 ++++++++----------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs index 7a7b02e46764..002611d2a871 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs @@ -38,12 +38,9 @@ public void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect { // TODO uno_native_arrange(element.Handle, arrangeRect.Left, arrangeRect.Top, arrangeRect.Width, arrangeRect.Height, clipRect.Left, clipRect.Top, clipRect.Width, clipRect.Height); } - else + else if (this.Log().IsEnabled(LogLevel.Debug)) { - if (this.Log().IsEnabled(LogLevel.Warning)) - { - this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); - } + this.Log().Debug($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); } } @@ -53,12 +50,9 @@ public void AttachNativeElement(object content) { // TODO uno_native_attach(element.Handle); } - else + else if (this.Log().IsEnabled(LogLevel.Debug)) { - if (this.Log().IsEnabled(LogLevel.Warning)) - { - this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); - } + this.Log().Debug($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); } } @@ -70,12 +64,9 @@ public void ChangeNativeElementOpacity(object content, double opacity) // note: no marshaling needed as CGFloat is double for 64bits apps NativeUno.uno_native_set_opacity(element.Handle, opacity); } - else + else if (this.Log().IsEnabled(LogLevel.Debug)) { - if (this.Log().IsEnabled(LogLevel.Warning)) - { - this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); - } + this.Log().Debug($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); } } @@ -86,18 +77,24 @@ public void ChangeNativeElementVisibility(object content, bool visible) // https://developer.apple.com/documentation/appkit/nsview/1483369-hidden?language=objc NativeUno.uno_native_set_visibility(element.Handle, visible); } - else + else if (this.Log().IsEnabled(LogLevel.Debug)) { - if (this.Log().IsEnabled(LogLevel.Warning)) - { - this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); - } + this.Log().Debug($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); } } public object? CreateSampleComponent(string text) { - var handle = NativeUno.uno_native_create_sample(_window!.Handle, text); + if (_window is null) + { + if (this.Log().IsEnabled(LogLevel.Debug)) + { + this.Log().Debug($"CreateSampleComponent failed as no MacOSWindowNative could be found."); + } + return null; + } + + var handle = NativeUno.uno_native_create_sample(_window.Handle, text); return new MacOSNativeElement(handle); } @@ -107,12 +104,9 @@ public void DetachNativeElement(object content) { // TODO uno_native_detach(element.Handle); } - else + else if (this.Log().IsEnabled(LogLevel.Debug)) { - if (this.Log().IsEnabled(LogLevel.Warning)) - { - this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); - } + this.Log().Debug($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); } } @@ -125,14 +119,11 @@ public bool IsNativeElementAttached(object owner, object nativeElement) // TODO uno_native_is_attached(element.Handle); return false; } - else + else if (this.Log().IsEnabled(LogLevel.Debug)) { - if (this.Log().IsEnabled(LogLevel.Warning)) - { - this.Log().Warn($"Object `{nameof(owner)}` is a {owner.GetType().FullName} and not a MacOSNativeElement subclass."); - } - return false; + this.Log().Debug($"Object `{nameof(owner)}` is a {owner.GetType().FullName} and not a MacOSNativeElement subclass."); } + return false; } public Size MeasureNativeElement(object content, Size childMeasuredSize, Size availableSize) @@ -142,13 +133,10 @@ public Size MeasureNativeElement(object content, Size childMeasuredSize, Size av NativeUno.uno_native_measure(element.Handle, childMeasuredSize.Width, childMeasuredSize.Height, availableSize.Width, availableSize.Height, out var width, out var height); return new Size(width, height); } - else + else if (this.Log().IsEnabled(LogLevel.Debug)) { - if (this.Log().IsEnabled(LogLevel.Warning)) - { - this.Log().Warn($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); - } - return Size.Empty; + this.Log().Debug($"Object `{nameof(content)}` is a {content.GetType().FullName} and not a MacOSNativeElement subclass."); } + return Size.Empty; } } From f606e5f67ce98baea512a35c647e8c153a66d7d5 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Thu, 4 Jul 2024 21:35:46 -0400 Subject: [PATCH 03/17] chore: change sample native element background to red --- .../MacOSNativeElementHostingExtension.cs | 21 +++--- src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs | 13 ++++ .../UnoNativeMac/UnoNativeMac/UNONative.h | 14 ++++ .../UnoNativeMac/UnoNativeMac/UNONative.m | 68 ++++++++++++++++--- 4 files changed, 97 insertions(+), 19 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs index 002611d2a871..cf7e06aca489 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs @@ -9,14 +9,14 @@ namespace Uno.UI.Runtime.Skia.MacOS; -internal class MacOSNativeElement +internal class MacOSNativeElement : Microsoft.UI.Xaml.FrameworkElement { internal MacOSNativeElement(nint handle) { - Handle = handle; + NativeHandle = handle; } - public nint Handle { get; private set; } + public nint NativeHandle { get; private set; } } internal class MacOSNativeElementHostingExtension : ContentPresenter.INativeElementHostingExtension @@ -36,7 +36,7 @@ public void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect { if (content is MacOSNativeElement element) { - // TODO uno_native_arrange(element.Handle, 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, clipRect.Left, clipRect.Top, clipRect.Width, clipRect.Height); } else if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -48,7 +48,7 @@ public void AttachNativeElement(object content) { if (content is MacOSNativeElement element) { - // TODO uno_native_attach(element.Handle); + NativeUno.uno_native_attach(element.NativeHandle); } else if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -62,7 +62,7 @@ public void ChangeNativeElementOpacity(object content, double opacity) { // https://developer.apple.com/documentation/appkit/nsview/1483560-alphavalue?language=objc // note: no marshaling needed as CGFloat is double for 64bits apps - NativeUno.uno_native_set_opacity(element.Handle, opacity); + NativeUno.uno_native_set_opacity(element.NativeHandle, opacity); } else if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -75,7 +75,7 @@ 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.Handle, visible); + NativeUno.uno_native_set_visibility(element.NativeHandle, visible); } else if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -102,7 +102,7 @@ public void DetachNativeElement(object content) { if (content is MacOSNativeElement element) { - // TODO uno_native_detach(element.Handle); + NativeUno.uno_native_detach(element.NativeHandle); } else if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -116,8 +116,7 @@ public bool IsNativeElementAttached(object owner, object nativeElement) { if (nativeElement is MacOSNativeElement element) { - // TODO uno_native_is_attached(element.Handle); - return false; + return NativeUno.uno_native_is_attached(element.NativeHandle); } else if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -130,7 +129,7 @@ public Size MeasureNativeElement(object content, Size childMeasuredSize, Size av { if (content is MacOSNativeElement element) { - NativeUno.uno_native_measure(element.Handle, childMeasuredSize.Width, childMeasuredSize.Height, availableSize.Width, availableSize.Height, out var width, out var height); + NativeUno.uno_native_measure(element.NativeHandle, childMeasuredSize.Width, childMeasuredSize.Height, availableSize.Width, availableSize.Height, out var width, out var height); return new Size(width, height); } else if (this.Log().IsEnabled(LogLevel.Debug)) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs index f19766f53097..7b6e52708999 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs @@ -310,6 +310,19 @@ internal static unsafe partial void uno_set_window_close_callbacks( [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] 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); + + [LibraryImport("libUnoNativeMac.dylib")] + internal static partial void uno_native_attach(nint element); + + [LibraryImport("libUnoNativeMac.dylib")] + internal static partial void uno_native_detach(nint element); + + [LibraryImport("libUnoNativeMac.dylib")] + [return: MarshalAs(UnmanagedType.I1)] + internal static partial bool uno_native_is_attached(nint element); + [LibraryImport("libUnoNativeMac.dylib")] internal static partial void uno_native_set_opacity(nint element, double opacity); 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 1a5c33744f1d..5438df472ac9 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h @@ -8,8 +8,22 @@ NS_ASSUME_NONNULL_BEGIN +@interface UNOFlippedView : NSView + +@property(getter=isFlipped, readonly) BOOL flipped; + +@end + 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_attach(NSView* element); + +void uno_native_detach(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_set_opacity(NSView* element, double opacity); 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 c4eec93415e5..a7b63647de2c 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m @@ -6,23 +6,41 @@ static NSMutableSet *elements; +@implementation UNOFlippedView : NSView + +// behave like UIView (top/left) instead of bottom/left +-(BOOL) isFlipped { + return YES; +} + +// make the background red for easier tracking +- (BOOL)wantsUpdateLayer +{ + return YES; +} + +- (void)updateLayer +{ + self.layer.backgroundColor = NSColor.redColor.CGColor; +} + +@end + NSView* uno_native_create_sample(NSWindow *window, const char* _Nullable text) { // no NSLabel on macOS - NSTextField* label = [[NSTextField alloc] initWithFrame:NSMakeRect(100, 300, 200, 200)]; + NSTextField* label = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; label.bezeled = NO; label.drawsBackground = NO; label.editable = NO; label.selectable = NO; -#if DEBUG - NSLog(@"uno_native_create_sample label #%p : %s", label, text); -#endif label.stringValue = [NSString stringWithUTF8String:text]; + label.frame = NSMakeRect(0, 0, label.fittingSize.width, label.fittingSize.height); - NSView* sample = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 300, 600)]; + NSView* sample = [[UNOFlippedView alloc] initWithFrame:label.frame]; [sample addSubview:label]; #if DEBUG - NSLog(@"uno_native_create_sample #%p : %@", sample, label.stringValue); + NSLog(@"uno_native_create_sample #%p label: %@", sample, label.stringValue); #endif [window.contentViewController.view addSubview:sample]; if (!elements) { @@ -32,14 +50,48 @@ return sample; } +void uno_native_arrange(NSView *element, double arrangeLeft, double arrangeTop, double arrangeWidth, double arrangeHeight, double clipLeft, double clipTop, double clipWidth, double clipHeight) +{ +#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); +#endif + element.frame = NSMakeRect(arrangeLeft, arrangeTop, arrangeWidth, arrangeWidth); + // TODO +} + +void uno_native_attach(NSView* element) +{ +#if DEBUG + NSLog(@"uno_native_attach %p", element); +#endif + // TODO +} + +void uno_native_detach(NSView *element) +{ +#if DEBUG + NSLog(@"uno_native_detach %p", element); +#endif + // TODO +} + +bool uno_native_is_attached(NSView* element) +{ + bool attached = false; // TODO +#if DEBUG + NSLog(@"uno_native_is_attached %s", attached ? "TRUE" : "FALSE"); +#endif + return attached; +} + void uno_native_measure(NSView* element, double childWidth, double childHeight, double availableWidth, double availableHeight, double* width, double* height) { // FIXME - CGSize size = element.frame.size; // element.fittingSize; + CGSize size = element.subviews.firstObject.frame.size; // element.fittingSize; *width = size.width; *height = size.height; #if DEBUG - NSLog(@"uno_native_measure #%p : child %g x %g / available %g x %g -> %g x %g", element, childWidth, childHeight, availableWidth, availableHeight, *width, *height); + NSLog(@"uno_native_measure %p : child %g x %g / available %g x %g -> %g x %g", element, childWidth, childHeight, availableWidth, availableHeight, *width, *height); #endif } From 7496a4fba76ff0ca15fdaa9597a327cd3843d6d2 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Fri, 12 Jul 2024 10:53:35 -0400 Subject: [PATCH 04/17] chore: keep a ref when attaching, drop it when detaching --- .../UnoNativeMac/UnoNativeMac/UNONative.m | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) 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 a7b63647de2c..3bdbcad098e8 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m @@ -8,7 +8,7 @@ @implementation UNOFlippedView : NSView -// behave like UIView (top/left) instead of bottom/left +// behave like UIView/UWP/WinUI, where the origin is top/left, instead of bottom/left -(BOOL) isFlipped { return YES; } @@ -43,10 +43,6 @@ - (void)updateLayer NSLog(@"uno_native_create_sample #%p label: %@", sample, label.stringValue); #endif [window.contentViewController.view addSubview:sample]; - if (!elements) { - elements = [[NSMutableSet alloc] initWithCapacity:10]; - } - [elements addObject:sample]; return sample; } @@ -62,9 +58,12 @@ void uno_native_arrange(NSView *element, double arrangeLeft, double arrangeTop, void uno_native_attach(NSView* element) { #if DEBUG - NSLog(@"uno_native_attach %p", element); + NSLog(@"uno_native_attach %p -> %s attached", element, [elements containsObject:element] ? "already" : "not"); #endif - // TODO + if (!elements) { + elements = [[NSMutableSet alloc] initWithCapacity:10]; + } + [elements addObject:element]; } void uno_native_detach(NSView *element) @@ -72,14 +71,16 @@ void uno_native_detach(NSView *element) #if DEBUG NSLog(@"uno_native_detach %p", element); #endif - // TODO + if (elements) { + [elements removeObject:element]; + } } bool uno_native_is_attached(NSView* element) { - bool attached = false; // TODO + bool attached = elements ? [elements containsObject:element] : NO; #if DEBUG - NSLog(@"uno_native_is_attached %s", attached ? "TRUE" : "FALSE"); + NSLog(@"uno_native_is_attached %s", attached ? "YES" : "NO"); #endif return attached; } From bd0aae9f7f92be549a4604ac03e37319f4e4356c Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Mon, 15 Jul 2024 22:02:54 -0400 Subject: [PATCH 05/17] chore: fix flipped view and plugin svg clipping code (not yet working) --- .../MacOSWindowHost.cs | 23 ++++++-- src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs | 3 + .../UnoNativeMac/UnoNativeMac/UNONative.h | 4 +- .../UnoNativeMac/UnoNativeMac/UNONative.m | 9 +-- .../UnoNativeMac/UnoNativeMac/UNOWindow.h | 8 +++ .../UnoNativeMac/UnoNativeMac/UNOWindow.m | 57 ++++++++++++++++++- 6 files changed, 89 insertions(+), 15 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs index 44bd2e8e8310..0d257e07a5b8 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs @@ -16,6 +16,7 @@ using Uno.Foundation.Extensibility; using Uno.Foundation.Logging; +using Uno.UI.Helpers; using Uno.UI.Hosting; namespace Uno.UI.Runtime.Skia.MacOS; @@ -68,7 +69,7 @@ private void UpdateWindowSize(double nativeWidth, double nativeHeight) SizeChanged?.Invoke(this, new Size(nativeWidth, nativeHeight)); } - private void Draw(SKSurface surface) + private void Draw(double nativeWidth, double nativeHeight, SKSurface surface) { using var canvas = surface.Canvas; using (new SKAutoCanvasRestore(canvas, true)) @@ -77,7 +78,21 @@ private void Draw(SKSurface surface) if (RootElement?.Visual is { } rootVisual) { - RootElement.XamlRoot?.Compositor.RenderRootVisual(surface, rootVisual, null); + // remove previous clipping (if any) + NativeUno.uno_window_clip_svg(_nativeWindow.Handle, null); + int width = (int)nativeWidth; + int height = (int)nativeHeight; + var path = SkiaRenderHelper.RenderRootVisualAndReturnPath(width, height, rootVisual, surface); + // we clip the "negative" of what was drawn + if (path is { }) + { + using var negativePath = new SKPath(); + negativePath.AddRect(new SKRect(0, 0, width, height)); + using var diffPath = negativePath.Op(path, SKPathOp.Difference); + // FIXME: clipping not working correctly, comment next line to get something better + // note: use an online svg viewer to visualize the clipping path + NativeUno.uno_window_clip_svg(_nativeWindow.Handle, diffPath.ToSvgPathData()); + } } } @@ -112,7 +127,7 @@ private void MetalDraw(double nativeWidth, double nativeHeight, nint texture) surface.Canvas.Scale(scale, scale); - Draw(surface); + Draw(nativeWidth, nativeHeight, surface); _context?.Flush(); } @@ -152,7 +167,7 @@ private unsafe void SoftDraw(double nativeWidth, double nativeHeight, nint* data _rowBytes = info.RowBytes; } - Draw(_surface!); + Draw(nativeWidth, nativeHeight, _surface!); *data = _bitmap.GetPixels(out var bitmapSize); *size = (int)bitmapSize; diff --git a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs index 7b6e52708999..cfd82446ed0d 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs @@ -263,6 +263,9 @@ internal static unsafe partial void uno_set_window_close_callbacks( [LibraryImport("libUnoNativeMac.dylib")] internal static partial void uno_window_set_min_size(nint window, double width, double height); + [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] + internal static partial void uno_window_clip_svg(nint window, string? svg); + [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] internal static partial string? /* const char* _Nullable */ uno_pick_single_folder(string? prompt, string? identifier, int suggestedStartLocation); 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 5438df472ac9..d2e229a539ea 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h @@ -8,9 +8,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface UNOFlippedView : NSView - -@property(getter=isFlipped, readonly) BOOL flipped; +@interface UNORedView : NSView @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 3bdbcad098e8..61401cecd3c5 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m @@ -6,12 +6,7 @@ static NSMutableSet *elements; -@implementation UNOFlippedView : NSView - -// behave like UIView/UWP/WinUI, where the origin is top/left, instead of bottom/left --(BOOL) isFlipped { - return YES; -} +@implementation UNORedView : NSView // make the background red for easier tracking - (BOOL)wantsUpdateLayer @@ -37,7 +32,7 @@ - (void)updateLayer label.stringValue = [NSString stringWithUTF8String:text]; label.frame = NSMakeRect(0, 0, label.fittingSize.width, label.fittingSize.height); - NSView* sample = [[UNOFlippedView alloc] initWithFrame:label.frame]; + NSView* sample = [[UNORedView alloc] initWithFrame:label.frame]; [sample addSubview:label]; #if DEBUG NSLog(@"uno_native_create_sample #%p label: %@", sample, label.stringValue); diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.h b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.h index acbbbb759f1c..be9aa9b9c0f5 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.h @@ -53,6 +53,7 @@ void uno_window_exit_full_screen(NSWindow *window); void uno_window_minimize(NSWindow *window, bool activateWindow); void uno_window_restore(NSWindow *window, bool activateWindow); +void uno_window_clip_svg(UNOWindow* window, const char* svg); typedef NS_ENUM(sint32, OverlappedPresenterState) { OverlappedPresenterStateMaximized, @@ -322,4 +323,11 @@ void uno_set_window_screen_change_callbacks(window_did_change_screen_fn_ptr scre void uno_window_notify_screen_change(NSWindow *window); + +@interface UNOMetalFlippedView : MTKView + +@property(getter=isFlipped, readonly) BOOL flipped; + +@end + NS_ASSUME_NONNULL_END diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m index a29aa0ff8f36..91d57cd20544 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m @@ -87,6 +87,15 @@ - (void) applicationDidChangeScreenParametersNotification:(NSNotification*) note return main_window; } +@implementation UNOMetalFlippedView : MTKView + +// behave like UIView/UWP/WinUI, where the origin is top/left, instead of bottom/left +-(BOOL) isFlipped { + return YES; +} + +@end + NSWindow* uno_window_create(double width, double height) { CGRect size = NSMakeRect(0, 0, width, height); @@ -98,7 +107,7 @@ - (void) applicationDidChangeScreenParametersNotification:(NSNotification*) note id device = uno_application_get_metal_device(); if (device) { - MTKView *v = [[MTKView alloc] initWithFrame:size device:device]; + UNOMetalFlippedView *v = [[UNOMetalFlippedView alloc] initWithFrame:size device:device]; v.enableSetNeedsDisplay = YES; window.metalViewDelegate = [[UNOMetalViewDelegate alloc] initWithMetalKitView:v]; v.delegate = window.metalViewDelegate; @@ -589,6 +598,52 @@ void uno_set_window_close_callbacks(window_should_close_fn_ptr shouldClose, wind return gr_direct_context_make_metal(device, queue); } +void uno_window_clip_svg(UNOWindow* window, const char* svg) +{ + if (svg) { +#if DEBUG + NSLog(@"uno_window_clip_svg %@ %@ %s", window, window.contentView.layer.description, svg); +#endif + // FIXME: convert SVG string into a CGMutablePathRef, what's below is hardcoded to the initiall values + CGMutablePathRef path = CGPathCreateMutable(); + // M613 246L776 246L776 261L613 261L613 246Z + CGPathMoveToPoint(path, nil, 613, 246); + CGPathAddLineToPoint(path, nil, 776, 246); + CGPathAddLineToPoint(path, nil, 776, 261); + CGPathAddLineToPoint(path, nil, 613, 261); + CGPathAddLineToPoint(path, nil, 613, 246); + CGPathCloseSubpath(path); + // M22 323L32 323L32 338L22 338L22 323Z + CGPathMoveToPoint(path, nil, 22, 323); + CGPathAddLineToPoint(path, nil, 32, 323); + CGPathAddLineToPoint(path, nil, 32, 338); + CGPathAddLineToPoint(path, nil, 22, 338); + CGPathAddLineToPoint(path, nil, 22, 323); + CGPathCloseSubpath(path); + // M34 523L22 523L22 538L34 538L34 523Z + CGPathMoveToPoint(path, nil, 34, 523); + CGPathAddLineToPoint(path, nil, 22, 523); + CGPathAddLineToPoint(path, nil, 22, 538); + CGPathAddLineToPoint(path, nil, 34, 538); + CGPathAddLineToPoint(path, nil, 34, 523); + CGPathCloseSubpath(path); + + // TODO make a CAShapeLayer out of the given SVG, note: we already have a CAMetalLayer present + CAShapeLayer* layer = nil; + if (!window.contentView.layer.sublayers) { + layer = [CAShapeLayer layer]; + [window.contentView.layer addSublayer:layer]; + } + layer.path = path; + layer.fillRule = kCAFillRuleEvenOdd; + } else { +#if DEBUG + NSLog(@"uno_window_clip_svg %@ nil - reseting layer %@ mask to nil", window, window.contentView.layer.description); +#endif + window.contentView.layer.sublayers = nil; + } +} + @implementation UNOWindow : NSWindow + (void)initialize { From 313e2cf2f3c758ded3facc6555cc4545bf2e8e3a Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Tue, 16 Jul 2024 14:02:56 -0400 Subject: [PATCH 06/17] chore: fix clipping removal --- .../MacOSWindowHost.cs | 1 - .../UnoNativeMac/UnoNativeMac/UNOWindow.h | 2 ++ .../UnoNativeMac/UnoNativeMac/UNOWindow.m | 23 ++++++++++++------- .../UnoNativeMac/UnoNativeMac/UnoNativeMac.h | 1 + 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs index 0d257e07a5b8..0a0c5d953e9d 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs @@ -89,7 +89,6 @@ private void Draw(double nativeWidth, double nativeHeight, SKSurface surface) using var negativePath = new SKPath(); negativePath.AddRect(new SKRect(0, 0, width, height)); using var diffPath = negativePath.Op(path, SKPathOp.Difference); - // FIXME: clipping not working correctly, comment next line to get something better // note: use an online svg viewer to visualize the clipping path NativeUno.uno_window_clip_svg(_nativeWindow.Handle, diffPath.ToSvgPathData()); } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.h b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.h index be9aa9b9c0f5..d40f906399be 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.h @@ -328,6 +328,8 @@ void uno_window_notify_screen_change(NSWindow *window); @property(getter=isFlipped, readonly) BOOL flipped; +@property CAShapeLayer *clipLayer; + @end NS_ASSUME_NONNULL_END diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m index 91d57cd20544..e363faf1dbdb 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m @@ -94,6 +94,14 @@ -(BOOL) isFlipped { return YES; } +-(instancetype) initWithFrame:(CGRect)frameRect device:(id)device { + self = [super initWithFrame:frameRect device:device]; + if (self) { + self.clipLayer = [CAShapeLayer layer]; + } + return self; +} + @end NSWindow* uno_window_create(double width, double height) @@ -600,6 +608,7 @@ void uno_set_window_close_callbacks(window_should_close_fn_ptr shouldClose, wind void uno_window_clip_svg(UNOWindow* window, const char* svg) { + UNOMetalFlippedView* v = window.contentView; if (svg) { #if DEBUG NSLog(@"uno_window_clip_svg %@ %@ %s", window, window.contentView.layer.description, svg); @@ -628,19 +637,17 @@ void uno_window_clip_svg(UNOWindow* window, const char* svg) CGPathAddLineToPoint(path, nil, 34, 523); CGPathCloseSubpath(path); - // TODO make a CAShapeLayer out of the given SVG, note: we already have a CAMetalLayer present - CAShapeLayer* layer = nil; + // TODO set the CAShapeLayer path to the given SVG + // note: we already have a CAMetalLayer present as the _main_ layer if (!window.contentView.layer.sublayers) { - layer = [CAShapeLayer layer]; - [window.contentView.layer addSublayer:layer]; + UNOMetalFlippedView* v = window.contentView; + [window.contentView.layer addSublayer:v.clipLayer]; } - layer.path = path; - layer.fillRule = kCAFillRuleEvenOdd; } else { #if DEBUG - NSLog(@"uno_window_clip_svg %@ nil - reseting layer %@ mask to nil", window, window.contentView.layer.description); + NSLog(@"uno_window_clip_svg %@ reset", window); #endif - window.contentView.layer.sublayers = nil; + [v.clipLayer removeFromSuperlayer]; } } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UnoNativeMac.h b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UnoNativeMac.h index d582469f2d3e..4fdaeaae44e2 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UnoNativeMac.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UnoNativeMac.h @@ -5,3 +5,4 @@ #import #import #import +#import From 49e741c4ca650f092f6d0937ddf41ffa3a7a4180 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Tue, 16 Jul 2024 20:03:49 -0400 Subject: [PATCH 07/17] chore: implement SVG parsing for clipping --- .../UnoNativeMac/UnoNativeMac/UNONative.m | 13 ++- .../UnoNativeMac/UnoNativeMac/UNOWindow.m | 92 ++++++++++++++----- 2 files changed, 78 insertions(+), 27 deletions(-) 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 61401cecd3c5..462fa7adf422 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m @@ -43,11 +43,18 @@ - (void)updateLayer void uno_native_arrange(NSView *element, double arrangeLeft, double arrangeTop, double arrangeWidth, double arrangeHeight, double clipLeft, double clipTop, double clipWidth, double clipHeight) { + NSRect clip = NSMakeRect(clipLeft, clipTop, clipWidth, clipHeight); + element.hidden = NSIsEmptyRect(clip); + // TODO handle partial case with element special layers + + NSRect arrange = NSMakeRect(arrangeLeft, arrangeTop, arrangeWidth, arrangeWidth); + 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) clip(%g,%g,%g,%g) %s", element, + arrangeLeft, arrangeTop, arrangeWidth, arrangeHeight, + clipLeft, clipTop, clipWidth, clipHeight, + element.hidden ? "EMPTY" : (clipWidth < arrangeWidth) || (clipHeight < arrangeHeight) ? "partial" : ""); #endif - element.frame = NSMakeRect(arrangeLeft, arrangeTop, arrangeWidth, arrangeWidth); - // TODO } void uno_native_attach(NSView* element) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m index e363faf1dbdb..436748ac94d1 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m @@ -606,6 +606,32 @@ void uno_set_window_close_callbacks(window_should_close_fn_ptr shouldClose, wind return gr_direct_context_make_metal(device, queue); } +CGFloat readNextCoord(const char *svg, int *position, long length) +{ + CGFloat result = NAN; + if (*position >= length) { +#if DEBUG + NSLog(@"uno_window_clip_svg readNextCoord position:%d >= length:%ld", *position, length); +#endif + return result; + } + for (int i=*position; i < length; i++) { + if (isdigit(svg[i])) { + if (isnan(result)) { + result = svg[i] - '0'; + } else { + result *= 10; + result += svg[i] - '0'; + } + *position = i; + } else if (!isnan(result)) { + *position = i - 1; + break; + } + } + return result; +} + void uno_window_clip_svg(UNOWindow* window, const char* svg) { UNOMetalFlippedView* v = window.contentView; @@ -613,31 +639,49 @@ void uno_window_clip_svg(UNOWindow* window, const char* svg) #if DEBUG NSLog(@"uno_window_clip_svg %@ %@ %s", window, window.contentView.layer.description, svg); #endif - // FIXME: convert SVG string into a CGMutablePathRef, what's below is hardcoded to the initiall values CGMutablePathRef path = CGPathCreateMutable(); - // M613 246L776 246L776 261L613 261L613 246Z - CGPathMoveToPoint(path, nil, 613, 246); - CGPathAddLineToPoint(path, nil, 776, 246); - CGPathAddLineToPoint(path, nil, 776, 261); - CGPathAddLineToPoint(path, nil, 613, 261); - CGPathAddLineToPoint(path, nil, 613, 246); - CGPathCloseSubpath(path); - // M22 323L32 323L32 338L22 338L22 323Z - CGPathMoveToPoint(path, nil, 22, 323); - CGPathAddLineToPoint(path, nil, 32, 323); - CGPathAddLineToPoint(path, nil, 32, 338); - CGPathAddLineToPoint(path, nil, 22, 338); - CGPathAddLineToPoint(path, nil, 22, 323); - CGPathCloseSubpath(path); - // M34 523L22 523L22 538L34 538L34 523Z - CGPathMoveToPoint(path, nil, 34, 523); - CGPathAddLineToPoint(path, nil, 22, 523); - CGPathAddLineToPoint(path, nil, 22, 538); - CGPathAddLineToPoint(path, nil, 34, 538); - CGPathAddLineToPoint(path, nil, 34, 523); - CGPathCloseSubpath(path); - - // TODO set the CAShapeLayer path to the given SVG + // small subset of an SVG path parser handling trusted input of integer-based points + long length = strlen(svg); + for (int i=0; i < length; i++) { + CGFloat x, y; + char op = svg[i]; + switch (op) { + case 'M': + x = readNextCoord(svg, &i, length); + i++; // skip separator + y = readNextCoord(svg, &i, length); + // there might not be a separator (not required before the next op) +#if DEBUG_PARSER + NSLog(@"uno_window_clip_svg parsing CGPathMoveToPoint %g %g - position %d", x, y, i); +#endif + CGPathMoveToPoint(path, nil, x, y); + break; + case 'L': + x = readNextCoord(svg, &i, length); + i++; // skip separator + y = readNextCoord(svg, &i, length); + // there might not be a separator (not required before the next op) +#if DEBUG_PARSER + NSLog(@"uno_window_clip_svg parsing CGPathAddLineToPoint %g %g - position %d", x, y, i); +#endif + CGPathAddLineToPoint(path, nil, x, y); + break; + case 'Z': +#if DEBUG_PARSER + NSLog(@"uno_window_clip_svg parsing CGPathCloseSubpath - position %d", i); +#endif + CGPathCloseSubpath(path); + break; +#if DEBUG + default: + if (op != ' ') { + NSLog(@"uno_window_clip_svg parsing unknown op %c at position %d", op, i - 1); + } + break; +#endif + } + } + // note: we already have a CAMetalLayer present as the _main_ layer if (!window.contentView.layer.sublayers) { UNOMetalFlippedView* v = window.contentView; From c29745252cc8a6dc37955f63be9980d2f1598afa Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Fri, 9 Aug 2024 14:57:55 -0400 Subject: [PATCH 08/17] fix: fix native element visibility (reset by clipping) --- .../UnoNativeMac/UnoNativeMac/UNONative.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 462fa7adf422..05e5bf635cf6 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m @@ -44,7 +44,9 @@ - (void)updateLayer void uno_native_arrange(NSView *element, double arrangeLeft, double arrangeTop, double arrangeWidth, double arrangeHeight, double clipLeft, double clipTop, double clipWidth, double clipHeight) { NSRect clip = NSMakeRect(clipLeft, clipTop, clipWidth, clipHeight); - element.hidden = NSIsEmptyRect(clip); + if (!element.hidden) { + element.hidden = NSIsEmptyRect(clip); + } // TODO handle partial case with element special layers NSRect arrange = NSMakeRect(arrangeLeft, arrangeTop, arrangeWidth, arrangeWidth); @@ -109,7 +111,7 @@ void uno_native_set_opacity(NSView* element, double opacity) void uno_native_set_visibility(NSView* element, bool visible) { #if DEBUG - NSLog(@"uno_native_set_visibility #%p : %s -> %s", element, element.hidden ? "TRUE" : "FALSE", visible ? "TRUE" : "FALSE"); + NSLog(@"uno_native_set_visibility #%p : hidden %s -> visible %s", element, element.hidden ? "TRUE" : "FALSE", visible ? "TRUE" : "FALSE"); #endif element.hidden = !visible; } From 11be12ea19da6643feaad9f5b1af9599b2d04a55 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Sun, 3 Nov 2024 13:44:19 -0500 Subject: [PATCH 09/17] chore: cleanup without native element clipping --- src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs index a916054cae97..0eaa75832efa 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs @@ -211,7 +211,7 @@ internal static unsafe partial void uno_set_window_events_callbacks( internal static partial string uno_window_get_title(nint window); [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] - internal static partial nint uno_window_set_title(nint window, string title); + internal static partial void uno_window_set_title(nint window, string title); [LibraryImport("libUnoNativeMac.dylib")] internal static unsafe partial void uno_set_window_close_callbacks( From c260496754f010120eda0e4563cf5122771bf9c4 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Tue, 5 Nov 2024 14:14:21 -0500 Subject: [PATCH 10/17] chore: cleanup without native element clipping --- .../MacOSMetalRenderer.cs | 2 +- .../MacOSNativeElementHostingExtension.cs | 34 ++++++++++++++----- .../MacOSWindowHost.cs | 12 ++----- .../UnoNativeMac/UnoNativeMac/UNONative.h | 6 ++++ .../UnoNativeMac/UnoNativeMac/UNONative.m | 20 +++++++---- .../UnoNativeMac/UnoNativeMac/UNOWindow.m | 14 ++------ 6 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSMetalRenderer.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSMetalRenderer.cs index 88ad6fd08cc8..32278f2a529e 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSMetalRenderer.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSMetalRenderer.cs @@ -15,7 +15,7 @@ internal static class MacOSMetalRenderer // FIXME: contribute some extra API (e.g. using `nint` or `IntPtr`) to SkiaSharp to avoid reflection // net8+ alternative -> https://steven-giesel.com/blogPost/05ecdd16-8dc4-490f-b1cf-780c994346a4 var get = typeof(GRContext).GetMethod("GetObject", BindingFlags.Static | BindingFlags.NonPublic); - var context = (GRContext?)get?.Invoke(null, new object[] { ctx, true }); + var context = (GRContext?)get?.Invoke(null, [ctx, true]); if (context is null) { // Macs since 2012 have Metal 2 support and macOS 10.14 Mojave (2018) requires Metal diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs index cf7e06aca489..08c2038fab74 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSNativeElementHostingExtension.cs @@ -11,12 +11,9 @@ namespace Uno.UI.Runtime.Skia.MacOS; internal class MacOSNativeElement : Microsoft.UI.Xaml.FrameworkElement { - internal MacOSNativeElement(nint handle) - { - NativeHandle = handle; - } + public nint NativeHandle { get; internal set; } - public nint NativeHandle { get; private set; } + internal bool Detached { get; set; } } internal class MacOSNativeElementHostingExtension : ContentPresenter.INativeElementHostingExtension @@ -36,7 +33,14 @@ public void ArrangeNativeElement(object content, Rect arrangeRect, Rect clipRect { if (content is MacOSNativeElement element) { - NativeUno.uno_native_arrange(element.NativeHandle, arrangeRect.Left, arrangeRect.Top, arrangeRect.Width, arrangeRect.Height, clipRect.Left, clipRect.Top, clipRect.Width, clipRect.Height); + if (element.Detached) + { + this.Log().Debug($"Cannot arrange element `{nameof(content)}` of type {content.GetType().FullName} since it was detached."); + } + else + { + NativeUno.uno_native_arrange(element.NativeHandle, arrangeRect.Left, arrangeRect.Top, arrangeRect.Width, arrangeRect.Height, clipRect.Left, clipRect.Top, clipRect.Width, clipRect.Height); + } } else if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -89,20 +93,32 @@ public void ChangeNativeElementVisibility(object content, bool visible) { if (this.Log().IsEnabled(LogLevel.Debug)) { - this.Log().Debug($"CreateSampleComponent failed as no MacOSWindowNative could be found."); + this.Log().Debug($"CreateSampleComponent failed as no MacOSWindowNative instance could be found."); } return null; } var handle = NativeUno.uno_native_create_sample(_window.Handle, text); - return new MacOSNativeElement(handle); + return new MacOSNativeElement() + { + NativeHandle = handle, + AccessKey = text // FIXME: debug helper, to be removed + }; } public void DetachNativeElement(object content) { if (content is MacOSNativeElement element) { - NativeUno.uno_native_detach(element.NativeHandle); + if (element.Detached) + { + this.Log().Debug($"Object `{nameof(content)}` of type {content.GetType().FullName} was already detached."); + } + else + { + NativeUno.uno_native_detach(element.NativeHandle); + element.Detached = true; + } } else if (this.Log().IsEnabled(LogLevel.Debug)) { diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs index ddcd7147cdbe..49d17059b739 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs @@ -82,16 +82,8 @@ private void Draw(double nativeWidth, double nativeHeight, SKSurface surface) NativeUno.uno_window_clip_svg(_nativeWindow.Handle, null); int width = (int)nativeWidth; int height = (int)nativeHeight; - var path = SkiaRenderHelper.RenderRootVisualAndReturnPath(width, height, rootVisual, surface); - // we clip the "negative" of what was drawn - if (path is { }) - { - using var negativePath = new SKPath(); - negativePath.AddRect(new SKRect(0, 0, width, height)); - using var diffPath = negativePath.Op(path, SKPathOp.Difference); - // note: use an online svg viewer to visualize the clipping path - NativeUno.uno_window_clip_svg(_nativeWindow.Handle, diffPath.ToSvgPathData()); - } + SkiaRenderHelper.RenderRootVisualAndClearNativeAreas(width, height, rootVisual, surface); + // TODO clip the "negative" of what was drawn } } 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 d2e229a539ea..e8db0f80429c 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h @@ -12,6 +12,12 @@ NS_ASSUME_NONNULL_BEGIN @end +@protocol UNONativeElement + +-(void) detach; + +@end + 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); 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 05e5bf635cf6..66eca41fdf8a 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m @@ -43,13 +43,17 @@ - (void)updateLayer void uno_native_arrange(NSView *element, double arrangeLeft, double arrangeTop, double arrangeWidth, double arrangeHeight, double clipLeft, double clipTop, double clipWidth, double clipHeight) { - NSRect clip = NSMakeRect(clipLeft, clipTop, clipWidth, clipHeight); - if (!element.hidden) { - element.hidden = NSIsEmptyRect(clip); + NSLog(@"uno_native_arrange %p", element); + if (!element || element.hidden) { + NSLog(@"uno_native_arrange0 hidden %p", element); + return; } + + NSRect clip = NSMakeRect(arrangeLeft + clipLeft, arrangeTop - clipTop, clipWidth, clipHeight); + element.hidden = NSIsEmptyRect(clip) || clipHeight <= 0 || clipWidth <= 0; // TODO handle partial case with element special layers - NSRect arrange = NSMakeRect(arrangeLeft, arrangeTop, arrangeWidth, arrangeWidth); + 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) %s", element, @@ -62,7 +66,7 @@ void uno_native_arrange(NSView *element, double arrangeLeft, double arrangeTop, void uno_native_attach(NSView* element) { #if DEBUG - NSLog(@"uno_native_attach %p -> %s attached", element, [elements containsObject:element] ? "already" : "not"); + NSLog(@"uno_native_attach %p -> %s attached", element, [elements containsObject:element] ? "already" : "not previously"); #endif if (!elements) { elements = [[NSMutableSet alloc] initWithCapacity:10]; @@ -76,6 +80,10 @@ void uno_native_detach(NSView *element) NSLog(@"uno_native_detach %p", element); #endif if (elements) { + if ([element conformsToProtocol:@protocol(UNONativeElement)]) { + id native = (id) element; + [native detach]; + } [elements removeObject:element]; } } @@ -108,7 +116,7 @@ void uno_native_set_opacity(NSView* element, double opacity) element.alphaValue = opacity; } -void uno_native_set_visibility(NSView* element, bool visible) +void uno_native_set_visibility(UNORedView* element, bool visible) { #if DEBUG NSLog(@"uno_native_set_visibility #%p : hidden %s -> visible %s", element, element.hidden ? "TRUE" : "FALSE", visible ? "TRUE" : "FALSE"); diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m index a643eb859989..4e5a74618086 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m @@ -1,5 +1,5 @@ // -// UNOWindowDelegate.m +// UNOWindow.m // #import "UNOWindow.h" @@ -97,7 +97,7 @@ -(BOOL) isFlipped { -(instancetype) initWithFrame:(CGRect)frameRect device:(id)device { self = [super initWithFrame:frameRect device:device]; if (self) { - self.clipLayer = [CAShapeLayer layer]; + // TODO } return self; } @@ -642,7 +642,6 @@ CGFloat readNextCoord(const char *svg, int *position, long length) void uno_window_clip_svg(UNOWindow* window, const char* svg) { - UNOMetalFlippedView* v = window.contentView; if (svg) { #if DEBUG NSLog(@"uno_window_clip_svg %@ %@ %s", window, window.contentView.layer.description, svg); @@ -689,17 +688,10 @@ void uno_window_clip_svg(UNOWindow* window, const char* svg) #endif } } - - // note: we already have a CAMetalLayer present as the _main_ layer - if (!window.contentView.layer.sublayers) { - UNOMetalFlippedView* v = window.contentView; - [window.contentView.layer addSublayer:v.clipLayer]; - } } else { #if DEBUG NSLog(@"uno_window_clip_svg %@ reset", window); #endif - [v.clipLayer removeFromSuperlayer]; } } @@ -797,7 +789,7 @@ - (void)sendEvent:(NSEvent *)event { UniChar unicode = get_unicode(event); handled = uno_get_window_key_up_callback()(self, get_virtual_key(scanCode), get_modifiers(event.modifierFlags), scanCode, unicode); #if DEBUG - NSLog(@"NSEventTypeKeyUp: %@ window %p unocode %d handled? %s", event, self, unicode, handled ? "true" : "false"); + NSLog(@"NSEventTypeKeyUp: %@ window %p unicode %d handled? %s", event, self, unicode, handled ? "true" : "false"); #endif break; } From 5b7c0fc7931bf00fec6969f648281ef6cddeb46b Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Tue, 5 Nov 2024 16:29:58 -0500 Subject: [PATCH 11/17] fix: change how we handle the keyboard --- src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs index 49d17059b739..e798b89f4c63 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs @@ -11,6 +11,7 @@ using Windows.UI.Core; using Windows.UI.Input; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Input; using Window = Microsoft.UI.Xaml.Window; @@ -278,8 +279,7 @@ private static int OnRawKeyDown(nint handle, VirtualKey key, VirtualKeyModifiers } var args = CreateArgs(key, mods, scanCode, unicode); keyDown.Invoke(window!, args); - // we tell macOS it's always handled as WinUI does not mark as handled some keys that would make it beep in common cases - return 1; + return FocusManager.GetFocusedElement() == null ? 0 : 1; } catch (Exception e) { @@ -306,7 +306,7 @@ private static int OnRawKeyUp(nint handle, VirtualKey key, VirtualKeyModifiers m } var args = CreateArgs(key, mods, scanCode, unicode); keyUp.Invoke(window!, args); - return args.Handled ? 1 : 0; + return 1; } catch (Exception e) { @@ -414,8 +414,8 @@ internal static unsafe int OnMouseEvent(nint handle, NativeMouseEventData* data) } mouseEvent(window, BuildPointerArgs(*data)); - // let the window be activated (becoming the keyWindow) when clicked - return data->EventType == NativeMouseEvents.Down ? 0 : 1; + // always let the native side know about the mouse events, e.g. setting keyWindow, embedded native controls + return 0; } catch (Exception e) { From f80f7ff7207804ad99c5288c1abbbd3c33ad32d0 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Fri, 8 Nov 2024 17:49:43 -0500 Subject: [PATCH 12/17] chore: fix xcode project post merge (crashed Xcode) --- .../UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj index 0be42284d811..6a5fccac38ff 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ D116C63E2AC79876004B975F /* UNOCursor.m in Sources */ = {isa = PBXBuildFile; fileRef = D116C63D2AC79876004B975F /* UNOCursor.m */; }; D13AB8A82B5839B200693F8E /* libSkiaSharp.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D13AB8A72B5839B200693F8E /* libSkiaSharp.dylib */; }; + D15930D52CDEA3C0007B40FD /* UNOWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = D15930D32CDEA3C0007B40FD /* UNOWebView.h */; }; + D15930D62CDEA3C0007B40FD /* UNOWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = D15930D42CDEA3C0007B40FD /* UNOWebView.m */; }; D18D4FAC2C2DE804003E4BBF /* UNONative.m in Sources */ = {isa = PBXBuildFile; fileRef = D18D4FAB2C2DE804003E4BBF /* UNONative.m */; }; D1A0651E2A7066F700101BE6 /* UNOMetalViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D1A0651D2A7066F700101BE6 /* UNOMetalViewDelegate.m */; }; D1A065202A8467B200101BE6 /* UNOApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D1A0651F2A8467B200101BE6 /* UNOApplication.h */; }; @@ -25,6 +27,8 @@ D116C63D2AC79876004B975F /* UNOCursor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UNOCursor.m; sourceTree = ""; }; D13AB8A72B5839B200693F8E /* libSkiaSharp.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libSkiaSharp.dylib; path = UnoNativeMac/libSkiaSharp.dylib; sourceTree = ""; }; D13AB8AC2B58566400693F8E /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; + D15930D32CDEA3C0007B40FD /* UNOWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UNOWebView.h; sourceTree = ""; }; + D15930D42CDEA3C0007B40FD /* UNOWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UNOWebView.m; sourceTree = ""; }; D18D4FAA2C2DE76F003E4BBF /* UNONative.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UNONative.h; sourceTree = ""; }; D18D4FAB2C2DE804003E4BBF /* UNONative.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UNONative.m; sourceTree = ""; }; D1A0651C2A70664A00101BE6 /* UNOMetalViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UNOMetalViewDelegate.h; sourceTree = ""; }; @@ -101,6 +105,8 @@ D1FE7A2A2B75C8BB00ACFC76 /* UNOSoftView.m */, D1A065232A8AC15C00101BE6 /* UNOWindow.h */, D1A065242A8AC23800101BE6 /* UNOWindow.m */, + D15930D32CDEA3C0007B40FD /* UNOWebView.h */, + D15930D42CDEA3C0007B40FD /* UNOWebView.m */, ); path = UnoNativeMac; sourceTree = ""; @@ -112,6 +118,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + D15930D52CDEA3C0007B40FD /* UNOWebView.h in Headers */, D1F248A92A67288B008A609E /* UnoNativeMac.h in Headers */, D1A065202A8467B200101BE6 /* UNOApplication.h in Headers */, ); @@ -181,6 +188,7 @@ D1A065252A8AC23800101BE6 /* UNOWindow.m in Sources */, D1CC768B2AB9D464002A44F0 /* UNOClipboard.m in Sources */, D116C63E2AC79876004B975F /* UNOCursor.m in Sources */, + D15930D62CDEA3C0007B40FD /* UNOWebView.m in Sources */, D18D4FAC2C2DE804003E4BBF /* UNONative.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; From 124cb8465fd01d8759f86ae2eae71eb85a4fadbc Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Fri, 8 Nov 2024 17:52:53 -0500 Subject: [PATCH 13/17] chore: better/simpler basic clipping --- .../MacOSWindowHost.cs | 4 +- .../UnoNativeMac/UnoNativeMac/UNONative.h | 10 +++-- .../UnoNativeMac/UnoNativeMac/UNONative.m | 37 +++++++++++++------ 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs index 33a94bf7a50f..a9c3c3a2da43 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs @@ -83,10 +83,10 @@ private void Draw(double nativeWidth, double nativeHeight, SKSurface surface) if (RootElement?.Visual is { } rootVisual) { // remove previous clipping (if any) - NativeUno.uno_window_clip_svg(_nativeWindow.Handle, null); + // NativeUno.uno_window_clip_svg(_nativeWindow.Handle, null); int width = (int)nativeWidth; int height = (int)nativeHeight; - SkiaRenderHelper.RenderRootVisualAndClearNativeAreas(width, height, rootVisual, surface); + SkiaRenderHelper.RenderRootVisualAndReturnPath(width, height, rootVisual, surface); // TODO clip the "negative" of what was drawn } } 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 e8db0f80429c..7259446bb968 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h @@ -8,16 +8,18 @@ NS_ASSUME_NONNULL_BEGIN -@interface UNORedView : NSView - -@end - @protocol UNONativeElement +@property (nonatomic) bool visible; + -(void) detach; @end +@interface UNORedView : NSView + +@end + 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); 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 66eca41fdf8a..79a5dab4dae3 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m @@ -11,12 +11,18 @@ @implementation UNORedView : NSView // make the background red for easier tracking - (BOOL)wantsUpdateLayer { - return YES; + return !self.hidden; } - (void)updateLayer { - self.layer.backgroundColor = NSColor.redColor.CGColor; + self.layer.backgroundColor = self.hidden ? NSColor.clearColor.CGColor : NSColor.redColor.CGColor; +} + +@synthesize visible; + +- (void)detach { + // nothing needed } @end @@ -41,22 +47,28 @@ - (void)updateLayer 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, double clipLeft, double clipTop, double clipWidth, double clipHeight) { - NSLog(@"uno_native_arrange %p", element); - if (!element || element.hidden) { - NSLog(@"uno_native_arrange0 hidden %p", element); + if (!element || !element.visible) { +#if DEBUG + NSLog(@"uno_native_arrange %p '%@' is not visible - nothing to arrange", element, ((NSTextField*)element.subviews[0]).stringValue); +#endif return; } NSRect clip = NSMakeRect(arrangeLeft + clipLeft, arrangeTop - clipTop, clipWidth, clipHeight); element.hidden = NSIsEmptyRect(clip) || clipHeight <= 0 || clipWidth <= 0; - // TODO handle partial case with element special layers + if (element.hidden) { +#if DEBUG + NSLog(@"uno_native_arrange %p '%@' hidden by clipping", element, ((NSTextField*)element.subviews[0]).stringValue); +#endif + return; + } - NSRect arrange = NSMakeRect(arrangeLeft, arrangeTop, arrangeWidth, arrangeHeight); + NSRect arrange = NSMakeRect(arrangeLeft + clipLeft, arrangeTop + clipTop, MIN(arrangeWidth, clipWidth), MIN(arrangeHeight, clipHeight)); element.frame = arrange; #if DEBUG - NSLog(@"uno_native_arrange %p arrange(%g,%g,%g,%g) clip(%g,%g,%g,%g) %s", element, + NSLog(@"uno_native_arrange %p %@ arrange(%g,%g,%g,%g) clip(%g,%g,%g,%g) %s", element, ((NSTextField*)element.subviews[0]).stringValue, arrangeLeft, arrangeTop, arrangeWidth, arrangeHeight, clipLeft, clipTop, clipWidth, clipHeight, element.hidden ? "EMPTY" : (clipWidth < arrangeWidth) || (clipHeight < arrangeHeight) ? "partial" : ""); @@ -116,10 +128,13 @@ void uno_native_set_opacity(NSView* element, double opacity) element.alphaValue = opacity; } -void uno_native_set_visibility(UNORedView* element, bool visible) +void uno_native_set_visibility(NSView* element, bool visible) { #if DEBUG NSLog(@"uno_native_set_visibility #%p : hidden %s -> visible %s", element, element.hidden ? "TRUE" : "FALSE", visible ? "TRUE" : "FALSE"); #endif - element.hidden = !visible; + element.visible = visible; + // hidden is controlled by both visible and clipping + if (!visible) + element.hidden = true; } From 026226b0c12b1eb49e8cac90ea25002be21a53a6 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Wed, 13 Nov 2024 11:58:51 -0500 Subject: [PATCH 14/17] fix(macOS): native clip on the provided path/svg --- .../MacOSWindowHost.cs | 11 ++- .../UnoNativeMac/UnoNativeMac/UNONative.h | 1 + .../UnoNativeMac/UnoNativeMac/UNONative.m | 13 +-- .../UnoNativeMac/UnoNativeMac/UNOWindow.m | 87 +++++++++++-------- 4 files changed, 65 insertions(+), 47 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs index a9c3c3a2da43..6ceb64d78f3e 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs @@ -82,12 +82,15 @@ private void Draw(double nativeWidth, double nativeHeight, SKSurface surface) if (RootElement?.Visual is { } rootVisual) { - // remove previous clipping (if any) - // NativeUno.uno_window_clip_svg(_nativeWindow.Handle, null); int width = (int)nativeWidth; int height = (int)nativeHeight; - SkiaRenderHelper.RenderRootVisualAndReturnPath(width, height, rootVisual, surface); - // TODO clip the "negative" of what was drawn + var path = SkiaRenderHelper.RenderRootVisualAndReturnPath(width, height, rootVisual, surface); + if (path is { }) + { + var svg = path.ToSvgPathData(); + // remove first path since it cover the whole area + NativeUno.uno_window_clip_svg(_nativeWindow.Handle, svg.Substring(svg.IndexOf('Z') + 1)); + } } } 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 7259446bb968..e9cdec54ebe7 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.h @@ -5,6 +5,7 @@ #pragma once #import "UnoNativeMac.h" +#import "UNOWindow.h" NS_ASSUME_NONNULL_BEGIN 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 79a5dab4dae3..27607d4ac8e5 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNONative.m @@ -16,7 +16,7 @@ - (BOOL)wantsUpdateLayer - (void)updateLayer { - self.layer.backgroundColor = self.hidden ? NSColor.clearColor.CGColor : NSColor.redColor.CGColor; + self.layer.backgroundColor = NSColor.redColor.CGColor; } @synthesize visible; @@ -51,7 +51,7 @@ void uno_native_arrange(NSView *element, double arrangeLeft, d { if (!element || !element.visible) { #if DEBUG - NSLog(@"uno_native_arrange %p '%@' is not visible - nothing to arrange", element, ((NSTextField*)element.subviews[0]).stringValue); + NSLog(@"uno_native_arrange %p is not visible - nothing to arrange", element); #endif return; } @@ -60,7 +60,7 @@ void uno_native_arrange(NSView *element, double arrangeLeft, d element.hidden = NSIsEmptyRect(clip) || clipHeight <= 0 || clipWidth <= 0; if (element.hidden) { #if DEBUG - NSLog(@"uno_native_arrange %p '%@' hidden by clipping", element, ((NSTextField*)element.subviews[0]).stringValue); + NSLog(@"uno_native_arrange %p hidden by clipping", element); #endif return; } @@ -68,7 +68,7 @@ void uno_native_arrange(NSView *element, double arrangeLeft, d NSRect arrange = NSMakeRect(arrangeLeft + clipLeft, arrangeTop + clipTop, MIN(arrangeWidth, clipWidth), MIN(arrangeHeight, clipHeight)); element.frame = arrange; #if DEBUG - NSLog(@"uno_native_arrange %p %@ arrange(%g,%g,%g,%g) clip(%g,%g,%g,%g) %s", element, ((NSTextField*)element.subviews[0]).stringValue, + NSLog(@"uno_native_arrange %p arrange(%g,%g,%g,%g) clip(%g,%g,%g,%g) %s", element, arrangeLeft, arrangeTop, arrangeWidth, arrangeHeight, clipLeft, clipTop, clipWidth, clipHeight, element.hidden ? "EMPTY" : (clipWidth < arrangeWidth) || (clipHeight < arrangeHeight) ? "partial" : ""); @@ -83,6 +83,7 @@ void uno_native_attach(NSView* element) if (!elements) { elements = [[NSMutableSet alloc] initWithCapacity:10]; } + // note: it's too early to add a mask since the layer has not been set yet [elements addObject:element]; } @@ -91,6 +92,7 @@ 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; @@ -111,8 +113,7 @@ 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) { - // FIXME - CGSize size = element.subviews.firstObject.frame.size; // element.fittingSize; + CGSize size = element.subviews.firstObject.frame.size; *width = size.width; *height = size.height; #if DEBUG diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m index cab6692d6f57..6d149720118a 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m @@ -688,52 +688,65 @@ void uno_window_clip_svg(UNOWindow* window, const char* svg) #if DEBUG NSLog(@"uno_window_clip_svg %@ %@ %s", window, window.contentView.layer.description, svg); #endif - CGMutablePathRef path = CGPathCreateMutable(); - // small subset of an SVG path parser handling trusted input of integer-based points - long length = strlen(svg); - for (int i=0; i < length; i++) { - CGFloat x, y; - char op = svg[i]; - switch (op) { - case 'M': - x = readNextCoord(svg, &i, length); - i++; // skip separator - y = readNextCoord(svg, &i, length); - // there might not be a separator (not required before the next op) + NSArray<__kindof NSView *> *subviews = window.contentViewController.view.subviews; + for (int i = 0; i < subviews.count; i++) { + NSView* view = subviews[i]; +#if DEBUG + NSLog(@"uno_window_clip_svg subview %d %@ layer %@ mask %@", i, view, view.layer, view.layer.mask); +#endif + CGMutablePathRef path = CGPathCreateMutable(); + // small subset of an SVG path parser handling trusted input of integer-based points + long length = strlen(svg); + for (int i=0; i < length; i++) { + CGFloat x, y; + char op = svg[i]; + switch (op) { + case 'M': + x = readNextCoord(svg, &i, length); + i++; // skip separator + y = readNextCoord(svg, &i, length); + // there might not be a separator (not required before the next op) #if DEBUG_PARSER - NSLog(@"uno_window_clip_svg parsing CGPathMoveToPoint %g %g - position %d", x, y, i); -#endif - CGPathMoveToPoint(path, nil, x, y); - break; - case 'L': - x = readNextCoord(svg, &i, length); - i++; // skip separator - y = readNextCoord(svg, &i, length); - // there might not be a separator (not required before the next op) + NSLog(@"uno_window_clip_svg parsing CGPathMoveToPoint %g %g - position %d", x, y, i); +#endif + x -= view.frame.origin.x; + y -= view.frame.origin.y; + CGPathMoveToPoint(path, nil, x, y); + break; + case 'L': + x = readNextCoord(svg, &i, length); + i++; // skip separator + y = readNextCoord(svg, &i, length); + // there might not be a separator (not required before the next op) #if DEBUG_PARSER - NSLog(@"uno_window_clip_svg parsing CGPathAddLineToPoint %g %g - position %d", x, y, i); + NSLog(@"uno_window_clip_svg parsing CGPathAddLineToPoint %g %g - position %d", x, y, i); #endif - CGPathAddLineToPoint(path, nil, x, y); - break; - case 'Z': + x -= view.frame.origin.x; + y -= view.frame.origin.y; + CGPathAddLineToPoint(path, nil, x, y); + break; + case 'Z': #if DEBUG_PARSER - NSLog(@"uno_window_clip_svg parsing CGPathCloseSubpath - position %d", i); + NSLog(@"uno_window_clip_svg parsing CGPathCloseSubpath - position %d", i); #endif - CGPathCloseSubpath(path); - break; + CGPathCloseSubpath(path); + break; #if DEBUG - default: - if (op != ' ') { - NSLog(@"uno_window_clip_svg parsing unknown op %c at position %d", op, i - 1); - } - break; + default: + if (op != ' ') { + NSLog(@"uno_window_clip_svg parsing unknown op %c at position %d", op, i - 1); + } + break; #endif + } + } + CAShapeLayer* mask = view.layer.mask; + if (mask == nil) { + view.layer.mask = mask = [[CAShapeLayer alloc] init]; } + mask.fillColor = NSColor.blueColor.CGColor; // anything but clearColor + mask.path = path; } - } else { -#if DEBUG - NSLog(@"uno_window_clip_svg %@ reset", window); -#endif } } From 610789c59a67c1ead68d92871be4b68acf04d508 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Wed, 13 Nov 2024 21:51:07 -0500 Subject: [PATCH 15/17] chore: fix UnoNativeMac.xcodeproj/project.pbxproj --- .../UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj index 6a5fccac38ff..0be42284d811 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac.xcodeproj/project.pbxproj @@ -9,8 +9,6 @@ /* Begin PBXBuildFile section */ D116C63E2AC79876004B975F /* UNOCursor.m in Sources */ = {isa = PBXBuildFile; fileRef = D116C63D2AC79876004B975F /* UNOCursor.m */; }; D13AB8A82B5839B200693F8E /* libSkiaSharp.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D13AB8A72B5839B200693F8E /* libSkiaSharp.dylib */; }; - D15930D52CDEA3C0007B40FD /* UNOWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = D15930D32CDEA3C0007B40FD /* UNOWebView.h */; }; - D15930D62CDEA3C0007B40FD /* UNOWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = D15930D42CDEA3C0007B40FD /* UNOWebView.m */; }; D18D4FAC2C2DE804003E4BBF /* UNONative.m in Sources */ = {isa = PBXBuildFile; fileRef = D18D4FAB2C2DE804003E4BBF /* UNONative.m */; }; D1A0651E2A7066F700101BE6 /* UNOMetalViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D1A0651D2A7066F700101BE6 /* UNOMetalViewDelegate.m */; }; D1A065202A8467B200101BE6 /* UNOApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D1A0651F2A8467B200101BE6 /* UNOApplication.h */; }; @@ -27,8 +25,6 @@ D116C63D2AC79876004B975F /* UNOCursor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UNOCursor.m; sourceTree = ""; }; D13AB8A72B5839B200693F8E /* libSkiaSharp.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libSkiaSharp.dylib; path = UnoNativeMac/libSkiaSharp.dylib; sourceTree = ""; }; D13AB8AC2B58566400693F8E /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; - D15930D32CDEA3C0007B40FD /* UNOWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UNOWebView.h; sourceTree = ""; }; - D15930D42CDEA3C0007B40FD /* UNOWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UNOWebView.m; sourceTree = ""; }; D18D4FAA2C2DE76F003E4BBF /* UNONative.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UNONative.h; sourceTree = ""; }; D18D4FAB2C2DE804003E4BBF /* UNONative.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UNONative.m; sourceTree = ""; }; D1A0651C2A70664A00101BE6 /* UNOMetalViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UNOMetalViewDelegate.h; sourceTree = ""; }; @@ -105,8 +101,6 @@ D1FE7A2A2B75C8BB00ACFC76 /* UNOSoftView.m */, D1A065232A8AC15C00101BE6 /* UNOWindow.h */, D1A065242A8AC23800101BE6 /* UNOWindow.m */, - D15930D32CDEA3C0007B40FD /* UNOWebView.h */, - D15930D42CDEA3C0007B40FD /* UNOWebView.m */, ); path = UnoNativeMac; sourceTree = ""; @@ -118,7 +112,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - D15930D52CDEA3C0007B40FD /* UNOWebView.h in Headers */, D1F248A92A67288B008A609E /* UnoNativeMac.h in Headers */, D1A065202A8467B200101BE6 /* UNOApplication.h in Headers */, ); @@ -188,7 +181,6 @@ D1A065252A8AC23800101BE6 /* UNOWindow.m in Sources */, D1CC768B2AB9D464002A44F0 /* UNOClipboard.m in Sources */, D116C63E2AC79876004B975F /* UNOCursor.m in Sources */, - D15930D62CDEA3C0007B40FD /* UNOWebView.m in Sources */, D18D4FAC2C2DE804003E4BBF /* UNONative.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; From f20b0b864ef69e055f05c5561581588370386923 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Thu, 14 Nov 2024 15:20:34 -0500 Subject: [PATCH 16/17] fix: reverse clip and set fillrule to oddeven --- src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs | 7 ++++--- .../UnoNativeMac/UnoNativeMac/UNOWindow.m | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs index 6ceb64d78f3e..ee89a9fcf44d 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowHost.cs @@ -87,9 +87,10 @@ private void Draw(double nativeWidth, double nativeHeight, SKSurface surface) var path = SkiaRenderHelper.RenderRootVisualAndReturnPath(width, height, rootVisual, surface); if (path is { }) { - var svg = path.ToSvgPathData(); - // remove first path since it cover the whole area - NativeUno.uno_window_clip_svg(_nativeWindow.Handle, svg.Substring(svg.IndexOf('Z') + 1)); + using var negativePath = new SKPath(); + negativePath.AddRect(new SKRect(0, 0, width, height)); + using var diffPath = negativePath.Op(path, SKPathOp.Difference); + NativeUno.uno_window_clip_svg(_nativeWindow.Handle, diffPath.ToSvgPathData()); } } } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m index 6d149720118a..c70bfcee7c97 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m @@ -746,6 +746,7 @@ void uno_window_clip_svg(UNOWindow* window, const char* svg) } mask.fillColor = NSColor.blueColor.CGColor; // anything but clearColor mask.path = path; + mask.fillRule = kCAFillRuleEvenOdd; } } } From ab73de54d5d457b5abf1c84c327020a27198292d Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Thu, 14 Nov 2024 18:31:31 -0500 Subject: [PATCH 17/17] fix: parse floating point values in svg path --- .../UnoNativeMac/UnoNativeMac/UNOWindow.m | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m index c70bfcee7c97..6087ed76042d 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOWindow.m @@ -665,20 +665,10 @@ CGFloat readNextCoord(const char *svg, int *position, long length) #endif return result; } - for (int i=*position; i < length; i++) { - if (isdigit(svg[i])) { - if (isnan(result)) { - result = svg[i] - '0'; - } else { - result *= 10; - result += svg[i] - '0'; - } - *position = i; - } else if (!isnan(result)) { - *position = i - 1; - break; - } - } + const char* start = svg + *position; + char* end; + result = strtod(start, &end); + *position += (int)(end - start); return result; } @@ -697,11 +687,12 @@ void uno_window_clip_svg(UNOWindow* window, const char* svg) CGMutablePathRef path = CGPathCreateMutable(); // small subset of an SVG path parser handling trusted input of integer-based points long length = strlen(svg); - for (int i=0; i < length; i++) { + for (int i=0; i < length;) { CGFloat x, y; char op = svg[i]; switch (op) { case 'M': + i++; // skip M x = readNextCoord(svg, &i, length); i++; // skip separator y = readNextCoord(svg, &i, length); @@ -714,6 +705,7 @@ void uno_window_clip_svg(UNOWindow* window, const char* svg) CGPathMoveToPoint(path, nil, x, y); break; case 'L': + i++; // skip L x = readNextCoord(svg, &i, length); i++; // skip separator y = readNextCoord(svg, &i, length); @@ -726,6 +718,7 @@ void uno_window_clip_svg(UNOWindow* window, const char* svg) CGPathAddLineToPoint(path, nil, x, y); break; case 'Z': + i++; // skip Z #if DEBUG_PARSER NSLog(@"uno_window_clip_svg parsing CGPathCloseSubpath - position %d", i); #endif @@ -734,8 +727,9 @@ void uno_window_clip_svg(UNOWindow* window, const char* svg) #if DEBUG default: if (op != ' ') { - NSLog(@"uno_window_clip_svg parsing unknown op %c at position %d", op, i - 1); + NSLog(@"uno_window_clip_svg parsing unknown op %c at position %d", op, i); } + i++; // skip unknown op break; #endif }